Commit 9800d2d4 authored by tom's avatar tom

update select styles

parent d926c427
...@@ -17,7 +17,6 @@ export interface SelectOption<Value extends string = string> { ...@@ -17,7 +17,6 @@ export interface SelectOption<Value extends string = string> {
} }
export interface SelectControlProps extends ChakraSelect.ControlProps { export interface SelectControlProps extends ChakraSelect.ControlProps {
clearable?: boolean;
noIndicator?: boolean; noIndicator?: boolean;
triggerProps?: ChakraSelect.TriggerProps; triggerProps?: ChakraSelect.TriggerProps;
loading?: boolean; loading?: boolean;
...@@ -28,18 +27,17 @@ export const SelectControl = React.forwardRef< ...@@ -28,18 +27,17 @@ export const SelectControl = React.forwardRef<
SelectControlProps SelectControlProps
>(function SelectControl(props, ref) { >(function SelectControl(props, ref) {
// NOTE: here defaultValue means the "default" option of the select, not its initial value // NOTE: here defaultValue means the "default" option of the select, not its initial value
const { children, clearable, noIndicator, triggerProps, loading, defaultValue, ...rest } = props; const { children, noIndicator, triggerProps, loading, defaultValue, ...rest } = props;
const context = useSelectContext(); const context = useSelectContext();
const isDefaultValue = Array.isArray(defaultValue) ? context.value.every((item) => defaultValue.includes(item)) : context.value === defaultValue; const isDefaultValue = Array.isArray(defaultValue) ? context.value.every((item) => defaultValue.includes(item)) : context.value === defaultValue;
return ( return (
<Skeleton loading={ loading } asChild> <Skeleton loading={ loading } asChild>
<ChakraSelect.Control { ...rest } className="group"> <ChakraSelect.Control { ...rest }>
<ChakraSelect.Trigger ref={ ref } { ...triggerProps } data-default-value={ isDefaultValue }>{ children }</ChakraSelect.Trigger> <ChakraSelect.Trigger ref={ ref } className="group peer" { ...triggerProps } data-default-value={ isDefaultValue }>{ children }</ChakraSelect.Trigger>
{ (!noIndicator || clearable) && ( { (!noIndicator) && (
<ChakraSelect.IndicatorGroup> <ChakraSelect.IndicatorGroup>
{ clearable && <SelectClearTrigger/> }
{ !noIndicator && ( { !noIndicator && (
<ChakraSelect.Indicator <ChakraSelect.Indicator
transform="rotate(-90deg)" transform="rotate(-90deg)"
...@@ -56,7 +54,7 @@ export const SelectControl = React.forwardRef< ...@@ -56,7 +54,7 @@ export const SelectControl = React.forwardRef<
); );
}); });
const SelectClearTrigger = React.forwardRef< export const SelectClearTrigger = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
ChakraSelect.ClearTriggerProps ChakraSelect.ClearTriggerProps
>(function SelectClearTrigger(props, ref) { >(function SelectClearTrigger(props, ref) {
...@@ -93,12 +91,22 @@ export const SelectItem = React.forwardRef< ...@@ -93,12 +91,22 @@ export const SelectItem = React.forwardRef<
ChakraSelect.ItemProps ChakraSelect.ItemProps
>(function SelectItem(props, ref) { >(function SelectItem(props, ref) {
const { item, children, ...rest } = props; const { item, children, ...rest } = props;
const startElement = (() => {
if (item.icon) {
return typeof item.icon === 'string' ? <IconSvg name={ item.icon } boxSize={ 5 } flexShrink={ 0 }/> : item.icon;
}
return null;
})();
return ( return (
<ChakraSelect.Item key={ item.value } item={ item } { ...rest } ref={ ref }> <ChakraSelect.Item key={ item.value } item={ item } { ...rest } ref={ ref }>
{ startElement }
{ children }
<ChakraSelect.ItemIndicator asChild> <ChakraSelect.ItemIndicator asChild>
<IconSvg name="check" boxSize={ 5 } flexShrink={ 0 }/> <IconSvg name="check" boxSize={ 5 } flexShrink={ 0 } ml="auto"/>
</ChakraSelect.ItemIndicator> </ChakraSelect.ItemIndicator>
{ children }
</ChakraSelect.Item> </ChakraSelect.Item>
); );
}); });
...@@ -114,14 +122,40 @@ export const SelectValueText = React.forwardRef< ...@@ -114,14 +122,40 @@ export const SelectValueText = React.forwardRef<
>(function SelectValueText(props, ref) { >(function SelectValueText(props, ref) {
const { children, ...rest } = props; const { children, ...rest } = props;
return ( return (
<ChakraSelect.ValueText { ...rest } ref={ ref }> <ChakraSelect.ValueText { ...rest } display="inline-flex" alignItems="center" flexWrap="nowrap" ref={ ref }>
<ChakraSelect.Context> <ChakraSelect.Context>
{ (select) => { { (select) => {
const items = select.selectedItems; const items = select.selectedItems;
if (items.length === 0) return props.placeholder; if (items.length === 0) return props.placeholder;
if (children) return children(items); if (children) return children(items);
if (items.length === 1)
return select.collection.stringifyItem(items[0]); if (items.length === 1) {
const item = items[0] as CollectionItem & { icon?: React.ReactNode };
const icon = (() => {
if (item.icon) {
return typeof item.icon === 'string' ? <IconSvg name={ item.icon } boxSize={ 5 } flexShrink={ 0 } mr={ 1 }/> : item.icon;
}
return null;
})();
return (
<>
{ icon }
<span style={{
WebkitLineClamp: 1,
WebkitBoxOrient: 'vertical',
display: '-webkit-box',
}}>
{ select.collection.stringifyItem(item) }
</span>
</>
);
}
// FIXME: we don't have multiple selection in the select yet
return `${ items.length } selected`; return `${ items.length } selected`;
} } } }
</ChakraSelect.Context> </ChakraSelect.Context>
......
...@@ -291,16 +291,6 @@ const semanticTokens: ThemingConfig['semanticTokens'] = { ...@@ -291,16 +291,6 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
fg: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, fg: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } },
border: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.600}' } }, border: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.600}' } },
}, },
filter: {
fg: {
DEFAULT: { value: '{colors.button.dropdown.fg}' },
selected: { value: '{colors.button.dropdown.fg.selected}' },
},
border: {
DEFAULT: { value: '{colors.button.dropdown.border}' },
selected: { value: '{colors.button.dropdown.border.selected}' },
},
},
}, },
item: { item: {
bg: { bg: {
......
...@@ -60,12 +60,6 @@ export const recipe = defineSlotRecipe({ ...@@ -60,12 +60,6 @@ export const recipe = defineSlotRecipe({
outline: { outline: {
field: selectSlotRecipe.variants?.variant.outline.trigger, field: selectSlotRecipe.variants?.variant.outline.trigger,
}, },
filter: {
field: selectSlotRecipe.variants?.variant.filter.trigger,
},
sort: {
field: selectSlotRecipe.variants?.variant.sort.trigger,
},
}, },
size: { size: {
......
import { defineSlotRecipe } from '@chakra-ui/react'; import { defineSlotRecipe } from '@chakra-ui/react';
// TODO @tom2drum check sizes for select
export const recipe = defineSlotRecipe({ export const recipe = defineSlotRecipe({
slots: [ 'root', 'trigger', 'indicatorGroup', 'indicator', 'content', 'item', 'control', 'itemText', 'itemGroup', 'itemGroupLabel', 'label', 'valueText' ], slots: [ 'root', 'trigger', 'indicatorGroup', 'indicator', 'content', 'item', 'control', 'itemText', 'itemGroup', 'itemGroupLabel', 'label', 'valueText' ],
base: { base: {
...@@ -25,7 +24,7 @@ export const recipe = defineSlotRecipe({ ...@@ -25,7 +24,7 @@ export const recipe = defineSlotRecipe({
cursor: 'pointer', cursor: 'pointer',
focusVisibleRing: 'none', focusVisibleRing: 'none',
_disabled: { _disabled: {
layerStyle: 'disabled', opacity: 'control.disabled',
}, },
}, },
indicatorGroup: { indicatorGroup: {
...@@ -38,15 +37,15 @@ export const recipe = defineSlotRecipe({ ...@@ -38,15 +37,15 @@ export const recipe = defineSlotRecipe({
bottom: '0', bottom: '0',
px: '0', px: '0',
pointerEvents: 'none', pointerEvents: 'none',
_peerHover: {
color: 'link.primary.hover',
},
}, },
indicator: { indicator: {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
color: 'inherit', color: 'inherit',
_groupHover: {
color: 'link.primary.hover',
},
_open: { _open: {
color: 'link.primary.hover', color: 'link.primary.hover',
}, },
...@@ -65,6 +64,7 @@ export const recipe = defineSlotRecipe({ ...@@ -65,6 +64,7 @@ export const recipe = defineSlotRecipe({
overflowY: 'auto', overflowY: 'auto',
width: 'max-content', width: 'max-content',
minWidth: '150px', minWidth: '150px',
rowGap: '2',
_open: { _open: {
animationStyle: 'slide-fade-in', animationStyle: 'slide-fade-in',
animationDuration: 'fast', animationDuration: 'fast',
...@@ -87,11 +87,7 @@ export const recipe = defineSlotRecipe({ ...@@ -87,11 +87,7 @@ export const recipe = defineSlotRecipe({
borderRadius: 'none', borderRadius: 'none',
_disabled: { _disabled: {
pointerEvents: 'none', pointerEvents: 'none',
opacity: '0.5', opacity: 'control.disabled',
},
_icon: {
width: '4',
height: '4',
}, },
_highlighted: { _highlighted: {
bg: 'select.item.bg.highlighted', bg: 'select.item.bg.highlighted',
...@@ -115,7 +111,7 @@ export const recipe = defineSlotRecipe({ ...@@ -115,7 +111,7 @@ export const recipe = defineSlotRecipe({
userSelect: 'none', userSelect: 'none',
textStyle: 'sm', textStyle: 'sm',
_disabled: { _disabled: {
layerStyle: 'disabled', opacity: 'control.disabled',
}, },
}, },
valueText: { valueText: {
...@@ -142,82 +138,12 @@ export const recipe = defineSlotRecipe({ ...@@ -142,82 +138,12 @@ export const recipe = defineSlotRecipe({
}, },
_focusVisible: { _focusVisible: {
borderColor: 'link.primary.hover', borderColor: 'link.primary.hover',
focusVisibleRing: 'none',
}, },
_invalid: { _invalid: {
borderColor: 'border.error', borderColor: 'border.error',
}, },
}, },
}, },
filter: {
trigger: {
borderWidth: '2px',
color: 'select.trigger.filter.fg.selected',
bgColor: 'select.trigger.filter.border.selected',
borderColor: 'select.trigger.filter.border.selected',
_expanded: {
color: 'select.trigger.filter.fg.selected',
bgColor: 'select.trigger.filter.border.selected',
borderColor: 'select.trigger.filter.border.selected',
},
_hover: {
color: 'select.trigger.filter.fg.selected',
borderColor: 'select.trigger.filter.border.selected',
bgColor: 'select.trigger.filter.border.selected',
},
_focusVisible: {
borderColor: 'link.primary.hover',
focusVisibleRing: 'none',
},
_placeholderShown: {
color: 'select.trigger.filter.fg',
borderColor: 'select.trigger.filter.border',
bgColor: 'transparent',
_hover: {
color: 'link.primary.hover',
borderColor: 'link.primary.hover',
bgColor: 'transparent',
},
},
// If the default value is selected, the styles should be the same as when the placeholder is shown
'&[data-default-value="true"]': {
color: 'select.trigger.filter.fg',
borderColor: 'select.trigger.filter.border',
bgColor: 'transparent',
_hover: {
color: 'link.primary.hover',
borderColor: 'link.primary.hover',
bgColor: 'transparent',
},
_expanded: {
color: 'inherit',
borderColor: 'inherit',
bgColor: 'inherit',
},
},
},
},
sort: {
trigger: {
borderWidth: '2px',
borderColor: 'transparent',
bgColor: 'transparent',
_hover: {
color: 'link.primary.hover',
borderColor: 'link.primary.hover',
},
_open: {
bg: 'button.dropdown.border.selected',
color: 'button.dropdown.fg.selected',
borderColor: 'button.dropdown.border.selected',
_hover: {
bg: 'button.dropdown.border.selected',
color: 'button.dropdown.fg.selected',
borderColor: 'button.dropdown.border.selected',
},
},
},
},
}, },
size: { size: {
...@@ -229,7 +155,7 @@ export const recipe = defineSlotRecipe({ ...@@ -229,7 +155,7 @@ export const recipe = defineSlotRecipe({
}, },
content: { content: {
px: '0', px: '0',
py: '2', py: '4',
textStyle: 'md', textStyle: 'md',
}, },
trigger: { trigger: {
...@@ -245,12 +171,8 @@ export const recipe = defineSlotRecipe({ ...@@ -245,12 +171,8 @@ export const recipe = defineSlotRecipe({
pl: '1', pl: '1',
}, },
item: { item: {
py: '2', py: '5px',
pr: '4', px: '4',
pl: '44px',
_selected: {
px: '4',
},
}, },
itemGroup: { itemGroup: {
mt: '1', mt: '1',
......
...@@ -26,7 +26,6 @@ const PopoverFilterRadio = ({ name, hasActiveFilter, collection, isLoading, onCh ...@@ -26,7 +26,6 @@ const PopoverFilterRadio = ({ name, hasActiveFilter, collection, isLoading, onCh
collection={ collection } collection={ collection }
defaultValue={ initialValue ? [ initialValue ] : [ collection.items[0].value ] } defaultValue={ initialValue ? [ initialValue ] : [ collection.items[0].value ] }
onValueChange={ handleValueChange } onValueChange={ handleValueChange }
variant="filter"
> >
<SelectControl <SelectControl
triggerProps={{ asChild: true, px: { base: 1, lg: 2 } }} triggerProps={{ asChild: true, px: { base: 1, lg: 2 } }}
......
...@@ -3,23 +3,22 @@ import React from 'react'; ...@@ -3,23 +3,22 @@ import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { IconButton } from 'toolkit/chakra/icon-button'; import { IconButton } from 'toolkit/chakra/icon-button';
import type { SelectRootProps, SelectControlProps } from 'toolkit/chakra/select'; import type { SelectRootProps } from 'toolkit/chakra/select';
import { SelectContent, SelectItem, SelectRoot, SelectControl, SelectValueText } from 'toolkit/chakra/select'; import { SelectContent, SelectItem, SelectRoot, SelectControl, SelectValueText } from 'toolkit/chakra/select';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
export interface Props extends SelectRootProps { export interface Props extends SelectRootProps {
controlProps?: SelectControlProps;
isLoading?: boolean; isLoading?: boolean;
} }
const Sort = (props: Props) => { const Sort = (props: Props) => {
const { collection, controlProps, isLoading, ...rest } = props; const { collection, isLoading, ...rest } = props;
const isMobile = useIsMobile(false); const isMobile = useIsMobile(false);
const trigger = (() => { const trigger = (() => {
if (isMobile) { if (isMobile) {
return ( return (
<SelectControl { ...controlProps } triggerProps={{ asChild: true }} noIndicator> <SelectControl triggerProps={{ asChild: true }} noIndicator>
<IconButton <IconButton
loadingSkeleton={ isLoading } loadingSkeleton={ isLoading }
aria-label="sort" aria-label="sort"
...@@ -34,7 +33,7 @@ const Sort = (props: Props) => { ...@@ -34,7 +33,7 @@ const Sort = (props: Props) => {
return ( return (
<SelectControl <SelectControl
{ ...controlProps } triggerProps={{ borderColor: 'transparent' }}
loading={ isLoading } loading={ isLoading }
> >
<chakra.span <chakra.span
...@@ -42,19 +41,21 @@ const Sort = (props: Props) => { ...@@ -42,19 +41,21 @@ const Sort = (props: Props) => {
fontWeight="normal" fontWeight="normal"
color={{ _light: 'blackAlpha.600', _dark: 'whiteAlpha.600' }} color={{ _light: 'blackAlpha.600', _dark: 'whiteAlpha.600' }}
_groupHover={{ color: 'inherit' }} _groupHover={{ color: 'inherit' }}
_groupExpanded={{ color: 'inherit' }}
> >
Sort by Sort by
</chakra.span> </chakra.span>
<SelectValueText <SelectValueText
color={{ _light: 'blackAlpha.800', _dark: 'whiteAlpha.800' }} color={{ _light: 'blackAlpha.800', _dark: 'whiteAlpha.800' }}
_groupHover={{ color: 'inherit' }} _groupHover={{ color: 'inherit' }}
_groupExpanded={{ color: 'inherit' }}
/> />
</SelectControl> </SelectControl>
); );
})(); })();
return ( return (
<SelectRoot variant={{ lgDown: 'outline', lg: 'sort' }} collection={ collection } { ...rest }> <SelectRoot collection={ collection } { ...rest }>
{ trigger } { trigger }
<SelectContent> <SelectContent>
{ collection.items.map((item) => ( { collection.items.map((item) => (
......
...@@ -13,8 +13,8 @@ import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHead ...@@ -13,8 +13,8 @@ import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHead
const frameworks = createListCollection({ const frameworks = createListCollection({
items: [ items: [
{ label: 'React.js', value: 'react' }, { label: 'React.js is the most popular framework', value: 'react', icon: 'API' },
{ label: 'Vue.js', value: 'vue' }, { label: 'Vue.js is the second most popular framework', value: 'vue' },
{ label: 'Angular', value: 'angular' }, { label: 'Angular', value: 'angular' },
{ label: 'Svelte', value: 'svelte' }, { label: 'Svelte', value: 'svelte' },
], ],
...@@ -49,20 +49,6 @@ const SelectShowcase = () => { ...@@ -49,20 +49,6 @@ const SelectShowcase = () => {
</SelectContent> </SelectContent>
</SelectRoot> </SelectRoot>
</Sample> </Sample>
<Sample label="variant: filter">
<SelectRoot collection={ frameworks } variant="filter">
<SelectControl w="200px" noIndicator>
<SelectValueText placeholder="Select framework"/>
</SelectControl>
<SelectContent>
{ frameworks.items.map((framework) => (
<SelectItem item={ framework } key={ framework.value }>
{ framework.label }
</SelectItem>
)) }
</SelectContent>
</SelectRoot>
</Sample>
</SamplesStack> </SamplesStack>
</Section> </Section>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment