Migrate SearchField component from useStyles to useDefinition
Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
@@ -1793,6 +1793,9 @@ export const SearchField: ForwardRefExoticComponent<
|
||||
|
||||
// @public
|
||||
export const SearchFieldDefinition: {
|
||||
readonly styles: {
|
||||
readonly [key: string]: string;
|
||||
};
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-SearchField';
|
||||
readonly clear: 'bui-SearchFieldClear';
|
||||
@@ -1800,21 +1803,44 @@ export const SearchFieldDefinition: {
|
||||
readonly input: 'bui-SearchFieldInput';
|
||||
readonly inputIcon: 'bui-SearchFieldInputIcon';
|
||||
};
|
||||
readonly dataAttributes: {
|
||||
readonly startCollapsed: readonly [true, false];
|
||||
readonly size: readonly ['small', 'medium'];
|
||||
readonly propDefs: {
|
||||
readonly startCollapsed: {
|
||||
readonly dataAttribute: true;
|
||||
readonly default: false;
|
||||
};
|
||||
readonly size: {
|
||||
readonly dataAttribute: true;
|
||||
readonly default: 'small';
|
||||
};
|
||||
readonly className: {};
|
||||
readonly icon: {};
|
||||
readonly placeholder: {
|
||||
readonly default: 'Search';
|
||||
};
|
||||
readonly label: {};
|
||||
readonly description: {};
|
||||
readonly secondaryLabel: {};
|
||||
readonly isRequired: {};
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SearchFieldProps
|
||||
extends SearchFieldProps_2,
|
||||
Omit<FieldLabelProps, 'htmlFor' | 'id' | 'className'> {
|
||||
export type SearchFieldOwnProps = {
|
||||
icon?: ReactNode | false;
|
||||
placeholder?: string;
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
placeholder?: string;
|
||||
startCollapsed?: boolean;
|
||||
}
|
||||
className?: string;
|
||||
label?: FieldLabelProps['label'];
|
||||
description?: FieldLabelProps['description'];
|
||||
secondaryLabel?: FieldLabelProps['secondaryLabel'];
|
||||
isRequired?: boolean;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SearchFieldProps
|
||||
extends Omit<SearchFieldProps_2, 'className' | 'isRequired' | 'description'>,
|
||||
SearchFieldOwnProps {}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SearchState {
|
||||
|
||||
@@ -20,53 +20,39 @@ import {
|
||||
SearchField as AriaSearchField,
|
||||
Button,
|
||||
} from 'react-aria-components';
|
||||
import clsx from 'clsx';
|
||||
import { FieldLabel } from '../FieldLabel';
|
||||
import { FieldError } from '../FieldError';
|
||||
import { RiSearch2Line, RiCloseCircleLine } from '@remixicon/react';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { useDefinition } from '../../hooks/useDefinition';
|
||||
import { SearchFieldDefinition } from './definition';
|
||||
import styles from './SearchField.module.css';
|
||||
|
||||
import type { SearchFieldProps } from './types';
|
||||
|
||||
/** @public */
|
||||
export const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
|
||||
(props, ref) => {
|
||||
const {
|
||||
label,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-labelledby': ariaLabelledBy,
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (!label && !ariaLabel && !ariaLabelledBy) {
|
||||
console.warn(
|
||||
'SearchField requires either a visible label, aria-label, or aria-labelledby for accessibility',
|
||||
);
|
||||
}
|
||||
}, [label, ariaLabel, ariaLabelledBy]);
|
||||
|
||||
const { classNames, dataAttributes, style, cleanedProps } = useStyles(
|
||||
const { ownProps, restProps, dataAttributes } = useDefinition(
|
||||
SearchFieldDefinition,
|
||||
{
|
||||
size: 'small',
|
||||
placeholder: 'Search',
|
||||
startCollapsed: false,
|
||||
...props,
|
||||
},
|
||||
props,
|
||||
);
|
||||
|
||||
const {
|
||||
className,
|
||||
description,
|
||||
classes,
|
||||
label,
|
||||
icon,
|
||||
isRequired,
|
||||
secondaryLabel,
|
||||
placeholder,
|
||||
startCollapsed,
|
||||
...rest
|
||||
} = cleanedProps;
|
||||
description,
|
||||
} = ownProps;
|
||||
|
||||
useEffect(() => {
|
||||
if (!label && !restProps['aria-label'] && !restProps['aria-labelledby']) {
|
||||
console.warn(
|
||||
'SearchField requires either a visible label, aria-label, or aria-labelledby for accessibility',
|
||||
);
|
||||
}
|
||||
}, [label, restProps['aria-label'], restProps['aria-labelledby']]);
|
||||
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -76,7 +62,7 @@ export const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
|
||||
secondaryLabel || (isRequired ? 'Required' : null);
|
||||
|
||||
const handleFocusChange = (isFocused: boolean) => {
|
||||
props.onFocusChange?.(isFocused);
|
||||
restProps.onFocusChange?.(isFocused);
|
||||
setIsFocused(isFocused);
|
||||
};
|
||||
|
||||
@@ -89,17 +75,17 @@ export const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
|
||||
|
||||
const isCollapsed = hasInputRef
|
||||
? startCollapsed && !hasValue && !isFocused
|
||||
: startCollapsed && !rest.value && !rest.defaultValue && !isFocused;
|
||||
: startCollapsed &&
|
||||
!restProps.value &&
|
||||
!restProps.defaultValue &&
|
||||
!isFocused;
|
||||
|
||||
return (
|
||||
<AriaSearchField
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
className={classes.root}
|
||||
{...dataAttributes}
|
||||
aria-label={ariaLabel}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
data-collapsed={isCollapsed}
|
||||
style={style}
|
||||
{...rest}
|
||||
{...restProps}
|
||||
onFocusChange={handleFocusChange}
|
||||
ref={ref}
|
||||
>
|
||||
@@ -109,19 +95,13 @@ export const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
|
||||
description={description}
|
||||
/>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.inputWrapper,
|
||||
styles[classNames.inputWrapper],
|
||||
)}
|
||||
className={classes.inputWrapper}
|
||||
data-size={dataAttributes['data-size']}
|
||||
onClick={handleContainerClick}
|
||||
>
|
||||
{icon !== false && (
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.inputIcon,
|
||||
styles[classNames.inputIcon],
|
||||
)}
|
||||
className={classes.inputIcon}
|
||||
data-size={dataAttributes['data-size']}
|
||||
aria-hidden="true"
|
||||
>
|
||||
@@ -130,12 +110,12 @@ export const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
|
||||
)}
|
||||
<Input
|
||||
ref={inputRef}
|
||||
className={clsx(classNames.input, styles[classNames.input])}
|
||||
className={classes.input}
|
||||
{...(icon !== false && { 'data-icon': true })}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
<Button
|
||||
className={clsx(classNames.clear, styles[classNames.clear])}
|
||||
className={classes.clear}
|
||||
data-size={dataAttributes['data-size']}
|
||||
>
|
||||
<RiCloseCircleLine />
|
||||
|
||||
@@ -14,13 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ComponentDefinition } from '../../types';
|
||||
import { defineComponent } from '../../hooks/useDefinition';
|
||||
import type { SearchFieldOwnProps } from './types';
|
||||
import styles from './SearchField.module.css';
|
||||
|
||||
/**
|
||||
* Component definition for SearchField
|
||||
* @public
|
||||
*/
|
||||
export const SearchFieldDefinition = {
|
||||
export const SearchFieldDefinition = defineComponent<SearchFieldOwnProps>()({
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-SearchField',
|
||||
clear: 'bui-SearchFieldClear',
|
||||
@@ -28,8 +31,15 @@ export const SearchFieldDefinition = {
|
||||
input: 'bui-SearchFieldInput',
|
||||
inputIcon: 'bui-SearchFieldInputIcon',
|
||||
},
|
||||
dataAttributes: {
|
||||
startCollapsed: [true, false] as const,
|
||||
size: ['small', 'medium'] as const,
|
||||
propDefs: {
|
||||
startCollapsed: { dataAttribute: true, default: false },
|
||||
size: { dataAttribute: true, default: 'small' },
|
||||
className: {},
|
||||
icon: {},
|
||||
placeholder: { default: 'Search' },
|
||||
label: {},
|
||||
description: {},
|
||||
secondaryLabel: {},
|
||||
isRequired: {},
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
});
|
||||
|
||||
@@ -15,14 +15,12 @@
|
||||
*/
|
||||
|
||||
import type { SearchFieldProps as AriaSearchFieldProps } from 'react-aria-components';
|
||||
import { ReactNode } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { Breakpoint } from '../../types';
|
||||
import type { FieldLabelProps } from '../FieldLabel/types';
|
||||
|
||||
/** @public */
|
||||
export interface SearchFieldProps
|
||||
extends AriaSearchFieldProps,
|
||||
Omit<FieldLabelProps, 'htmlFor' | 'id' | 'className'> {
|
||||
export type SearchFieldOwnProps = {
|
||||
/**
|
||||
* An icon to render before the input
|
||||
*/
|
||||
@@ -43,4 +41,19 @@ export interface SearchFieldProps
|
||||
* Controls whether the SearchField starts in a collapsed state.
|
||||
*/
|
||||
startCollapsed?: boolean;
|
||||
}
|
||||
|
||||
className?: string;
|
||||
|
||||
label?: FieldLabelProps['label'];
|
||||
description?: FieldLabelProps['description'];
|
||||
secondaryLabel?: FieldLabelProps['secondaryLabel'];
|
||||
isRequired?: boolean;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export interface SearchFieldProps
|
||||
extends Omit<
|
||||
AriaSearchFieldProps,
|
||||
'className' | 'isRequired' | 'description'
|
||||
>,
|
||||
SearchFieldOwnProps {}
|
||||
|
||||
Reference in New Issue
Block a user