diff --git a/apps/www/src/components/playground/chip-examples.tsx b/apps/www/src/components/playground/chip-examples.tsx index 6c29e5ef5..8446356e7 100644 --- a/apps/www/src/components/playground/chip-examples.tsx +++ b/apps/www/src/components/playground/chip-examples.tsx @@ -35,7 +35,7 @@ export function ChipExamples() { alert('dismissed')} - ariaLabel='Dismissible chip' + aria-label='Dismissible chip' > Dismissable Chip @@ -44,7 +44,7 @@ export function ChipExamples() { color='accent' isDismissible onDismiss={() => alert('dismissed')} - ariaLabel='Dismissible chip' + aria-label='Dismissible chip' > Dismissable Chip @@ -53,7 +53,7 @@ export function ChipExamples() { color='accent' isDismissible onDismiss={() => alert('dismissed')} - ariaLabel='Dismissible chip' + aria-label='Dismissible chip' > Dismissable Chip diff --git a/apps/www/src/content/docs/components/chip/demo.ts b/apps/www/src/content/docs/components/chip/demo.ts index 6591c9706..e15705ceb 100644 --- a/apps/www/src/content/docs/components/chip/demo.ts +++ b/apps/www/src/content/docs/components/chip/demo.ts @@ -73,9 +73,9 @@ export const dismissableDemo = { type: 'code', code: ` - alert('dismissed')} ariaLabel="Dismissible chip">Dismissable Chip - alert('dismissed')} ariaLabel="Dismissible chip">Dismissable Chip - alert('dismissed')} ariaLabel="Dismissible chip">Dismissable Chip + alert('dismissed')} aria-label="Dismissible chip">Dismissable Chip + alert('dismissed')} aria-label="Dismissible chip">Dismissable Chip + alert('dismissed')} aria-label="Dismissible chip">Dismissable Chip ` }; diff --git a/apps/www/src/content/docs/components/chip/props.ts b/apps/www/src/content/docs/components/chip/props.ts index 99396d0d7..9f270cd68 100644 --- a/apps/www/src/content/docs/components/chip/props.ts +++ b/apps/www/src/content/docs/components/chip/props.ts @@ -44,5 +44,5 @@ export interface ChipProps { role?: string; /** Custom accessibility label for the chip */ - ariaLabel?: string; + 'aria-label'?: string; } diff --git a/docs/V1-migration.md b/docs/V1-migration.md index 40996ba86..903b1f31c 100644 --- a/docs/V1-migration.md +++ b/docs/V1-migration.md @@ -21,6 +21,7 @@ This guide covers all breaking changes when upgrading from the last stable Radix - [Avatar](#avatar) - [Breadcrumb](#breadcrumb) - [Button](#button) + - [Chip](#chip) - [Checkbox](#checkbox) - [New: `Checkbox.Group`](#new-checkboxgroup) - [New Features](#new-features) @@ -418,6 +419,31 @@ Unchanged: `size`, `radius`, `variant`, `color`, `fallback`, `src`, `alt`, `clas --- +### Chip + +**`ariaLabel` prop removed** -- use the standard `aria-label` HTML attribute: + +```tsx +// Before + + Tag + + +// After + + Tag + +``` + +`Chip` now forwards all standard HTML attributes through `...props`, making the dedicated `ariaLabel` prop redundant. The auto-fallback to string children when no label is supplied is unchanged: + +```tsx +// Still works — uses "Tag" as the accessibility label +Tag +``` + +--- + ### Checkbox 1. **Indeterminate API changed** -- `checked="indeterminate"` replaced by separate `indeterminate` boolean: diff --git a/packages/raystack/components/chip/__tests__/chip.test.tsx b/packages/raystack/components/chip/__tests__/chip.test.tsx index a15348866..ad5b58385 100644 --- a/packages/raystack/components/chip/__tests__/chip.test.tsx +++ b/packages/raystack/components/chip/__tests__/chip.test.tsx @@ -214,18 +214,57 @@ describe('Chip', () => { expect(chip).toHaveAttribute('aria-label', 'Test Label'); }); - it('uses custom ariaLabel when provided', () => { - render(Test Chip); + it('uses custom aria-label when provided', () => { + render(Test Chip); const chip = screen.getByRole('status'); expect(chip).toHaveAttribute('aria-label', 'Custom Label'); }); - it('custom ariaLabel overrides string children', () => { - render(String Child); + it('custom aria-label overrides string children', () => { + render(String Child); const chip = screen.getByRole('status'); expect(chip).toHaveAttribute('aria-label', 'Override Label'); }); + + it('accepts native aria-label attribute', () => { + render(String Child); + + const chip = screen.getByRole('status'); + expect(chip).toHaveAttribute('aria-label', 'Native Label'); + }); + }); + + describe('Forwarded HTML attributes', () => { + it('forwards arbitrary HTML attributes onto the root span', () => { + render( + + Test Chip + + ); + + const chip = screen.getByTestId('chip-root'); + expect(chip).toHaveAttribute('id', 'my-chip'); + expect(chip).toHaveAttribute('data-state', 'active'); + expect(chip).toHaveAttribute('title', 'Tooltip'); + }); + + it('forwards mouse events from the spread props', () => { + const onMouseEnter = vi.fn(); + render( + + Test Chip + + ); + + fireEvent.mouseEnter(screen.getByTestId('chip-root')); + expect(onMouseEnter).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/packages/raystack/components/chip/chip.tsx b/packages/raystack/components/chip/chip.tsx index 424298a4f..ccd3518ad 100644 --- a/packages/raystack/components/chip/chip.tsx +++ b/packages/raystack/components/chip/chip.tsx @@ -1,7 +1,7 @@ 'use client'; import { cva, type VariantProps } from 'class-variance-authority'; -import { ReactNode } from 'react'; +import { ComponentProps, ReactNode } from 'react'; import styles from './chip.module.css'; @@ -27,19 +27,15 @@ const chip = cva(styles.chip, { } }); -type ChipProps = VariantProps & { - trailingIcon?: ReactNode; - leadingIcon?: ReactNode; - isDismissible?: boolean; - children: ReactNode; - className?: string; - onDismiss?: () => void; - onClick?: () => void; - role?: string; - ariaLabel?: string; - disabled?: boolean; - 'data-state'?: string; -}; +type ChipProps = ComponentProps<'span'> & + VariantProps & { + trailingIcon?: ReactNode; + leadingIcon?: ReactNode; + isDismissible?: boolean; + children: ReactNode; + onDismiss?: () => void; + disabled?: boolean; + }; export const Chip = ({ variant, @@ -53,9 +49,9 @@ export const Chip = ({ onDismiss, onClick, role = 'status', - ariaLabel, disabled, - 'data-state': dataState + 'aria-label': ariaLabel, + ...props }: ChipProps) => { const handleDismiss = (e: React.MouseEvent) => { e.stopPropagation(); @@ -64,14 +60,14 @@ export const Chip = ({ return ( {leadingIcon && ( { expect(input).toHaveValue('initial value'); }); }); + + describe('Forwarded HTML attributes', () => { + it('forwards arbitrary HTML attributes onto the root div', () => { + render( + + ); + + const root = screen.getByTestId('filter-root'); + expect(root).toHaveAttribute('id', 'my-filter'); + expect(root).toHaveAttribute('title', 'Tooltip'); + }); + }); }); diff --git a/packages/raystack/components/filter-chip/filter-chip.tsx b/packages/raystack/components/filter-chip/filter-chip.tsx index 1ab910194..03942b760 100644 --- a/packages/raystack/components/filter-chip/filter-chip.tsx +++ b/packages/raystack/components/filter-chip/filter-chip.tsx @@ -2,7 +2,7 @@ import { Cross1Icon } from '@radix-ui/react-icons'; import { cva, cx, VariantProps } from 'class-variance-authority'; -import { ReactElement, ReactNode, useCallback, useState } from 'react'; +import { ComponentProps, ReactElement, useCallback, useState } from 'react'; import { FilterOperation, FilterOperator, @@ -32,13 +32,12 @@ const chip = cva(styles.chip, { } }); -export interface FilterChipProps extends VariantProps { +export interface FilterChipProps + extends ComponentProps<'div'>, + VariantProps { label: string; value?: string; onRemove?: () => void; - className?: string; - ref?: React.RefObject; - children?: ReactNode; columnType?: FilterTypes; options?: FilterSelectOption[]; onValueChange?: (value: any, operation: string) => void;