Commit 9800d2d4 authored by tom's avatar tom

update select styles

parent d926c427
......@@ -17,7 +17,6 @@ export interface SelectOption<Value extends string = string> {
}
export interface SelectControlProps extends ChakraSelect.ControlProps {
clearable?: boolean;
noIndicator?: boolean;
triggerProps?: ChakraSelect.TriggerProps;
loading?: boolean;
......@@ -28,18 +27,17 @@ export const SelectControl = React.forwardRef<
SelectControlProps
>(function SelectControl(props, ref) {
// 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 isDefaultValue = Array.isArray(defaultValue) ? context.value.every((item) => defaultValue.includes(item)) : context.value === defaultValue;
return (
<Skeleton loading={ loading } asChild>
<ChakraSelect.Control { ...rest } className="group">
<ChakraSelect.Trigger ref={ ref } { ...triggerProps } data-default-value={ isDefaultValue }>{ children }</ChakraSelect.Trigger>
{ (!noIndicator || clearable) && (
<ChakraSelect.Control { ...rest }>
<ChakraSelect.Trigger ref={ ref } className="group peer" { ...triggerProps } data-default-value={ isDefaultValue }>{ children }</ChakraSelect.Trigger>
{ (!noIndicator) && (
<ChakraSelect.IndicatorGroup>
{ clearable && <SelectClearTrigger/> }
{ !noIndicator && (
<ChakraSelect.Indicator
transform="rotate(-90deg)"
......@@ -56,7 +54,7 @@ export const SelectControl = React.forwardRef<
);
});
const SelectClearTrigger = React.forwardRef<
export const SelectClearTrigger = React.forwardRef<
HTMLButtonElement,
ChakraSelect.ClearTriggerProps
>(function SelectClearTrigger(props, ref) {
......@@ -93,12 +91,22 @@ export const SelectItem = React.forwardRef<
ChakraSelect.ItemProps
>(function SelectItem(props, ref) {
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 (
<ChakraSelect.Item key={ item.value } item={ item } { ...rest } ref={ ref }>
{ startElement }
{ children }
<ChakraSelect.ItemIndicator asChild>
<IconSvg name="check" boxSize={ 5 } flexShrink={ 0 }/>
<IconSvg name="check" boxSize={ 5 } flexShrink={ 0 } ml="auto"/>
</ChakraSelect.ItemIndicator>
{ children }
</ChakraSelect.Item>
);
});
......@@ -114,14 +122,40 @@ export const SelectValueText = React.forwardRef<
>(function SelectValueText(props, ref) {
const { children, ...rest } = props;
return (
<ChakraSelect.ValueText { ...rest } ref={ ref }>
<ChakraSelect.ValueText { ...rest } display="inline-flex" alignItems="center" flexWrap="nowrap" ref={ ref }>
<ChakraSelect.Context>
{ (select) => {
const items = select.selectedItems;
if (items.length === 0) return props.placeholder;
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`;
} }
</ChakraSelect.Context>
......
......@@ -291,16 +291,6 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
fg: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } },
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: {
bg: {
......
......@@ -60,12 +60,6 @@ export const recipe = defineSlotRecipe({
outline: {
field: selectSlotRecipe.variants?.variant.outline.trigger,
},
filter: {
field: selectSlotRecipe.variants?.variant.filter.trigger,
},
sort: {
field: selectSlotRecipe.variants?.variant.sort.trigger,
},
},
size: {
......
import { defineSlotRecipe } from '@chakra-ui/react';
// TODO @tom2drum check sizes for select
export const recipe = defineSlotRecipe({
slots: [ 'root', 'trigger', 'indicatorGroup', 'indicator', 'content', 'item', 'control', 'itemText', 'itemGroup', 'itemGroupLabel', 'label', 'valueText' ],
base: {
......@@ -25,7 +24,7 @@ export const recipe = defineSlotRecipe({
cursor: 'pointer',
focusVisibleRing: 'none',
_disabled: {
layerStyle: 'disabled',
opacity: 'control.disabled',
},
},
indicatorGroup: {
......@@ -38,15 +37,15 @@ export const recipe = defineSlotRecipe({
bottom: '0',
px: '0',
pointerEvents: 'none',
_peerHover: {
color: 'link.primary.hover',
},
},
indicator: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'inherit',
_groupHover: {
color: 'link.primary.hover',
},
_open: {
color: 'link.primary.hover',
},
......@@ -65,6 +64,7 @@ export const recipe = defineSlotRecipe({
overflowY: 'auto',
width: 'max-content',
minWidth: '150px',
rowGap: '2',
_open: {
animationStyle: 'slide-fade-in',
animationDuration: 'fast',
......@@ -87,11 +87,7 @@ export const recipe = defineSlotRecipe({
borderRadius: 'none',
_disabled: {
pointerEvents: 'none',
opacity: '0.5',
},
_icon: {
width: '4',
height: '4',
opacity: 'control.disabled',
},
_highlighted: {
bg: 'select.item.bg.highlighted',
......@@ -115,7 +111,7 @@ export const recipe = defineSlotRecipe({
userSelect: 'none',
textStyle: 'sm',
_disabled: {
layerStyle: 'disabled',
opacity: 'control.disabled',
},
},
valueText: {
......@@ -142,82 +138,12 @@ export const recipe = defineSlotRecipe({
},
_focusVisible: {
borderColor: 'link.primary.hover',
focusVisibleRing: 'none',
},
_invalid: {
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: {
......@@ -229,7 +155,7 @@ export const recipe = defineSlotRecipe({
},
content: {
px: '0',
py: '2',
py: '4',
textStyle: 'md',
},
trigger: {
......@@ -245,13 +171,9 @@ export const recipe = defineSlotRecipe({
pl: '1',
},
item: {
py: '2',
pr: '4',
pl: '44px',
_selected: {
py: '5px',
px: '4',
},
},
itemGroup: {
mt: '1',
},
......
......@@ -26,7 +26,6 @@ const PopoverFilterRadio = ({ name, hasActiveFilter, collection, isLoading, onCh
collection={ collection }
defaultValue={ initialValue ? [ initialValue ] : [ collection.items[0].value ] }
onValueChange={ handleValueChange }
variant="filter"
>
<SelectControl
triggerProps={{ asChild: true, px: { base: 1, lg: 2 } }}
......
......@@ -3,23 +3,22 @@ import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
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 IconSvg from 'ui/shared/IconSvg';
export interface Props extends SelectRootProps {
controlProps?: SelectControlProps;
isLoading?: boolean;
}
const Sort = (props: Props) => {
const { collection, controlProps, isLoading, ...rest } = props;
const { collection, isLoading, ...rest } = props;
const isMobile = useIsMobile(false);
const trigger = (() => {
if (isMobile) {
return (
<SelectControl { ...controlProps } triggerProps={{ asChild: true }} noIndicator>
<SelectControl triggerProps={{ asChild: true }} noIndicator>
<IconButton
loadingSkeleton={ isLoading }
aria-label="sort"
......@@ -34,7 +33,7 @@ const Sort = (props: Props) => {
return (
<SelectControl
{ ...controlProps }
triggerProps={{ borderColor: 'transparent' }}
loading={ isLoading }
>
<chakra.span
......@@ -42,19 +41,21 @@ const Sort = (props: Props) => {
fontWeight="normal"
color={{ _light: 'blackAlpha.600', _dark: 'whiteAlpha.600' }}
_groupHover={{ color: 'inherit' }}
_groupExpanded={{ color: 'inherit' }}
>
Sort by
</chakra.span>
<SelectValueText
color={{ _light: 'blackAlpha.800', _dark: 'whiteAlpha.800' }}
_groupHover={{ color: 'inherit' }}
_groupExpanded={{ color: 'inherit' }}
/>
</SelectControl>
);
})();
return (
<SelectRoot variant={{ lgDown: 'outline', lg: 'sort' }} collection={ collection } { ...rest }>
<SelectRoot collection={ collection } { ...rest }>
{ trigger }
<SelectContent>
{ collection.items.map((item) => (
......
......@@ -13,8 +13,8 @@ import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHead
const frameworks = createListCollection({
items: [
{ label: 'React.js', value: 'react' },
{ label: 'Vue.js', value: 'vue' },
{ label: 'React.js is the most popular framework', value: 'react', icon: 'API' },
{ label: 'Vue.js is the second most popular framework', value: 'vue' },
{ label: 'Angular', value: 'angular' },
{ label: 'Svelte', value: 'svelte' },
],
......@@ -49,20 +49,6 @@ const SelectShowcase = () => {
</SelectContent>
</SelectRoot>
</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>
</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