fix(SW-1509): removed support for plain array items in order to handle proper props extending

various fixes for supporting default selected
This commit is contained in:
Christian Andolf
2025-04-10 14:37:52 +02:00
parent 2b6fe17c32
commit a93ed00ee5
5 changed files with 83 additions and 95 deletions

View File

@@ -14,9 +14,15 @@ export default meta
type Story = StoryObj<typeof Select> type Story = StoryObj<typeof Select>
const items = [
{ label: 'Foo', value: 'foo' },
{ label: 'Bar', value: 'bar' },
{ label: 'Baz', value: 'baz' },
]
export const Default: Story = { export const Default: Story = {
args: { args: {
items: ['Foo', 'Bar', 'Baz'], items,
label: 'Select an item', label: 'Select an item',
name: 'foo', name: 'foo',
}, },
@@ -24,40 +30,38 @@ export const Default: Story = {
export const DefaultSelected: Story = { export const DefaultSelected: Story = {
args: { args: {
items: ['Foo', 'Bar', 'Baz'], items,
label: 'Select an item',
name: 'foo',
defaultSelectedKey: 'Foo',
},
}
export const ObjectItem: Story = {
args: {
items: [
{ label: 'Foo', value: 'foo' },
{ label: 'Bar', value: 'bar' },
{ label: 'Baz', value: 'baz' },
],
label: 'Select an item', label: 'Select an item',
name: 'foo', name: 'foo',
defaultSelectedKey: 'foo',
}, },
} }
export const Icons: Story = { export const Icons: Story = {
args: { args: {
icon: 'star', items,
itemIcon: 'check',
items: ['Foo', 'Bar', 'Baz'],
label: 'Select an item', label: 'Select an item',
name: 'foo', name: 'foo',
icon: 'star',
itemIcon: 'check',
}, },
} }
export const Filtering: Story = { export const Filtering: Story = {
args: { args: {
items: ['Foo', 'Bar', 'Baz'], items,
label: 'Select an item', label: 'Select an item',
name: 'foo', name: 'foo',
enableFiltering: true, enableFiltering: true,
}, },
} }
export const FilteringSelected: Story = {
args: {
items,
label: 'Select an item',
name: 'foo',
enableFiltering: true,
defaultSelectedKey: 'foo',
},
}

View File

@@ -12,7 +12,7 @@ import { Typography } from '../Typography'
import { SelectItem } from './SelectItem' import { SelectItem } from './SelectItem'
import { SelectFilter } from './SelectFilter' import { SelectFilter } from './SelectFilter'
import type { SelectProps } from './types' import type { SelectProps, SelectFilterProps } from './types'
import styles from './select.module.css' import styles from './select.module.css'
@@ -23,10 +23,9 @@ export function Select({
isDisabled, isDisabled,
icon, icon,
itemIcon, itemIcon,
enableFiltering,
...props ...props
}: SelectProps) { }: SelectProps | SelectFilterProps) {
if (enableFiltering) { if ('enableFiltering' in props) {
return ( return (
<SelectFilter <SelectFilter
name={name} name={name}
@@ -34,7 +33,6 @@ export function Select({
items={items} items={items}
icon={icon} icon={icon}
itemIcon={itemIcon} itemIcon={itemIcon}
isDisabled={isDisabled}
{...props} {...props}
/> />
) )
@@ -59,22 +57,26 @@ export function Select({
/> />
) : null} ) : null}
<SelectValue className={cx(styles.displayText, styles.selectValue)}> <SelectValue className={cx(styles.displayText, styles.selectValue)}>
{({ isPlaceholder, selectedText }) => ( {({ selectedText }) => {
<> return (
<Typography <>
variant={ <Typography
isPlaceholder ? 'Body/Paragraph/mdRegular' : 'Label/xsRegular' variant={
} selectedText
> ? 'Label/xsRegular'
<span className={styles.label}>{label}</span> : 'Body/Paragraph/mdRegular'
</Typography> }
{selectedText ? ( >
<Typography variant="Body/Paragraph/mdRegular"> <span className={styles.label}>{label}</span>
<span>{selectedText}</span>
</Typography> </Typography>
) : null} {selectedText ? (
</> <Typography variant="Body/Paragraph/mdRegular">
)} <span>{selectedText}</span>
</Typography>
) : null}
</>
)
}}
</SelectValue> </SelectValue>
<MaterialIcon <MaterialIcon
@@ -88,27 +90,16 @@ export function Select({
<Popover className={styles.popover} shouldFlip={false}> <Popover className={styles.popover} shouldFlip={false}>
<ListBox className={styles.listBox}> <ListBox className={styles.listBox}>
{items.map((item) => {items.map((item) => (
typeof item === 'object' ? ( <SelectItem
<SelectItem key={item.value}
key={item.label} id={item.value}
id={item.label} icon={item.icon || itemIcon}
icon={item.icon || itemIcon} isDisabled={item.isDisabled}
isDisabled={item.isDisabled} >
> {item.label}
{item.label} </SelectItem>
</SelectItem> ))}
) : (
<SelectItem
key={item}
id={item}
icon={itemIcon}
isDisabled={isDisabled}
>
{item.toString()}
</SelectItem>
)
)}
</ListBox> </ListBox>
</Popover> </Popover>
</AriaSelect> </AriaSelect>

View File

@@ -6,7 +6,6 @@ import {
ListBox, ListBox,
Popover, Popover,
} from 'react-aria-components' } from 'react-aria-components'
import { cx } from 'class-variance-authority'
import { useState } from 'react' import { useState } from 'react'
import { MaterialIcon } from '../Icons/MaterialIcon' import { MaterialIcon } from '../Icons/MaterialIcon'
@@ -21,13 +20,14 @@ export function SelectFilter({
name, name,
label, label,
items, items,
isRequired,
isDisabled, isDisabled,
icon, icon,
itemIcon, itemIcon,
defaultSelectedKey,
...props
}: SelectFilterProps) { }: SelectFilterProps) {
const [focus, setFocus] = useState(false) const [focus, setFocus] = useState(false)
const [value, setValue] = useState<Key | null>(null) const [value, setValue] = useState<Key | null>(defaultSelectedKey ?? null)
const iconColor = isDisabled ? 'Icon/Interactive/Disabled' : 'Icon/Default' const iconColor = isDisabled ? 'Icon/Interactive/Disabled' : 'Icon/Default'
return ( return (
@@ -35,11 +35,12 @@ export function SelectFilter({
className={styles.select} className={styles.select}
name={name} name={name}
aria-label={label} aria-label={label}
isRequired={isRequired}
isDisabled={isDisabled} isDisabled={isDisabled}
onSelectionChange={(val) => setValue(val)} onSelectionChange={setValue}
onFocus={() => setFocus(true)} onFocus={() => setFocus(true)}
onBlur={() => setFocus(false)} onBlur={() => setFocus(false)}
defaultSelectedKey={defaultSelectedKey}
{...props}
> >
<label className={styles.inner}> <label className={styles.inner}>
{icon ? ( {icon ? (
@@ -60,7 +61,7 @@ export function SelectFilter({
<span className={styles.label}>{label}</span> <span className={styles.label}>{label}</span>
</Typography> </Typography>
<Typography variant="Body/Paragraph/mdRegular"> <Typography variant="Body/Paragraph/mdRegular">
<Input className={cx(styles.input, { [styles.hasValue]: value })} /> <Input className={styles.input} />
</Typography> </Typography>
</span> </span>
<Button className={styles.button}> <Button className={styles.button}>
@@ -81,27 +82,16 @@ export function SelectFilter({
offset={22} offset={22}
> >
<ListBox className={styles.listBox}> <ListBox className={styles.listBox}>
{items.map((item) => {items.map((item) => (
typeof item === 'object' ? ( <SelectItem
<SelectItem key={item.value}
key={item.label} id={item.value}
id={item.label} icon={item.icon || itemIcon}
icon={item.icon || itemIcon} isDisabled={item.isDisabled}
isDisabled={item.isDisabled} >
> {item.label}
{item.label} </SelectItem>
</SelectItem> ))}
) : (
<SelectItem
key={item}
id={item}
icon={itemIcon}
isDisabled={isDisabled}
>
{item.toString()}
</SelectItem>
)
)}
</ListBox> </ListBox>
</Popover> </Popover>
</ComboBox> </ComboBox>

View File

@@ -70,7 +70,7 @@
position: absolute; position: absolute;
padding: 0; padding: 0;
&.hasValue { &[value]:not([value='']) {
position: unset; position: unset;
} }
} }

View File

@@ -1,5 +1,5 @@
import { ComponentProps } from 'react' import { ComponentProps } from 'react'
import { Key, ListBoxItem, Select } from 'react-aria-components' import { ComboBox, Key, ListBoxItem, Select } from 'react-aria-components'
import { MaterialIconProps } from '../Icons/MaterialIcon' import { MaterialIconProps } from '../Icons/MaterialIcon'
interface Item extends Record<string, unknown> { interface Item extends Record<string, unknown> {
@@ -12,10 +12,9 @@ interface Item extends Record<string, unknown> {
export interface SelectProps extends ComponentProps<typeof Select> { export interface SelectProps extends ComponentProps<typeof Select> {
icon?: MaterialIconProps['icon'] icon?: MaterialIconProps['icon']
itemIcon?: MaterialIconProps['icon'] itemIcon?: MaterialIconProps['icon']
items: (Key | Item)[] items: Item[]
name: string name: string
label: string label: string
enableFiltering?: boolean
} }
export interface SelectItemProps extends ComponentProps<typeof ListBoxItem> { export interface SelectItemProps extends ComponentProps<typeof ListBoxItem> {
@@ -23,7 +22,11 @@ export interface SelectItemProps extends ComponentProps<typeof ListBoxItem> {
children: string children: string
} }
// Disabling rule because we're just omitting one prop while keeping interface export interface SelectFilterProps extends ComponentProps<typeof ComboBox> {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type icon?: MaterialIconProps['icon']
export interface SelectFilterProps itemIcon?: MaterialIconProps['icon']
extends Omit<SelectProps, 'enableFiltering'> {} items: Item[]
name: string
label: string
enableFiltering: boolean
}