mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-26 06:47:17 +10:00
Create FilterPopup component
This commit is contained in:
parent
b928c95a3b
commit
0870f01b13
5 changed files with 293 additions and 0 deletions
77
src/components/FilterPopup/FilterPopup.css
Normal file
77
src/components/FilterPopup/FilterPopup.css
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
.root {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.label {
|
||||
@apply --marketplaceButtonStylesSecondary;
|
||||
@apply --marketplaceSearchFilterLabelFontStyles;
|
||||
|
||||
padding: 9px 16px 10px 16px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
border-radius: 4px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
background-color: var(--matterColorLight);
|
||||
border-color: transparent;
|
||||
text-decoration: none;
|
||||
box-shadow: var(--boxShadowFilterButton);
|
||||
}
|
||||
}
|
||||
|
||||
.labelSelected {
|
||||
@apply --marketplaceButtonStyles;
|
||||
@apply --marketplaceSearchFilterLabelFontStyles;
|
||||
font-weight: var(--fontWeightSemiBold);
|
||||
|
||||
padding: 9px 16px 10px 16px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--marketplaceColor);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border: 1px solid var(--marketplaceColorDark);
|
||||
}
|
||||
}
|
||||
|
||||
.popup {
|
||||
/* By default hide the content */
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
/* Position */
|
||||
position: absolute;
|
||||
z-index: var(--zIndexPopup);
|
||||
|
||||
/* Layout */
|
||||
min-width: 300px;
|
||||
margin-top: 7px;
|
||||
background-color: var(--matterColorLight);
|
||||
|
||||
/* Borders */
|
||||
border-top: 1px solid var(--matterColorNegative);
|
||||
box-shadow: var(--boxShadowPopup);
|
||||
border-radius: 4px;
|
||||
transition: var(--transitionStyleButton);
|
||||
}
|
||||
|
||||
.popupSize {
|
||||
padding: 15px 30px 17px 30px;
|
||||
}
|
||||
|
||||
.isOpen {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
25
src/components/FilterPopup/FilterPopup.example.js
Normal file
25
src/components/FilterPopup/FilterPopup.example.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import { FieldTextInput } from '../../components';
|
||||
import FilterPopup from './FilterPopup';
|
||||
|
||||
const id = 'FilterPopupExample';
|
||||
const field = <FieldTextInput type="text" id={`${id}.input1`} name="input1" label="Input:" />;
|
||||
|
||||
export const FilterPopupExample = {
|
||||
component: FilterPopup,
|
||||
props: {
|
||||
id,
|
||||
liveEdit: false,
|
||||
showAsPopup: true,
|
||||
urlParam: 'example',
|
||||
initialValues: {},
|
||||
isSelected: false,
|
||||
contentPlacementOffset: -14,
|
||||
onSubmit: (urlParam, values) => {
|
||||
console.log(`onSubmit with urlParam: ${urlParam} and values: ${JSON.stringify(values)}`);
|
||||
},
|
||||
label: 'Example label',
|
||||
children: field,
|
||||
},
|
||||
group: 'misc',
|
||||
};
|
||||
188
src/components/FilterPopup/FilterPopup.js
Normal file
188
src/components/FilterPopup/FilterPopup.js
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
import React, { Component } from 'react';
|
||||
import { bool, func, node, number, object, string } from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { injectIntl, intlShape } from 'react-intl';
|
||||
|
||||
import { FilterForm } from '../../forms';
|
||||
import css from './FilterPopup.css';
|
||||
|
||||
const KEY_CODE_ESCAPE = 27;
|
||||
|
||||
class FilterPopup extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { isOpen: false };
|
||||
this.filter = null;
|
||||
this.filterContent = null;
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleClear = this.handleClear.bind(this);
|
||||
this.handleCancel = this.handleCancel.bind(this);
|
||||
this.handleBlur = this.handleBlur.bind(this);
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||
this.toggleOpen = this.toggleOpen.bind(this);
|
||||
this.positionStyleForContent = this.positionStyleForContent.bind(this);
|
||||
}
|
||||
|
||||
handleSubmit(values) {
|
||||
const { onSubmit, urlParam } = this.props;
|
||||
this.setState({ isOpen: false });
|
||||
onSubmit(urlParam, values);
|
||||
}
|
||||
|
||||
handleClear() {
|
||||
const { onSubmit, onClear, urlParam } = this.props;
|
||||
this.setState({ isOpen: false });
|
||||
|
||||
if (onClear) {
|
||||
onClear();
|
||||
}
|
||||
|
||||
onSubmit(urlParam, null);
|
||||
}
|
||||
|
||||
handleCancel() {
|
||||
const { onSubmit, onCancel, initialValues, urlParam } = this.props;
|
||||
this.setState({ isOpen: false });
|
||||
|
||||
if (onCancel) {
|
||||
onCancel();
|
||||
}
|
||||
|
||||
onSubmit(urlParam, initialValues);
|
||||
}
|
||||
|
||||
handleBlur(event) {
|
||||
// FocusEvent is fired faster than the link elements native click handler
|
||||
// gets its own event. Therefore, we need to check the origin of this FocusEvent.
|
||||
if (event.relatedTarget && !this.filter.contains(event.relatedTarget)) {
|
||||
this.setState({ isOpen: false });
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
// Gather all escape presses to close menu
|
||||
if (e.keyCode === KEY_CODE_ESCAPE) {
|
||||
this.toggleOpen(false);
|
||||
}
|
||||
}
|
||||
|
||||
toggleOpen(enforcedState) {
|
||||
if (enforcedState) {
|
||||
this.setState({ isOpen: enforcedState });
|
||||
} else {
|
||||
this.setState(prevState => ({ isOpen: !prevState.isOpen }));
|
||||
}
|
||||
}
|
||||
|
||||
positionStyleForContent() {
|
||||
if (this.filter && this.filterContent) {
|
||||
// Render the filter content to the right from the menu
|
||||
// unless there's no space in which case it is rendered
|
||||
// to the left
|
||||
const distanceToRight = window.innerWidth - this.filter.getBoundingClientRect().right;
|
||||
const labelWidth = this.filter.offsetWidth;
|
||||
const contentWidth = this.filterContent.offsetWidth;
|
||||
const contentWidthBiggerThanLabel = contentWidth - labelWidth;
|
||||
const renderToRight = distanceToRight > contentWidthBiggerThanLabel;
|
||||
const contentPlacementOffset = this.props.contentPlacementOffset;
|
||||
|
||||
const offset = renderToRight
|
||||
? { left: contentPlacementOffset }
|
||||
: { right: contentPlacementOffset };
|
||||
// set a min-width if the content is narrower than the label
|
||||
const minWidth = contentWidth < labelWidth ? { minWidth: labelWidth } : null;
|
||||
|
||||
return { ...offset, ...minWidth };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
rootClassName,
|
||||
className,
|
||||
popupClassName,
|
||||
id,
|
||||
label,
|
||||
isSelected,
|
||||
children,
|
||||
initialValues,
|
||||
contentPlacementOffset,
|
||||
} = this.props;
|
||||
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
const popupClasses = classNames(css.popup, { [css.isOpen]: this.state.isOpen });
|
||||
const popupSizeClasses = popupClassName || css.popupSize;
|
||||
const labelStyles = isSelected ? css.labelSelected : css.label;
|
||||
const contentStyle = this.positionStyleForContent();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
onBlur={this.handleBlur}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
ref={node => {
|
||||
this.filter = node;
|
||||
}}
|
||||
>
|
||||
<button className={labelStyles} onClick={() => this.toggleOpen()}>
|
||||
{label}
|
||||
</button>
|
||||
<div
|
||||
id={id}
|
||||
className={popupClasses}
|
||||
ref={node => {
|
||||
this.filterContent = node;
|
||||
}}
|
||||
style={contentStyle}
|
||||
>
|
||||
{this.state.isOpen ? (
|
||||
<FilterForm
|
||||
id={`${id}.form`}
|
||||
paddingClasses={popupSizeClasses}
|
||||
showAsPopup
|
||||
contentPlacementOffset={contentPlacementOffset}
|
||||
initialValues={initialValues}
|
||||
onSubmit={this.handleSubmit}
|
||||
onCancel={this.handleCancel}
|
||||
onClear={this.handleClear}
|
||||
>
|
||||
{children}
|
||||
</FilterForm>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FilterPopup.defaultProps = {
|
||||
rootClassName: null,
|
||||
className: null,
|
||||
popupClassName: null,
|
||||
initialValues: null,
|
||||
contentPlacementOffset: 0,
|
||||
liveEdit: false,
|
||||
label: null,
|
||||
};
|
||||
|
||||
FilterPopup.propTypes = {
|
||||
rootClassName: string,
|
||||
className: string,
|
||||
popupClassName: string,
|
||||
id: string.isRequired,
|
||||
urlParam: string.isRequired,
|
||||
onSubmit: func.isRequired,
|
||||
initialValues: object,
|
||||
contentPlacementOffset: number,
|
||||
label: string.isRequired,
|
||||
isSelected: bool.isRequired,
|
||||
children: node.isRequired,
|
||||
|
||||
// form injectIntl
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(FilterPopup);
|
||||
|
|
@ -29,6 +29,7 @@ export { default as FieldReviewRating } from './FieldReviewRating/FieldReviewRat
|
|||
export { default as FieldSelect } from './FieldSelect/FieldSelect';
|
||||
export { default as FieldTextInput } from './FieldTextInput/FieldTextInput';
|
||||
export { default as FilterPlain } from './FilterPlain/FilterPlain';
|
||||
export { default as FilterPopup } from './FilterPopup/FilterPopup';
|
||||
export { default as Footer } from './Footer/Footer';
|
||||
export { default as Form } from './Form/Form';
|
||||
export { default as IconAdd } from './IconAdd/IconAdd';
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import * as FieldReviewRating from './components/FieldReviewRating/FieldReviewRa
|
|||
import * as FieldSelect from './components/FieldSelect/FieldSelect.example';
|
||||
import * as FieldTextInput from './components/FieldTextInput/FieldTextInput.example';
|
||||
import * as FilterPlain from './components/FilterPlain/FilterPlain.example';
|
||||
import * as FilterPopup from './components/FilterPopup/FilterPopup.example';
|
||||
import * as Footer from './components/Footer/Footer.example';
|
||||
import * as IconAdd from './components/IconAdd/IconAdd.example';
|
||||
import * as IconBannedUser from './components/IconBannedUser/IconBannedUser.example';
|
||||
|
|
@ -122,6 +123,7 @@ export {
|
|||
FieldTextInput,
|
||||
FilterForm,
|
||||
FilterPlain,
|
||||
FilterPopup,
|
||||
Footer,
|
||||
IconAdd,
|
||||
IconBannedUser,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue