From becee362247ce679095afe5fa70b9a94d85ffea6 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 13 Feb 2026 16:32:32 +0000 Subject: [PATCH] Update Accordion component Signed-off-by: Charles de Dreuille --- .changeset/social-bushes-flash.md | 5 + packages/ui/report.api.md | 114 +++++++--- .../components/Accordion/Accordion.module.css | 12 ++ .../Accordion/Accordion.stories.tsx | 55 +++++ .../ui/src/components/Accordion/Accordion.tsx | 198 +++++++++--------- .../ui/src/components/Accordion/definition.ts | 78 ++++++- packages/ui/src/components/Accordion/index.ts | 11 +- packages/ui/src/components/Accordion/types.ts | 59 ++++-- packages/ui/src/definitions.ts | 7 +- 9 files changed, 387 insertions(+), 152 deletions(-) create mode 100644 .changeset/social-bushes-flash.md diff --git a/.changeset/social-bushes-flash.md b/.changeset/social-bushes-flash.md new file mode 100644 index 0000000000..44736c8424 --- /dev/null +++ b/.changeset/social-bushes-flash.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Migrated Accordion components to use `useDefinition` instead of `useStyles`, and added automatic background adaptation based on parent container context. diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index b25b3c9b27..b0d6ca3778 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -66,15 +66,15 @@ export const Accordion: ForwardRefExoticComponent< // @public export const AccordionDefinition: { + readonly styles: { + readonly [key: string]: string; + }; readonly classNames: { readonly root: 'bui-Accordion'; - readonly trigger: 'bui-AccordionTrigger'; - readonly triggerButton: 'bui-AccordionTriggerButton'; - readonly triggerTitle: 'bui-AccordionTriggerTitle'; - readonly triggerSubtitle: 'bui-AccordionTriggerSubtitle'; - readonly triggerIcon: 'bui-AccordionTriggerIcon'; - readonly panel: 'bui-AccordionPanel'; - readonly group: 'bui-AccordionGroup'; + }; + readonly bg: 'consumer'; + readonly propDefs: { + readonly className: {}; }; }; @@ -84,11 +84,36 @@ export const AccordionGroup: ForwardRefExoticComponent< >; // @public -export interface AccordionGroupProps extends DisclosureGroupProps { - allowsMultiple?: boolean; - // (undocumented) +export const AccordionGroupDefinition: { + readonly styles: { + readonly [key: string]: string; + }; + readonly classNames: { + readonly root: 'bui-AccordionGroup'; + }; + readonly propDefs: { + readonly className: {}; + readonly allowsMultiple: { + readonly default: false; + }; + }; +}; + +// @public +export type AccordionGroupOwnProps = { className?: string; -} + allowsMultiple?: boolean; +}; + +// @public +export interface AccordionGroupProps + extends Omit, + AccordionGroupOwnProps {} + +// @public +export type AccordionOwnProps = { + className?: string; +}; // @public (undocumented) export const AccordionPanel: ForwardRefExoticComponent< @@ -96,16 +121,32 @@ export const AccordionPanel: ForwardRefExoticComponent< >; // @public -export interface AccordionPanelProps extends DisclosurePanelProps { - // (undocumented) - className?: string; -} +export const AccordionPanelDefinition: { + readonly styles: { + readonly [key: string]: string; + }; + readonly classNames: { + readonly root: 'bui-AccordionPanel'; + }; + readonly propDefs: { + readonly className: {}; + }; +}; // @public -export interface AccordionProps extends DisclosureProps { - // (undocumented) +export type AccordionPanelOwnProps = { className?: string; -} +}; + +// @public +export interface AccordionPanelProps + extends Omit, + AccordionPanelOwnProps {} + +// @public +export interface AccordionProps + extends Omit, + AccordionOwnProps {} // @public (undocumented) export const AccordionTrigger: ForwardRefExoticComponent< @@ -113,16 +154,37 @@ export const AccordionTrigger: ForwardRefExoticComponent< >; // @public -export interface AccordionTriggerProps extends HeadingProps { - // (undocumented) - children?: React.ReactNode; - // (undocumented) +export const AccordionTriggerDefinition: { + readonly styles: { + readonly [key: string]: string; + }; + readonly classNames: { + readonly root: 'bui-AccordionTrigger'; + readonly button: 'bui-AccordionTriggerButton'; + readonly title: 'bui-AccordionTriggerTitle'; + readonly subtitle: 'bui-AccordionTriggerSubtitle'; + readonly icon: 'bui-AccordionTriggerIcon'; + }; + readonly propDefs: { + readonly className: {}; + readonly title: {}; + readonly subtitle: {}; + readonly children: {}; + }; +}; + +// @public +export type AccordionTriggerOwnProps = { className?: string; - // (undocumented) - subtitle?: string; - // (undocumented) title?: string; -} + subtitle?: string; + children?: ReactNode; +}; + +// @public +export interface AccordionTriggerProps + extends Omit, + AccordionTriggerOwnProps {} // @public export const Alert: ForwardRefExoticComponent< diff --git a/packages/ui/src/components/Accordion/Accordion.module.css b/packages/ui/src/components/Accordion/Accordion.module.css index 7300b802e2..cef40a6744 100644 --- a/packages/ui/src/components/Accordion/Accordion.module.css +++ b/packages/ui/src/components/Accordion/Accordion.module.css @@ -22,6 +22,18 @@ background-color: var(--bui-bg-neutral-1); border-radius: var(--bui-radius-3); padding: var(--bui-space-3); + + &[data-on-bg='neutral-1'] { + background-color: var(--bui-bg-neutral-2); + } + + &[data-on-bg='neutral-2'] { + background-color: var(--bui-bg-neutral-3); + } + + &[data-on-bg='neutral-3'] { + background-color: var(--bui-bg-neutral-4); + } } .bui-AccordionTrigger { diff --git a/packages/ui/src/components/Accordion/Accordion.stories.tsx b/packages/ui/src/components/Accordion/Accordion.stories.tsx index 541cce5c84..f87c36e5bc 100644 --- a/packages/ui/src/components/Accordion/Accordion.stories.tsx +++ b/packages/ui/src/components/Accordion/Accordion.stories.tsx @@ -21,6 +21,7 @@ import { AccordionGroup, } from './Accordion'; import { Box } from '../Box'; +import { Flex } from '../Flex'; import { Text } from '../Text'; const Content = () => ( @@ -171,3 +172,57 @@ export const GroupMultipleOpen = meta.story({ ), }); + +export const AutoBg = meta.story({ + render: () => ( + +
+ Accordions automatically detect their parent bg context and increment + the neutral level by 1. No prop is needed on the accordion -- it's fully + automatic. +
+ + Default (no container) + + + + + + + + + Neutral 1 container + + + + + + + + + + + Neutral 2 container + + + + + + + + + + + Neutral 3 container + + + + + + + + + +
+ ), +}); diff --git a/packages/ui/src/components/Accordion/Accordion.tsx b/packages/ui/src/components/Accordion/Accordion.tsx index f31b923490..101a2d16c0 100644 --- a/packages/ui/src/components/Accordion/Accordion.tsx +++ b/packages/ui/src/components/Accordion/Accordion.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { forwardRef } from 'react'; +import { forwardRef, Ref } from 'react'; import { Disclosure as RADisclosure, Button as RAButton, @@ -23,132 +23,128 @@ import { Heading as RAHeading, } from 'react-aria-components'; import { RiArrowDownSLine } from '@remixicon/react'; -import clsx from 'clsx'; import type { AccordionProps, AccordionTriggerProps, AccordionPanelProps, AccordionGroupProps, } from './types'; -import { useStyles } from '../../hooks/useStyles'; -import { AccordionDefinition } from './definition'; -import styles from './Accordion.module.css'; +import { useDefinition } from '../../hooks/useDefinition'; +import { + AccordionDefinition, + AccordionTriggerDefinition, + AccordionPanelDefinition, + AccordionGroupDefinition, +} from './definition'; import { Flex } from '../Flex'; /** @public */ -export const Accordion = forwardRef< - React.ElementRef, - AccordionProps ->(({ className, ...props }, ref) => { - const { classNames, cleanedProps } = useStyles(AccordionDefinition, props); +export const Accordion = forwardRef( + (props: AccordionProps, ref: Ref>) => { + const { ownProps, restProps, dataAttributes } = useDefinition( + AccordionDefinition, + props, + ); + const { classes } = ownProps; - return ( - - ); -}); + return ( + + ); + }, +); Accordion.displayName = 'Accordion'; /** @public */ -export const AccordionTrigger = forwardRef< - React.ElementRef, - AccordionTriggerProps ->(({ className, title, subtitle, children, ...props }, ref) => { - const { classNames, cleanedProps } = useStyles(AccordionDefinition, props); +export const AccordionTrigger = forwardRef( + ( + props: AccordionTriggerProps, + ref: Ref>, + ) => { + const { ownProps, restProps, dataAttributes } = useDefinition( + AccordionTriggerDefinition, + props, + ); + const { classes, title, subtitle, children } = ownProps; - return ( - - - {children ? ( - children - ) : ( - - - {title} - - {subtitle && ( - - {subtitle} - - )} - - )} - - + {children ? ( + children + ) : ( + + {title} + {subtitle && {subtitle}} + )} - size={16} - /> - - - ); -}); + + + + + ); + }, +); AccordionTrigger.displayName = 'AccordionTrigger'; /** @public */ -export const AccordionPanel = forwardRef< - React.ElementRef, - AccordionPanelProps ->(({ className, ...props }, ref) => { - const { classNames, cleanedProps } = useStyles(AccordionDefinition, props); +export const AccordionPanel = forwardRef( + ( + props: AccordionPanelProps, + ref: Ref>, + ) => { + const { ownProps, restProps, dataAttributes } = useDefinition( + AccordionPanelDefinition, + props, + ); + const { classes } = ownProps; - return ( - - ); -}); + return ( + + ); + }, +); AccordionPanel.displayName = 'AccordionPanel'; /** @public */ -export const AccordionGroup = forwardRef< - React.ElementRef, - AccordionGroupProps ->(({ className, allowsMultiple = false, ...props }, ref) => { - const { classNames, cleanedProps } = useStyles(AccordionDefinition, props); +export const AccordionGroup = forwardRef( + ( + props: AccordionGroupProps, + ref: Ref>, + ) => { + const { ownProps, restProps, dataAttributes } = useDefinition( + AccordionGroupDefinition, + props, + ); + const { classes, allowsMultiple } = ownProps; - return ( - - ); -}); + return ( + + ); + }, +); AccordionGroup.displayName = 'AccordionGroup'; diff --git a/packages/ui/src/components/Accordion/definition.ts b/packages/ui/src/components/Accordion/definition.ts index afc508b922..4c525e3cc6 100644 --- a/packages/ui/src/components/Accordion/definition.ts +++ b/packages/ui/src/components/Accordion/definition.ts @@ -14,21 +14,79 @@ * limitations under the License. */ -import type { ComponentDefinition } from '../../types'; +import { defineComponent } from '../../hooks/useDefinition'; +import type { + AccordionOwnProps, + AccordionTriggerOwnProps, + AccordionPanelOwnProps, + AccordionGroupOwnProps, +} from './types'; +import styles from './Accordion.module.css'; /** * Component definition for Accordion * @public */ -export const AccordionDefinition = { +export const AccordionDefinition = defineComponent()({ + styles, classNames: { root: 'bui-Accordion', - trigger: 'bui-AccordionTrigger', - triggerButton: 'bui-AccordionTriggerButton', - triggerTitle: 'bui-AccordionTriggerTitle', - triggerSubtitle: 'bui-AccordionTriggerSubtitle', - triggerIcon: 'bui-AccordionTriggerIcon', - panel: 'bui-AccordionPanel', - group: 'bui-AccordionGroup', }, -} as const satisfies ComponentDefinition; + bg: 'consumer', + propDefs: { + className: {}, + }, +}); + +/** + * Component definition for AccordionTrigger + * @public + */ +export const AccordionTriggerDefinition = + defineComponent()({ + styles, + classNames: { + root: 'bui-AccordionTrigger', + button: 'bui-AccordionTriggerButton', + title: 'bui-AccordionTriggerTitle', + subtitle: 'bui-AccordionTriggerSubtitle', + icon: 'bui-AccordionTriggerIcon', + }, + propDefs: { + className: {}, + title: {}, + subtitle: {}, + children: {}, + }, + }); + +/** + * Component definition for AccordionPanel + * @public + */ +export const AccordionPanelDefinition = + defineComponent()({ + styles, + classNames: { + root: 'bui-AccordionPanel', + }, + propDefs: { + className: {}, + }, + }); + +/** + * Component definition for AccordionGroup + * @public + */ +export const AccordionGroupDefinition = + defineComponent()({ + styles, + classNames: { + root: 'bui-AccordionGroup', + }, + propDefs: { + className: {}, + allowsMultiple: { default: false }, + }, + }); diff --git a/packages/ui/src/components/Accordion/index.ts b/packages/ui/src/components/Accordion/index.ts index e20f49bec7..59a2af53b4 100644 --- a/packages/ui/src/components/Accordion/index.ts +++ b/packages/ui/src/components/Accordion/index.ts @@ -20,10 +20,19 @@ export { AccordionPanel, AccordionGroup, } from './Accordion'; -export { AccordionDefinition } from './definition'; +export { + AccordionDefinition, + AccordionTriggerDefinition, + AccordionPanelDefinition, + AccordionGroupDefinition, +} from './definition'; export type { + AccordionOwnProps, AccordionProps, + AccordionTriggerOwnProps, AccordionTriggerProps, + AccordionPanelOwnProps, AccordionPanelProps, + AccordionGroupOwnProps, AccordionGroupProps, } from './types'; diff --git a/packages/ui/src/components/Accordion/types.ts b/packages/ui/src/components/Accordion/types.ts index 06c8549ebd..9afc2f9a3f 100644 --- a/packages/ui/src/components/Accordion/types.ts +++ b/packages/ui/src/components/Accordion/types.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { ReactNode } from 'react'; import type { DisclosureProps as RADisclosureProps, HeadingProps as RAHeadingProps, @@ -21,42 +22,74 @@ import type { DisclosureGroupProps as RADisclosureGroupProps, } from 'react-aria-components'; +/** + * Own props for the Accordion component. + * @public + */ +export type AccordionOwnProps = { + className?: string; +}; + /** * Props for the Accordion component. * @public */ -export interface AccordionProps extends RADisclosureProps { +export interface AccordionProps + extends Omit, + AccordionOwnProps {} + +/** + * Own props for the AccordionTrigger component. + * @public + */ +export type AccordionTriggerOwnProps = { className?: string; -} + title?: string; + subtitle?: string; + children?: ReactNode; +}; /** * Props for the AccordionTrigger component. * @public */ -export interface AccordionTriggerProps extends RAHeadingProps { +export interface AccordionTriggerProps + extends Omit, + AccordionTriggerOwnProps {} + +/** + * Own props for the AccordionPanel component. + * @public + */ +export type AccordionPanelOwnProps = { className?: string; - title?: string; - subtitle?: string; - children?: React.ReactNode; -} +}; /** * Props for the AccordionPanel component. * @public */ -export interface AccordionPanelProps extends RADisclosurePanelProps { - className?: string; -} +export interface AccordionPanelProps + extends Omit, + AccordionPanelOwnProps {} /** - * Props for the AccordionGroup component. + * Own props for the AccordionGroup component. * @public */ -export interface AccordionGroupProps extends RADisclosureGroupProps { +export type AccordionGroupOwnProps = { className?: string; /** * Whether multiple accordions can be expanded at the same time. * @defaultValue false */ allowsMultiple?: boolean; -} +}; + +/** + * Props for the AccordionGroup component. + * @public + */ +export interface AccordionGroupProps + extends Omit, + AccordionGroupOwnProps {} diff --git a/packages/ui/src/definitions.ts b/packages/ui/src/definitions.ts index 808ea0abc3..030584fbb3 100644 --- a/packages/ui/src/definitions.ts +++ b/packages/ui/src/definitions.ts @@ -19,7 +19,12 @@ * @packageDocumentation */ -export { AccordionDefinition } from './components/Accordion/definition'; +export { + AccordionDefinition, + AccordionTriggerDefinition, + AccordionPanelDefinition, + AccordionGroupDefinition, +} from './components/Accordion/definition'; export { AlertDefinition } from './components/Alert/definition'; export { AvatarDefinition } from './components/Avatar/definition'; export { BoxDefinition } from './components/Box/definition';