Skip to content

Instantly share code, notes, and snippets.

@coehne
Forked from csandman/README.md
Created April 30, 2021 10:06
Show Gist options
  • Save coehne/4862a4d2eb45eea953a76daa9c4cd8ee to your computer and use it in GitHub Desktop.
Save coehne/4862a4d2eb45eea953a76daa9c4cd8ee 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
/* eslint-disable no-underscore-dangle */
// Demo: https://codesandbox.io/s/chakra-ui-react-select-648uv?file=/multi-select.js
import React from 'react';
import Select, { components as selectComponents } from 'react-select';
import {
Flex,
Tag,
TagCloseButton,
TagLabel,
Divider,
CloseButton,
Center,
Box,
Portal,
StylesProvider,
useMultiStyleConfig,
useStyles,
useTheme,
useColorModeValue,
} from '@chakra-ui/react';
import { ChevronDownIcon } from '@chakra-ui/icons';
const chakraStyles = {
input: (provided) => ({
...provided,
color: 'inherit',
lineHeight: 1,
}),
menu: (provided) => ({
...provided,
boxShadow: 'none',
}),
valueContainer: (provided) => ({
...provided,
padding: '0.125rem 1rem',
}),
control: () => {},
menuList: () => {},
option: () => {},
multiValue: () => {},
multiValueLabel: () => {},
multiValueRemove: () => {},
group: () => {},
};
const chakraComponents = {
// Control 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: ({ innerRef, innerProps }) => (
<Divider
ref={innerRef}
{...innerProps}
orientation="vertical"
opacity="1"
/>
),
ClearIndicator: ({ innerRef, innerProps }) => (
<CloseButton ref={innerRef} {...innerProps} size="sm" mx={2} />
),
DropdownIndicator: ({ innerRef, innerProps }) => {
const { addon } = useStyles();
return (
<Center
ref={innerRef}
{...innerProps}
sx={{
...addon,
h: '100%',
borderRadius: 0,
borderWidth: 0,
cursor: 'pointer',
}}
>
<ChevronDownIcon h={5} w={5} />
</Center>
);
},
// Menu components
MenuPortal: ({ innerRef, innerProps, children }) => (
<Portal ref={innerRef} {...innerProps}>
{children}
</Portal>
),
Menu: ({ children, ...props }) => {
const menuStyles = useMultiStyleConfig('Menu');
return (
<selectComponents.Menu {...props}>
<StylesProvider value={menuStyles}>{children}</StylesProvider>
</selectComponents.Menu>
);
},
MenuList: ({ innerRef, innerProps, children, maxHeight }) => {
const { list } = useStyles();
return (
<Box
sx={{
...list,
maxH: `${maxHeight}px`,
overflowY: 'auto',
}}
ref={innerRef}
{...innerProps}
>
{children}
</Box>
);
},
GroupHeading: ({ innerProps, children }) => {
const { groupTitle } = useStyles();
return (
<Box sx={groupTitle} {...innerProps}>
{children}
</Box>
);
},
Option: ({ innerRef, innerProps, children, isFocused, isDisabled }) => {
const { item } = useStyles();
return (
<Box
as="button"
sx={{
...item,
w: '100%',
textAlign: 'left',
bg: isFocused ? item._focus.bg : 'transparent',
...(isDisabled && item._disabled),
}}
ref={innerRef}
{...innerProps}
{...(isDisabled && { disabled: true })}
>
{children}
</Box>
);
},
};
const MultiSelect = ({
name = '',
styles = {},
components = {},
theme = {},
...props
}) => {
const chakraTheme = useTheme();
const placeholderColor = useColorModeValue(
chakraTheme.colors.gray[400],
chakraTheme.colors.whiteAlpha[400]
);
return (
<Select
name={name}
components={{
...chakraComponents,
...components,
}}
styles={{
...chakraStyles,
...styles,
}}
theme={(baseTheme) => ({
...baseTheme,
borderRadius: chakraTheme.radii.md,
colors: {
...baseTheme.colors,
neutral50: placeholderColor, // placeholder text color
neutral40: placeholderColor, // noOptionsMessage color
...theme.colors,
},
spacing: {
...baseTheme.spacing,
...theme.spacing,
},
})}
{...props}
/>
);
};
export default MultiSelect;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment