Skip to content

Instantly share code, notes, and snippets.

@bbovenzi
Forked from csandman/README.md
Last active May 27, 2021 20:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bbovenzi/76a28701b7933420655925eefaa03dd5 to your computer and use it in GitHub Desktop.
Save bbovenzi/76a28701b7933420655925eefaa03dd5 to your computer and use it in GitHub Desktop.
A Chakra UI wrapper for react-select, made to be used as a multi-select which Chakra does not currently have (with Typescript support)
/* eslint-disable no-underscore-dangle */
import React from 'react';
import Select, { components as selectComponents, Props as SelectProps } from 'react-select';
import {
Flex,
Tag,
TagCloseButton,
TagLabel,
Divider,
CloseButton,
Center,
Box,
Portal,
StylesProvider,
useMultiStyleConfig,
useStyles,
useTheme,
useColorModeValue,
RecursiveCSSObject,
CSSWithMultiValues,
} from '@chakra-ui/react';
import { ChevronDownIcon } from '@chakra-ui/icons';
const MultiSelect = ({
name = '',
styles = {},
components = {},
...props
}: SelectProps): JSX.Element => {
const chakraTheme = useTheme();
const placeholderColor = useColorModeValue(
chakraTheme.colors.gray[400],
chakraTheme.colors.whiteAlpha[400],
);
const chakraStyles = {
input: (provided: Record<string, any>) => ({
...provided,
color: 'inherit',
lineHeight: 1,
}),
menu: (provided: Record<string, any>) => ({
...provided,
boxShadow: 'none',
}),
valueContainer: (provided: Record<string, any>) => ({
...provided,
padding: '0.125rem 1rem',
}),
};
return (
<Select
name={name}
components={{
...{
Control: ({
children, innerRef, innerProps, isDisabled, isFocused,
}) => {
const inputStyles = useMultiStyleConfig('Input', {});
return (
<StylesProvider value={inputStyles}>
<Flex
ref={innerRef}
sx={{
...inputStyles.field,
p: 0,
overflow: 'hidden',
h: 'auto',
minH: 10,
}}
{...innerProps}
{...(isFocused && { 'data-focus': true })}
{...(isDisabled && { disabled: true })}
>
{children}
</Flex>
</StylesProvider>
);
},
MultiValueContainer: ({
children,
innerRef,
innerProps,
data: { isFixed },
}) => (
<Tag
ref={innerRef}
{...innerProps}
m="0.125rem"
variant={isFixed ? 'solid' : 'subtle'}
>
{children}
</Tag>
),
MultiValueLabel: ({ children, innerRef, innerProps }) => (
<TagLabel ref={innerRef} {...innerProps}>
{children}
</TagLabel>
),
MultiValueRemove: ({
children, innerRef, innerProps, data: { isFixed },
}) => {
if (isFixed) {
return null;
}
return (
<TagCloseButton ref={innerRef} {...innerProps}>
{children}
</TagCloseButton>
);
},
IndicatorSeparator: ({ innerProps }) => (
<Divider
{...innerProps}
orientation="vertical"
opacity="1"
/>
),
ClearIndicator: ({ innerProps }) => (
<CloseButton {...innerProps} size="sm" mx={2} />
),
DropdownIndicator: ({ innerProps }) => {
const { addon } = useStyles();
return (
<Center
{...innerProps}
sx={{
...addon,
h: '100%',
p: 0,
borderRadius: 0,
borderWidth: 0,
cursor: 'pointer',
}}
>
<ChevronDownIcon h={5} w={5} />
</Center>
);
},
// Menu components
MenuPortal: ({ children, ...portalProps }) => (
<Portal {...portalProps}>
{children}
</Portal>
),
Menu: ({ children, ...menuProps }) => {
const menuStyles = useMultiStyleConfig('Menu', {});
return (
<selectComponents.Menu {...menuProps}>
<StylesProvider value={menuStyles}>{children}</StylesProvider>
</selectComponents.Menu>
);
},
MenuList: ({
innerRef, children, maxHeight,
}) => {
const { list } = useStyles();
return (
<Box
sx={{
...list,
maxH: `${maxHeight}px`,
overflowY: 'auto',
}}
ref={innerRef}
>
{children}
</Box>
);
},
GroupHeading: ({ innerProps, children }) => {
const { groupTitle } = useStyles();
return (
<Box sx={groupTitle} {...innerProps}>
{children}
</Box>
);
},
Option: ({
innerRef, innerProps, children, isFocused, isDisabled,
}) => {
const { item } = useStyles();
interface ItemProps extends CSSWithMultiValues {
_disabled: CSSWithMultiValues,
_focus: CSSWithMultiValues,
}
return (
<Box
sx={{
...item,
w: '100%',
textAlign: 'left',
cursor: 'pointer',
bg: isFocused ? (item as RecursiveCSSObject<ItemProps>)._focus.bg : 'transparent',
...(isDisabled && (item as RecursiveCSSObject<ItemProps>)._disabled),
}}
ref={innerRef}
{...innerProps}
{...(isDisabled && { disabled: true })}
>
{children}
</Box>
);
},
},
...components,
}}
styles={{
...chakraStyles,
...styles,
}}
theme={(baseTheme) => ({
...baseTheme,
borderRadius: chakraTheme.radii.md,
colors: {
...baseTheme.colors,
neutral50: placeholderColor, // placeholder text color
neutral40: placeholderColor, // noOptionsMessage color
},
})}
{...props}
/>
);
};
export default MultiSelect;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment