@smashing/select-menu
Version:
Component used to select options
95 lines (94 loc) • 5.8 kB
JavaScript
import * as React from 'react';
import styled from 'styled-components';
import { Strong } from '@smashing/typography';
import { Menu } from '@smashing/menu';
import { safeInvoke, useDefaults } from '@smashing/theme';
import * as S from './styles';
const SelectMenuFC = (props) => {
const defaults = useDefaults('selectMenu', props, {
height: 32,
hasCloseButton: true,
isSelectable: true,
arrowIcon: undefined,
popoverAppearance: 'card',
appearance: 'default',
placeholder: undefined,
placeholderForMultipleSelected: (selected => `${selected.length} items`)
});
// extract allowed props than can be passed to the popover component
const { content, onOpenStarted, minWidth, children, isShown, ...popoverProps } = props.popoverProps || {};
const [currentFilter, setCurrentFilter] = React.useState('');
const menuList = React.useRef(null);
const popoverPropsForAppearance = React.useMemo(() => ({
accordion: {
appearance: 'accordion',
matchTargetWidth: true,
transitionType: 'expand',
targetOffset: -1
},
card: {}
}[defaults.popoverAppearance || 'accordion']), [defaults.popoverAppearance]);
const filteredOptions = React.useMemo(() => {
const options = props.hideSelectedItem && props.value
? props.options.filter(o => o.value !== props.value)
: props.options;
if (!currentFilter.trim()) {
return options;
}
return options.filter(o => `${o.label} ${o.value}`
.toLowerCase()
.indexOf(currentFilter.trim().toLowerCase()) > -1);
}, [props.hideSelectedItem, props.value, currentFilter, props.options]);
const scrollToSelectedItem = React.useCallback(() => {
if (menuList.current) {
const selectedOption = menuList.current.querySelector('[aria-checked="true"]');
if (selectedOption !== null) {
menuList.current.scrollTo(0, selectedOption.offsetTop);
}
}
}, [menuList.current]);
const placeholder = React.useMemo(() => {
const selected = props.options.filter(item => Array.isArray(props.value)
? props.value.includes(item.value)
: props.value === item.value);
const hasOneSelectedValue = selected.length === 1;
if (typeof defaults.placeholder === 'function') {
return defaults.placeholder(selected);
}
if (selected.length === 0) {
return defaults.placeholder || 'Select...';
}
if (!Array.isArray(props.value) || hasOneSelectedValue) {
return selected[0].label;
}
if (typeof defaults.placeholderForMultipleSelected === 'function') {
return defaults.placeholderForMultipleSelected(selected);
}
return `Items: ${selected.length}`;
}, [props.value]);
return (React.createElement(S.Popover, Object.assign({}, popoverPropsForAppearance, { invalid: props.invalid, minWidth: 150, buttonAppearance: defaults.appearance, onOpenStarted: scrollToSelectedItem, exactPosition: true, content: ({ close }) => (React.createElement(React.Fragment, null,
props.hasTitle && (React.createElement(S.PopoverHeader, null,
React.createElement(S.Title, { variant: 300 }, props.title),
defaults.hasCloseButton && (React.createElement(S.CloseButton, { appearance: "minimal", height: 24, onClick: close },
React.createElement("svg", { viewBox: "0 0 16 16" },
React.createElement("path", { d: "M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L9.41 8z", fillRule: "evenodd" })))))),
props.hasFilter && (React.createElement(S.FilterInput, { appearance: "underline", value: currentFilter, onChange: (event) => setCurrentFilter(event.target.value), placeholder: "Filter..." })),
React.createElement(S.MenuContainer, { height: popoverProps.height, ref: menuList }, filteredOptions.length > 0 ? (React.createElement(Menu, null,
React.createElement(Menu.OptionsGroup, { options: filteredOptions, value: props.value, itemHeight: defaults.height, itemIsSelectable: defaults.isSelectable, separated: defaults.popoverAppearance === 'accordion', invalid: props.invalid, onChange: value => {
safeInvoke(props.onChange, value);
}, onSelect: option => {
safeInvoke(props.onSelect, option.value);
if (!defaults.isSelectable) {
close();
}
}, onDeselect: option => {
safeInvoke(props.onDeselect, option.value);
} }))) : (React.createElement(S.EmptyView, null,
React.createElement(Strong, { variant: 300 }, "No items found")))))), position: "bottom-left" }, popoverProps), typeof props.children === 'function' ? (popoverChildrenProps => children({
...popoverChildrenProps,
selectedItems: props.value || []
})) : (React.createElement(S.Button, Object.assign({ className: props.className, width: props.width, invalid: props.invalid, disabled: props.disabled, appearance: defaults.appearance, popoverAppearance: defaults.popoverAppearance, type: "button", iconAfter: defaults.arrowIcon }, (defaults.height ? { height: defaults.height } : {})),
React.createElement(S.PlaceholderText, null, placeholder)))));
};
const SelectMenu = styled(SelectMenuFC) ``;
export { SelectMenu };