diff --git a/.changeset/cruel-plums-talk.md b/.changeset/cruel-plums-talk.md new file mode 100644 index 0000000000..bc7a30b048 --- /dev/null +++ b/.changeset/cruel-plums-talk.md @@ -0,0 +1,58 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Migrated Checkbox component from Base UI to React Aria Components. + +API changes required: + +- `checked` → `isSelected` +- `defaultChecked` → `defaultSelected` +- `disabled` → `isDisabled` +- `required` → `isRequired` +- `label` prop removed - use `children` instead +- CSS: `bui-CheckboxLabel` class removed +- Data attribute: `data-checked` → `data-selected` +- Use without label is no longer supported + +Migration examples: + +Before: + +```tsx + +``` + +After: + +```tsx + + Accept terms + +``` + +Before: + +```tsx + +``` + +After: + +```tsx +Option +``` + +Before: + +```tsx + +``` + +After: + +```tsx + + Accessible label + +``` diff --git a/.changeset/upset-teeth-add.md b/.changeset/upset-teeth-add.md new file mode 100644 index 0000000000..834ce7be2c --- /dev/null +++ b/.changeset/upset-teeth-add.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-mui-to-bui': patch +--- + +Updated BUI checkbox preview example to align with new component API. diff --git a/docs-ui/src/content/components/checkbox.props.ts b/docs-ui/src/content/components/checkbox.props.ts index c7ee69280e..ce0f1033ff 100644 --- a/docs-ui/src/content/components/checkbox.props.ts +++ b/docs-ui/src/content/components/checkbox.props.ts @@ -5,31 +5,32 @@ import { } from '@/utils/propDefs'; export const checkboxPropDefs: Record = { - label: { - type: 'string', - responsive: false, - }, - defaultChecked: { + children: { type: 'enum', - values: ['boolean', "'indeterminate'"], + values: ['React.ReactNode'], responsive: false, }, - checked: { - type: 'enum', - values: ['boolean', "'indeterminate'"], - responsive: false, - }, - onChange: { - type: 'enum', - values: ["(checked: boolean | 'indeterminate') => void"], - responsive: false, - }, - disabled: { + isSelected: { type: 'enum', values: ['boolean'], responsive: false, }, - required: { + defaultSelected: { + type: 'enum', + values: ['boolean'], + responsive: false, + }, + onChange: { + type: 'enum', + values: ['(isSelected: boolean) => void'], + responsive: false, + }, + isDisabled: { + type: 'enum', + values: ['boolean'], + responsive: false, + }, + isRequired: { type: 'enum', values: ['boolean'], responsive: false, @@ -48,13 +49,13 @@ export const checkboxPropDefs: Record = { export const checkboxUsageSnippet = `import { Checkbox } from '@backstage/ui'; -`; +Accept terms`; -export const checkboxDefaultSnippet = ``; +export const checkboxDefaultSnippet = `Accept terms and conditions`; -export const checkboxVariantsSnippet = ` - - - - -`; +export const checkboxVariantsSnippet = ` + Unchecked + Checked + Disabled + Checked & Disabled +`; diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 4e762f431c..7cdee27d0e 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -6,6 +6,7 @@ import { Avatar as Avatar_2 } from '@base-ui-components/react/avatar'; import { ButtonProps as ButtonProps_2 } from 'react-aria-components'; import { CellProps as CellProps_2 } from 'react-aria-components'; +import { CheckboxProps as CheckboxProps_2 } from 'react-aria-components'; import { Collapsible as Collapsible_2 } from '@base-ui-components/react/collapsible'; import { ColumnProps as ColumnProps_2 } from 'react-aria-components'; import { ComponentProps } from 'react'; @@ -270,31 +271,13 @@ export interface CellProps extends CellProps_2 { // @public (undocumented) export const Checkbox: ForwardRefExoticComponent< - CheckboxProps & RefAttributes + CheckboxProps & RefAttributes >; // @public (undocumented) -export interface CheckboxProps { +export interface CheckboxProps extends CheckboxProps_2 { // (undocumented) - checked?: boolean; - // (undocumented) - className?: string; - // (undocumented) - defaultChecked?: boolean; - // (undocumented) - disabled?: boolean; - // (undocumented) - label?: string; - // (undocumented) - name?: string; - // (undocumented) - onChange?: (checked: boolean) => void; - // (undocumented) - required?: boolean; - // (undocumented) - style?: React.CSSProperties; - // (undocumented) - value?: string; + children: React.ReactNode; } // @public @@ -431,12 +414,11 @@ export const componentDefinitions: { }; readonly Checkbox: { readonly classNames: { - readonly root: 'bui-CheckboxRoot'; - readonly label: 'bui-CheckboxLabel'; + readonly root: 'bui-Checkbox'; readonly indicator: 'bui-CheckboxIndicator'; }; readonly dataAttributes: { - readonly checked: readonly [true, false]; + readonly selected: readonly [true, false]; }; }; readonly Collapsible: { diff --git a/packages/ui/src/components/Checkbox/Checkbox.module.css b/packages/ui/src/components/Checkbox/Checkbox.module.css index 2e08b08c1a..bc4f700bce 100644 --- a/packages/ui/src/components/Checkbox/Checkbox.module.css +++ b/packages/ui/src/components/Checkbox/Checkbox.module.css @@ -17,35 +17,7 @@ @layer tokens, base, components, utilities; @layer components { - .bui-CheckboxRoot { - border: none; - display: flex; - align-items: center; - justify-content: center; - width: 1rem; - height: 1rem; - box-shadow: inset 0 0 0 1px var(--bui-border); - cursor: pointer; - border-radius: 2px; - transition: background-color 0.2s ease-in-out; - background-color: var(--bui-bg-surface-1); - padding: 0; - flex-shrink: 0; - } - - .bui-CheckboxRoot:focus-visible { - transition: none; - outline: 2px solid var(--bui-ring); - outline-offset: 2px; - } - - .bui-CheckboxRoot[data-checked] { - background-color: var(--bui-bg-solid); - box-shadow: none; - color: var(--bui-fg-solid); - } - - .bui-CheckboxLabel { + .bui-Checkbox { display: flex; flex-direction: row; align-items: center; @@ -55,18 +27,48 @@ font-weight: var(--bui-font-weight-regular); color: var(--bui-fg-primary); user-select: none; + cursor: pointer; + } - &:hover { - & .bui-CheckboxRoot:not([data-checked]) { - box-shadow: inset 0 0 0 1px var(--bui-border-hover); + .bui-Checkbox[data-disabled] { + cursor: not-allowed; + opacity: 0.5; + } + + .bui-CheckboxIndicator { + border: none; + display: flex; + align-items: center; + justify-content: center; + width: 1rem; + height: 1rem; + box-shadow: inset 0 0 0 1px var(--bui-border); + border-radius: 2px; + transition: background-color 0.2s ease-in-out; + background-color: var(--bui-bg-surface-1); + padding: 0; + flex-shrink: 0; + color: var(--bui-fg-solid); + + @media (prefers-reduced-motion: reduce) { + & { + transition: none; } } } - .bui-CheckboxIndicator { - display: flex; - align-items: center; - justify-content: center; - color: var(--bui-fg-solid); + .bui-Checkbox[data-focus-visible] .bui-CheckboxIndicator { + transition: none; + outline: 2px solid var(--bui-ring); + outline-offset: 2px; + } + + .bui-Checkbox[data-selected] .bui-CheckboxIndicator { + background-color: var(--bui-bg-solid); + box-shadow: none; + } + + .bui-Checkbox[data-hovered]:not([data-selected]) .bui-CheckboxIndicator { + box-shadow: inset 0 0 0 1px var(--bui-border-hover); } } diff --git a/packages/ui/src/components/Checkbox/Checkbox.stories.tsx b/packages/ui/src/components/Checkbox/Checkbox.stories.tsx index 080cae40b6..a41b7f2c8d 100644 --- a/packages/ui/src/components/Checkbox/Checkbox.stories.tsx +++ b/packages/ui/src/components/Checkbox/Checkbox.stories.tsx @@ -17,7 +17,6 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { Checkbox } from './Checkbox'; import { Flex } from '../Flex'; -import { Text } from '../Text'; const meta = { title: 'Backstage UI/Checkbox', @@ -29,31 +28,20 @@ type Story = StoryObj; export const Default: Story = { args: { - label: 'Accept terms and conditions', + children: 'Accept terms and conditions', }, }; export const AllVariants: Story = { + ...Default, render: () => ( - - - - - - - ), -}; - -export const Playground: Story = { - render: () => ( - - All variants - - - - - - + + Unchecked + Checked + Disabled + + Checked & Disabled + ), }; diff --git a/packages/ui/src/components/Checkbox/Checkbox.tsx b/packages/ui/src/components/Checkbox/Checkbox.tsx index 2414f29f5f..49019370a4 100644 --- a/packages/ui/src/components/Checkbox/Checkbox.tsx +++ b/packages/ui/src/components/Checkbox/Checkbox.tsx @@ -15,7 +15,7 @@ */ import { forwardRef } from 'react'; -import { Checkbox as CheckboxPrimitive } from '@base-ui-components/react/checkbox'; +import { Checkbox as RACheckbox } from 'react-aria-components'; import type { CheckboxProps } from './types'; import { useStyles } from '../../hooks/useStyles'; import clsx from 'clsx'; @@ -23,33 +23,24 @@ import styles from './Checkbox.module.css'; import { RiCheckLine } from '@remixicon/react'; /** @public */ -export const Checkbox = forwardRef( +export const Checkbox = forwardRef( (props, ref) => { - const { classNames, cleanedProps } = useStyles('Checkbox', props); - const { label, onChange, className, ...rest } = cleanedProps; + const { classNames } = useStyles('Checkbox'); + const { className, children, ...rest } = props; - const checkboxElement = ( - - - - - ); - - return label ? ( - - ) : ( - checkboxElement + + {children} + ); }, ); diff --git a/packages/ui/src/components/Checkbox/types.ts b/packages/ui/src/components/Checkbox/types.ts index 0240954afa..86e4b620b8 100644 --- a/packages/ui/src/components/Checkbox/types.ts +++ b/packages/ui/src/components/Checkbox/types.ts @@ -13,17 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { CheckboxProps as RACheckboxProps } from 'react-aria-components'; /** @public */ -export interface CheckboxProps { - label?: string; - defaultChecked?: boolean; - checked?: boolean; - onChange?: (checked: boolean) => void; - disabled?: boolean; - required?: boolean; - className?: string; - name?: string; - value?: string; - style?: React.CSSProperties; +export interface CheckboxProps extends RACheckboxProps { + children: React.ReactNode; } diff --git a/packages/ui/src/utils/componentDefinitions.ts b/packages/ui/src/utils/componentDefinitions.ts index 4f914cd1f9..e9ba832bf8 100644 --- a/packages/ui/src/utils/componentDefinitions.ts +++ b/packages/ui/src/utils/componentDefinitions.ts @@ -89,12 +89,11 @@ export const componentDefinitions = { }, Checkbox: { classNames: { - root: 'bui-CheckboxRoot', - label: 'bui-CheckboxLabel', + root: 'bui-Checkbox', indicator: 'bui-CheckboxIndicator', }, dataAttributes: { - checked: [true, false] as const, + selected: [true, false] as const, }, }, Collapsible: { diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.tsx b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.tsx index e59f4e78dd..0b62134178 100644 --- a/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.tsx +++ b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.tsx @@ -92,7 +92,7 @@ export function BuiThemePreview({ mode, styleObject }: IsolatedPreviewProps) { { value: 'option3', label: 'Option 3' }, ]} /> - + Checkbox Option Option 1 Option 2