Migrate Select component from useStyles to useDefinition
Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
+30
-17
@@ -1962,36 +1962,49 @@ export const Select: ForwardRefExoticComponent<
|
||||
|
||||
// @public
|
||||
export const SelectDefinition: {
|
||||
readonly styles: {
|
||||
readonly [key: string]: string;
|
||||
};
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-Select';
|
||||
readonly popover: 'bui-SelectPopover';
|
||||
readonly trigger: 'bui-SelectTrigger';
|
||||
readonly chevron: 'bui-SelectTriggerChevron';
|
||||
readonly value: 'bui-SelectValue';
|
||||
readonly list: 'bui-SelectList';
|
||||
readonly item: 'bui-SelectItem';
|
||||
readonly itemIndicator: 'bui-SelectItemIndicator';
|
||||
readonly itemLabel: 'bui-SelectItemLabel';
|
||||
readonly searchWrapper: 'bui-SelectSearchWrapper';
|
||||
readonly search: 'bui-SelectSearch';
|
||||
readonly searchClear: 'bui-SelectSearchClear';
|
||||
readonly noResults: 'bui-SelectNoResults';
|
||||
};
|
||||
readonly dataAttributes: {
|
||||
readonly size: readonly ['small', 'medium'];
|
||||
readonly propDefs: {
|
||||
readonly icon: {};
|
||||
readonly size: {
|
||||
readonly dataAttribute: true;
|
||||
readonly default: 'small';
|
||||
};
|
||||
readonly options: {};
|
||||
readonly searchable: {};
|
||||
readonly searchPlaceholder: {};
|
||||
readonly label: {};
|
||||
readonly secondaryLabel: {};
|
||||
readonly description: {};
|
||||
readonly isRequired: {};
|
||||
readonly className: {};
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SelectProps<T extends 'single' | 'multiple'>
|
||||
extends SelectProps_2<Option_2, T>,
|
||||
Omit<FieldLabelProps, 'htmlFor' | 'id' | 'className'> {
|
||||
export type SelectOwnProps = {
|
||||
icon?: ReactNode;
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
options?: Array<Option_2>;
|
||||
searchable?: boolean;
|
||||
searchPlaceholder?: string;
|
||||
label?: FieldLabelProps['label'];
|
||||
secondaryLabel?: FieldLabelProps['secondaryLabel'];
|
||||
description?: FieldLabelProps['description'];
|
||||
isRequired?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SelectProps<T extends 'single' | 'multiple'>
|
||||
extends SelectOwnProps,
|
||||
Omit<SelectProps_2<Option_2, T>, keyof SelectOwnProps> {
|
||||
selectionMode?: T;
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -18,13 +18,11 @@ import { forwardRef, useEffect } from 'react';
|
||||
import { Select as AriaSelect, Popover } from 'react-aria-components';
|
||||
import clsx from 'clsx';
|
||||
import { SelectProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { useDefinition } from '../../hooks/useDefinition';
|
||||
import { SelectDefinition } from './definition';
|
||||
import { PopoverDefinition } from '../Popover/definition';
|
||||
import { FieldLabel } from '../FieldLabel';
|
||||
import { FieldError } from '../FieldError';
|
||||
import styles from './Select.module.css';
|
||||
import stylesPopover from '../Popover/Popover.module.css';
|
||||
import { SelectTrigger } from './SelectTrigger';
|
||||
import { SelectContent } from './SelectContent';
|
||||
|
||||
@@ -33,30 +31,28 @@ export const Select = forwardRef<
|
||||
HTMLDivElement,
|
||||
SelectProps<'single' | 'multiple'>
|
||||
>((props, ref) => {
|
||||
const { classNames: popoverClassNames } = useStyles(PopoverDefinition);
|
||||
const { classNames, dataAttributes, cleanedProps } = useStyles(
|
||||
const { ownProps, restProps, dataAttributes } = useDefinition(
|
||||
SelectDefinition,
|
||||
{
|
||||
size: 'small',
|
||||
placeholder: 'Select an option',
|
||||
...props,
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
className,
|
||||
classes,
|
||||
label,
|
||||
description,
|
||||
options,
|
||||
icon,
|
||||
searchable,
|
||||
searchPlaceholder,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-labelledby': ariaLabelledBy,
|
||||
isRequired,
|
||||
secondaryLabel,
|
||||
...rest
|
||||
} = cleanedProps;
|
||||
} = ownProps;
|
||||
|
||||
const ariaLabel = restProps['aria-label'];
|
||||
const ariaLabelledBy = restProps['aria-labelledby'];
|
||||
|
||||
useEffect(() => {
|
||||
if (!label && !ariaLabel && !ariaLabelledBy) {
|
||||
@@ -70,12 +66,10 @@ export const Select = forwardRef<
|
||||
|
||||
return (
|
||||
<AriaSelect
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
className={classes.root}
|
||||
{...dataAttributes}
|
||||
ref={ref}
|
||||
aria-label={ariaLabel}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
{...rest}
|
||||
{...restProps}
|
||||
>
|
||||
<FieldLabel
|
||||
label={label}
|
||||
@@ -86,10 +80,9 @@ export const Select = forwardRef<
|
||||
<FieldError />
|
||||
<Popover
|
||||
className={clsx(
|
||||
popoverClassNames.root,
|
||||
stylesPopover[popoverClassNames.root],
|
||||
classNames.popover,
|
||||
styles[classNames.popover],
|
||||
PopoverDefinition.classNames.root,
|
||||
PopoverDefinition.styles[PopoverDefinition.classNames.root],
|
||||
classes.popover,
|
||||
)}
|
||||
{...dataAttributes}
|
||||
>
|
||||
|
||||
@@ -22,11 +22,9 @@ import {
|
||||
} from 'react-aria-components';
|
||||
import { useFilter } from 'react-aria';
|
||||
import { RiCloseCircleLine } from '@remixicon/react';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { SelectDefinition } from './definition';
|
||||
import { useDefinition } from '../../hooks/useDefinition';
|
||||
import { SelectContentDefinition } from './definition';
|
||||
import { SelectListBox } from './SelectListBox';
|
||||
import styles from './Select.module.css';
|
||||
import type { Option } from './types';
|
||||
|
||||
interface SelectContentProps {
|
||||
@@ -35,13 +33,10 @@ interface SelectContentProps {
|
||||
options?: Array<Option>;
|
||||
}
|
||||
|
||||
export function SelectContent({
|
||||
searchable,
|
||||
searchPlaceholder = 'Search...',
|
||||
options,
|
||||
}: SelectContentProps) {
|
||||
export function SelectContent(props: SelectContentProps) {
|
||||
const { contains } = useFilter({ sensitivity: 'base' });
|
||||
const { classNames } = useStyles(SelectDefinition);
|
||||
const { ownProps } = useDefinition(SelectContentDefinition, props);
|
||||
const { classes, searchable, searchPlaceholder, options } = ownProps;
|
||||
|
||||
if (!searchable) {
|
||||
return <SelectListBox options={options} />;
|
||||
@@ -51,22 +46,11 @@ export function SelectContent({
|
||||
<Autocomplete filter={contains}>
|
||||
<SearchField
|
||||
autoFocus
|
||||
className={clsx(
|
||||
classNames.searchWrapper,
|
||||
styles[classNames.searchWrapper],
|
||||
)}
|
||||
className={classes.root}
|
||||
aria-label={searchPlaceholder}
|
||||
>
|
||||
<Input
|
||||
placeholder={searchPlaceholder}
|
||||
className={clsx(classNames.search, styles[classNames.search])}
|
||||
/>
|
||||
<Button
|
||||
className={clsx(
|
||||
classNames.searchClear,
|
||||
styles[classNames.searchClear],
|
||||
)}
|
||||
>
|
||||
<Input placeholder={searchPlaceholder} className={classes.search} />
|
||||
<Button className={classes.searchClear}>
|
||||
<RiCloseCircleLine />
|
||||
</Button>
|
||||
</SearchField>
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
|
||||
import { ListBox, ListBoxItem, Text } from 'react-aria-components';
|
||||
import { RiCheckLine } from '@remixicon/react';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { SelectDefinition } from './definition';
|
||||
import styles from './Select.module.css';
|
||||
import { useDefinition } from '../../hooks/useDefinition';
|
||||
import { SelectListBoxDefinition } from './definition';
|
||||
import type { Option } from './types';
|
||||
|
||||
interface SelectListBoxProps {
|
||||
@@ -27,42 +25,30 @@ interface SelectListBoxProps {
|
||||
}
|
||||
|
||||
const NoResults = () => {
|
||||
const { classNames } = useStyles(SelectDefinition);
|
||||
const { ownProps } = useDefinition(SelectListBoxDefinition, {});
|
||||
const { classes } = ownProps;
|
||||
|
||||
return (
|
||||
<div className={clsx(classNames.noResults, styles[classNames.noResults])}>
|
||||
No results found.
|
||||
</div>
|
||||
);
|
||||
return <div className={classes.noResults}>No results found.</div>;
|
||||
};
|
||||
|
||||
export function SelectListBox({ options, ...props }: SelectListBoxProps) {
|
||||
const { classNames } = useStyles(SelectDefinition, props);
|
||||
export function SelectListBox(props: SelectListBoxProps) {
|
||||
const { ownProps } = useDefinition(SelectListBoxDefinition, props);
|
||||
const { classes, options } = ownProps;
|
||||
|
||||
return (
|
||||
<ListBox
|
||||
className={clsx(classNames.list, styles[classNames.list])}
|
||||
renderEmptyState={() => <NoResults />}
|
||||
>
|
||||
<ListBox className={classes.root} renderEmptyState={() => <NoResults />}>
|
||||
{options?.map(option => (
|
||||
<ListBoxItem
|
||||
key={option.value}
|
||||
id={option.value}
|
||||
textValue={option.label}
|
||||
className={clsx(classNames.item, styles[classNames.item])}
|
||||
className={classes.item}
|
||||
isDisabled={option.disabled}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.itemIndicator,
|
||||
styles[classNames.itemIndicator],
|
||||
)}
|
||||
>
|
||||
<div className={classes.itemIndicator}>
|
||||
<RiCheckLine />
|
||||
</div>
|
||||
<Text
|
||||
slot="label"
|
||||
className={clsx(classNames.itemLabel, styles[classNames.itemLabel])}
|
||||
>
|
||||
<Text slot="label" className={classes.itemLabel}>
|
||||
{option.label}
|
||||
</Text>
|
||||
</ListBoxItem>
|
||||
|
||||
@@ -17,25 +17,22 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Button, SelectValue } from 'react-aria-components';
|
||||
import { RiArrowDownSLine } from '@remixicon/react';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { SelectDefinition } from './definition';
|
||||
import styles from './Select.module.css';
|
||||
import { useDefinition } from '../../hooks/useDefinition';
|
||||
import { SelectTriggerDefinition } from './definition';
|
||||
|
||||
interface SelectTriggerProps {
|
||||
icon?: ReactNode;
|
||||
}
|
||||
|
||||
export function SelectTrigger({ icon }: SelectTriggerProps) {
|
||||
const { classNames } = useStyles(SelectDefinition);
|
||||
export function SelectTrigger(props: SelectTriggerProps) {
|
||||
const { ownProps } = useDefinition(SelectTriggerDefinition, props);
|
||||
const { classes, icon } = ownProps;
|
||||
|
||||
return (
|
||||
<Button className={clsx(classNames.trigger, styles[classNames.trigger])}>
|
||||
<Button className={classes.root}>
|
||||
{icon}
|
||||
<SelectValue
|
||||
className={clsx(classNames.value, styles[classNames.value])}
|
||||
/>
|
||||
<div className={clsx(classNames.chevron, styles[classNames.chevron])}>
|
||||
<SelectValue className={classes.value} />
|
||||
<div className={classes.chevron}>
|
||||
<RiArrowDownSLine aria-hidden="true" />
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
@@ -14,29 +14,105 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ComponentDefinition } from '../../types';
|
||||
import { defineComponent } from '../../hooks/useDefinition';
|
||||
import type { SelectOwnProps } from './types';
|
||||
import styles from './Select.module.css';
|
||||
|
||||
/**
|
||||
* Component definition for Select
|
||||
* @public
|
||||
*/
|
||||
export const SelectDefinition = {
|
||||
export const SelectDefinition = defineComponent<SelectOwnProps>()({
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-Select',
|
||||
popover: 'bui-SelectPopover',
|
||||
trigger: 'bui-SelectTrigger',
|
||||
chevron: 'bui-SelectTriggerChevron',
|
||||
value: 'bui-SelectValue',
|
||||
list: 'bui-SelectList',
|
||||
item: 'bui-SelectItem',
|
||||
itemIndicator: 'bui-SelectItemIndicator',
|
||||
itemLabel: 'bui-SelectItemLabel',
|
||||
searchWrapper: 'bui-SelectSearchWrapper',
|
||||
search: 'bui-SelectSearch',
|
||||
searchClear: 'bui-SelectSearchClear',
|
||||
noResults: 'bui-SelectNoResults',
|
||||
},
|
||||
dataAttributes: {
|
||||
size: ['small', 'medium'] as const,
|
||||
propDefs: {
|
||||
icon: {},
|
||||
size: { dataAttribute: true, default: 'small' },
|
||||
options: {},
|
||||
searchable: {},
|
||||
searchPlaceholder: {},
|
||||
label: {},
|
||||
secondaryLabel: {},
|
||||
description: {},
|
||||
isRequired: {},
|
||||
className: {},
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
});
|
||||
|
||||
/** @internal */
|
||||
interface SelectTriggerOwnProps {
|
||||
icon?: SelectOwnProps['icon'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Component definition for SelectTrigger
|
||||
* @internal
|
||||
*/
|
||||
export const SelectTriggerDefinition = defineComponent<SelectTriggerOwnProps>()(
|
||||
{
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-SelectTrigger',
|
||||
chevron: 'bui-SelectTriggerChevron',
|
||||
value: 'bui-SelectValue',
|
||||
},
|
||||
propDefs: {
|
||||
icon: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
/** @internal */
|
||||
interface SelectContentOwnProps {
|
||||
searchable?: boolean;
|
||||
searchPlaceholder?: string;
|
||||
options?: SelectOwnProps['options'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Component definition for SelectContent
|
||||
* @internal
|
||||
*/
|
||||
export const SelectContentDefinition = defineComponent<SelectContentOwnProps>()(
|
||||
{
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-SelectSearchWrapper',
|
||||
search: 'bui-SelectSearch',
|
||||
searchClear: 'bui-SelectSearchClear',
|
||||
},
|
||||
propDefs: {
|
||||
searchable: {},
|
||||
searchPlaceholder: { default: 'Search...' },
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
/** @internal */
|
||||
interface SelectListBoxOwnProps {
|
||||
options?: SelectOwnProps['options'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Component definition for SelectListBox
|
||||
* @internal
|
||||
*/
|
||||
export const SelectListBoxDefinition = defineComponent<SelectListBoxOwnProps>()(
|
||||
{
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-SelectList',
|
||||
item: 'bui-SelectItem',
|
||||
itemIndicator: 'bui-SelectItemIndicator',
|
||||
itemLabel: 'bui-SelectItemLabel',
|
||||
noResults: 'bui-SelectNoResults',
|
||||
},
|
||||
propDefs: {
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -23,9 +23,7 @@ import type { FieldLabelProps } from '../FieldLabel/types';
|
||||
export type Option = { value: string; label: string; disabled?: boolean };
|
||||
|
||||
/** @public */
|
||||
export interface SelectProps<T extends 'single' | 'multiple'>
|
||||
extends AriaSelectProps<Option, T>,
|
||||
Omit<FieldLabelProps, 'htmlFor' | 'id' | 'className'> {
|
||||
export type SelectOwnProps = {
|
||||
/**
|
||||
* An icon to render before the input
|
||||
*/
|
||||
@@ -55,6 +53,17 @@ export interface SelectProps<T extends 'single' | 'multiple'>
|
||||
*/
|
||||
searchPlaceholder?: string;
|
||||
|
||||
label?: FieldLabelProps['label'];
|
||||
secondaryLabel?: FieldLabelProps['secondaryLabel'];
|
||||
description?: FieldLabelProps['description'];
|
||||
isRequired?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export interface SelectProps<T extends 'single' | 'multiple'>
|
||||
extends SelectOwnProps,
|
||||
Omit<AriaSelectProps<Option, T>, keyof SelectOwnProps> {
|
||||
/**
|
||||
* Selection mode, single or multiple
|
||||
* @defaultvalue 'single'
|
||||
|
||||
Reference in New Issue
Block a user