refactor(ui): replace Collapsible with Accordion component
Replaces the Base UI Collapsible component with a new Accordion component built on React Aria's Disclosure primitives. Key changes: - Removed Collapsible component and all related files - Added new Accordion component with AccordionTrigger, AccordionPanel, and AccordionGroup - Introduced opinionated styling with built-in trigger component featuring animated chevron icon - Added support for title/subtitle props and custom trigger content via children - Implemented AccordionGroup with single/multiple expansion modes - Updated all documentation from collapsible.mdx to accordion.mdx - Added comprehensive migration guide in changeset - Updated API reports and exports This is a breaking change. Users must migrate from Collapsible to either: 1. Accordion (opinionated styled component) - recommended for most use cases 2. React Aria Disclosure directly - for full customization The changeset provides detailed migration examples for both paths. Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
---
|
||||
'@backstage/ui': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed `Collapsible` component. Migrate to `Accordion` or use React Aria `Disclosure`.
|
||||
|
||||
## Migration Path 1: Accordion (Opinionated Styled Component)
|
||||
|
||||
Accordion provides preset styling with a similar component structure.
|
||||
|
||||
```diff
|
||||
- import { Collapsible } from '@backstage/ui';
|
||||
+ import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui';
|
||||
|
||||
- <Collapsible.Root>
|
||||
- <Collapsible.Trigger render={(props) => <Button {...props}>Toggle</Button>} />
|
||||
- <Collapsible.Panel>Content</Collapsible.Panel>
|
||||
- </Collapsible.Root>
|
||||
|
||||
+ <Accordion>
|
||||
+ <AccordionTrigger title="Toggle" />
|
||||
+ <AccordionPanel>Content</AccordionPanel>
|
||||
+ </Accordion>
|
||||
```
|
||||
|
||||
CSS classes: `.bui-CollapsibleRoot` → `.bui-Accordion`, `.bui-CollapsibleTrigger` → `.bui-AccordionTrigger` (now on heading element), `.bui-CollapsiblePanel` → `.bui-AccordionPanel`
|
||||
|
||||
## Migration Path 2: React Aria Disclosure (Full Customization)
|
||||
|
||||
For custom styling without preset styles:
|
||||
|
||||
```tsx
|
||||
import { Disclosure, Button, DisclosurePanel } from 'react-aria-components';
|
||||
|
||||
<Disclosure>
|
||||
<Button slot="trigger">Toggle</Button>
|
||||
<DisclosurePanel>Content</DisclosurePanel>
|
||||
</Disclosure>;
|
||||
```
|
||||
@@ -0,0 +1,128 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { AccordionSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
accordionPropDefs,
|
||||
accordionTriggerPropDefs,
|
||||
accordionPanelPropDefs,
|
||||
accordionGroupPropDefs,
|
||||
accordionUsageSnippet,
|
||||
accordionWithSubtitleSnippet,
|
||||
accordionCustomTriggerSnippet,
|
||||
accordionDefaultExpandedSnippet,
|
||||
accordionGroupSingleOpenSnippet,
|
||||
accordionGroupMultipleOpenSnippet,
|
||||
} from './accordion.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Accordion"
|
||||
description="A component for showing and hiding content with animation."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
height={240}
|
||||
preview={<AccordionSnippet story="Default" />}
|
||||
code={accordionUsageSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={accordionUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
### Accordion
|
||||
|
||||
Root container for the accordion. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={accordionPropDefs} />
|
||||
|
||||
### AccordionTrigger
|
||||
|
||||
Trigger component with built-in animated chevron icon. Renders a heading element (defaults to `<h3>`, configurable via `level` prop) wrapping a `<button>`.
|
||||
|
||||
<PropsTable data={accordionTriggerPropDefs} />
|
||||
|
||||
### AccordionPanel
|
||||
|
||||
Panel with the accordion content. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={accordionPanelPropDefs} />
|
||||
|
||||
### AccordionGroup
|
||||
|
||||
Container for managing multiple accordions. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={accordionGroupPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### With Subtitle
|
||||
|
||||
Here's a view when using both title and subtitle props.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
height={240}
|
||||
preview={<AccordionSnippet story="WithSubtitle" />}
|
||||
code={accordionWithSubtitleSnippet}
|
||||
/>
|
||||
|
||||
### Custom Trigger
|
||||
|
||||
Here's a view when providing custom multi-line content as children.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
height={280}
|
||||
preview={<AccordionSnippet story="CustomTrigger" />}
|
||||
code={accordionCustomTriggerSnippet}
|
||||
/>
|
||||
|
||||
### Default Expanded
|
||||
|
||||
Here's a view when the panel is expanded by default.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
height={280}
|
||||
preview={<AccordionSnippet story="DefaultExpanded" />}
|
||||
code={accordionDefaultExpandedSnippet}
|
||||
/>
|
||||
|
||||
### Group Single Open
|
||||
|
||||
Here's a view when only one accordion can be open at a time.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
height={280}
|
||||
preview={<AccordionSnippet story="GroupSingleOpen" />}
|
||||
code={accordionGroupSingleOpenSnippet}
|
||||
/>
|
||||
|
||||
### Group Multiple Open
|
||||
|
||||
Here's a view when multiple accordions can be open simultaneously.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
height={280}
|
||||
preview={<AccordionSnippet story="GroupMultipleOpen" />}
|
||||
code={accordionGroupMultipleOpenSnippet}
|
||||
/>
|
||||
|
||||
<Theming component="Accordion" />
|
||||
|
||||
<ChangelogComponent component="accordion" />
|
||||
@@ -0,0 +1,119 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const accordionPropDefs: Record<string, PropDef> = {
|
||||
children: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode', '(state: { isExpanded: boolean }) => ReactNode'],
|
||||
},
|
||||
defaultExpanded: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
isExpanded: {
|
||||
type: 'boolean',
|
||||
},
|
||||
onExpandedChange: {
|
||||
type: 'enum',
|
||||
values: ['(isExpanded: boolean) => void'],
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const accordionTriggerPropDefs: Record<string, PropDef> = {
|
||||
level: {
|
||||
type: 'enum',
|
||||
values: ['1', '2', '3', '4', '5', '6'],
|
||||
default: '3',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
},
|
||||
subtitle: {
|
||||
type: 'string',
|
||||
},
|
||||
children: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const accordionPanelPropDefs: Record<string, PropDef> = {
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const accordionGroupPropDefs: Record<string, PropDef> = {
|
||||
allowsMultiple: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const accordionUsageSnippet = `import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui';
|
||||
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Toggle Panel" />
|
||||
<AccordionPanel>Your content</AccordionPanel>
|
||||
</Accordion>`;
|
||||
|
||||
export const accordionWithSubtitleSnippet = `<Accordion>
|
||||
<AccordionTrigger
|
||||
title="Advanced Settings"
|
||||
subtitle="Configure additional options"
|
||||
/>
|
||||
<AccordionPanel>
|
||||
<Text>Your content here</Text>
|
||||
</AccordionPanel>
|
||||
</Accordion>`;
|
||||
|
||||
export const accordionCustomTriggerSnippet = `<Accordion>
|
||||
<AccordionTrigger>
|
||||
<Box>
|
||||
<Text as="div" weight="bold">Custom Multi-line Trigger</Text>
|
||||
<Text as="div" size="small" color="secondary">
|
||||
Click to expand additional details
|
||||
</Text>
|
||||
</Box>
|
||||
</AccordionTrigger>
|
||||
<AccordionPanel>
|
||||
<Text>Your content here</Text>
|
||||
</AccordionPanel>
|
||||
</Accordion>`;
|
||||
|
||||
export const accordionDefaultExpandedSnippet = `<Accordion defaultExpanded>
|
||||
<AccordionTrigger title="Toggle Panel" />
|
||||
<AccordionPanel>
|
||||
<Text>Your content here</Text>
|
||||
</AccordionPanel>
|
||||
</Accordion>`;
|
||||
|
||||
export const accordionGroupSingleOpenSnippet = `<AccordionGroup>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="First Panel" />
|
||||
<AccordionPanel>Content 1</AccordionPanel>
|
||||
</Accordion>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Second Panel" />
|
||||
<AccordionPanel>Content 2</AccordionPanel>
|
||||
</Accordion>
|
||||
</AccordionGroup>`;
|
||||
|
||||
export const accordionGroupMultipleOpenSnippet = `<AccordionGroup allowsMultiple>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="First Panel" />
|
||||
<AccordionPanel>Content 1</AccordionPanel>
|
||||
</Accordion>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Second Panel" />
|
||||
<AccordionPanel>Content 2</AccordionPanel>
|
||||
</Accordion>
|
||||
</AccordionGroup>`;
|
||||
@@ -1,72 +0,0 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { CollapsibleSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
collapsibleRootPropDefs,
|
||||
collapsibleTriggerPropDefs,
|
||||
collapsiblePanelPropDefs,
|
||||
collapsibleUsageSnippet,
|
||||
collapsibleDefaultSnippet,
|
||||
collapsibleTriggerSnippet,
|
||||
collapsibleOpenSnippet,
|
||||
} from './collapsible.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Collapsible"
|
||||
description="A collapsible component that can be used to display content in a box."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
height={240}
|
||||
preview={<CollapsibleSnippet story="Default" />}
|
||||
code={collapsibleDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={collapsibleUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
### Collapsible.Root
|
||||
|
||||
Groups all parts of the collapsible. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={collapsibleRootPropDefs} />
|
||||
|
||||
### Collapsible.Trigger
|
||||
|
||||
The trigger by default render a simple unstyled button. Because menus can be rendered in different ways, we recommend
|
||||
using the `render` prop to render a custom trigger.
|
||||
|
||||
<CodeBlock code={collapsibleTriggerSnippet} />
|
||||
|
||||
<PropsTable data={collapsibleTriggerPropDefs} />
|
||||
|
||||
### Collapsible.Panel
|
||||
|
||||
A panel with the collapsible contents. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={collapsiblePanelPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
Open the panel by default by setting the `defaultOpen` prop to `true`.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<CollapsibleSnippet story="Open" />}
|
||||
code={collapsibleOpenSnippet}
|
||||
/>
|
||||
|
||||
<Theming component="Collapsible" />
|
||||
|
||||
<ChangelogComponent component="collapsible" />
|
||||
@@ -1,86 +0,0 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
renderPropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const collapsibleRootPropDefs: Record<string, PropDef> = {
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
open: {
|
||||
type: 'boolean',
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(open) => void'],
|
||||
},
|
||||
...renderPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const collapsibleTriggerPropDefs: Record<string, PropDef> = {
|
||||
...renderPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const collapsiblePanelPropDefs: Record<string, PropDef> = {
|
||||
hiddenUntilFound: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
keepMounted: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
...renderPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const collapsibleUsageSnippet = `import { Collapsible } from '@backstage/ui';
|
||||
|
||||
<Collapsible.Root>
|
||||
<Collapsible.Trigger render={(props, state) => (
|
||||
<Button {...props}>
|
||||
{state.open ? 'Close Panel' : 'Open Panel'}
|
||||
</Button>
|
||||
)} />
|
||||
<Collapsible.Panel>Your content</Collapsible.Panel>
|
||||
</Collapsible.Root>`;
|
||||
|
||||
export const collapsibleDefaultSnippet = `<Collapsible.Root>
|
||||
<Collapsible.Trigger render={(props, state) => (
|
||||
<Button {...props}>
|
||||
{state.open ? 'Close Panel' : 'Open Panel'}
|
||||
</Button>
|
||||
)} />
|
||||
<Collapsible.Panel>
|
||||
<Box>
|
||||
<Text>It's the edge of the world and all of Western civilization</Text>
|
||||
<Text>The sun may rise in the East, at least it settled in a final location</Text>
|
||||
<Text>It's understood that Hollywood sells Californication</Text>
|
||||
</Box>
|
||||
</Collapsible.Panel>
|
||||
</Collapsible.Root>`;
|
||||
|
||||
export const collapsibleTriggerSnippet = `<Collapsible.Trigger render={props => <Button {...props} />} />`;
|
||||
|
||||
export const collapsibleOpenSnippet = `<Collapsible.Root defaultOpen>
|
||||
<Collapsible.Trigger render={(props, state) => (
|
||||
<Button {...props}>
|
||||
{state.open ? 'Close Panel' : 'Open Panel'}
|
||||
</Button>
|
||||
)} />
|
||||
<Collapsible.Panel>
|
||||
<Box>
|
||||
<Text>It's the edge of the world and all of Western civilization</Text>
|
||||
<Text>The sun may rise in the East, at least it settled in a final location</Text>
|
||||
<Text>It's understood that Hollywood sells Californication</Text>
|
||||
</Box>
|
||||
</Collapsible.Panel>
|
||||
</Collapsible.Root>`;
|
||||
@@ -15,7 +15,7 @@ import * as SelectStories from '../../../packages/ui/src/components/Select/Selec
|
||||
import * as MenuStories from '../../../packages/ui/src/components/Menu/Menu.stories';
|
||||
import * as LinkStories from '../../../packages/ui/src/components/Link/Link.stories';
|
||||
import * as AvatarStories from '../../../packages/ui/src/components/Avatar/Avatar.stories';
|
||||
import * as CollapsibleStories from '../../../packages/ui/src/components/Collapsible/Collapsible.stories';
|
||||
import * as AccordionStories from '../../../packages/ui/src/components/Accordion/Accordion.stories';
|
||||
import * as DialogStories from '../../../packages/ui/src/components/Dialog/Dialog.stories';
|
||||
import * as RadioGroupStories from '../../../packages/ui/src/components/RadioGroup/RadioGroup.stories';
|
||||
import * as TabsStories from '../../../packages/ui/src/components/Tabs/Tabs.stories';
|
||||
@@ -62,7 +62,7 @@ export const SelectSnippet = createSnippetComponent(SelectStories);
|
||||
export const MenuSnippet = createSnippetComponent(MenuStories);
|
||||
export const LinkSnippet = createSnippetComponent(LinkStories);
|
||||
export const AvatarSnippet = createSnippetComponent(AvatarStories);
|
||||
export const CollapsibleSnippet = createSnippetComponent(CollapsibleStories);
|
||||
export const AccordionSnippet = createSnippetComponent(AccordionStories);
|
||||
export const DialogSnippet = createSnippetComponent(DialogStories);
|
||||
export const RadioGroupSnippet = createSnippetComponent(RadioGroupStories);
|
||||
export const TabsSnippet = createSnippetComponent(TabsStories);
|
||||
|
||||
@@ -13,6 +13,7 @@ export type Component =
|
||||
| 'datatable'
|
||||
| 'select'
|
||||
| 'collapsible'
|
||||
| 'accordion'
|
||||
| 'checkbox'
|
||||
| 'container'
|
||||
| 'link'
|
||||
|
||||
@@ -24,6 +24,10 @@ export const layoutComponents: Page[] = [
|
||||
];
|
||||
|
||||
export const components: Page[] = [
|
||||
{
|
||||
title: 'Accordion',
|
||||
slug: 'accordion',
|
||||
},
|
||||
{
|
||||
title: 'Avatar',
|
||||
slug: 'avatar',
|
||||
@@ -48,10 +52,6 @@ export const components: Page[] = [
|
||||
title: 'Checkbox',
|
||||
slug: 'checkbox',
|
||||
},
|
||||
{
|
||||
title: 'Collapsible',
|
||||
slug: 'collapsible',
|
||||
},
|
||||
{
|
||||
title: 'Dialog',
|
||||
slug: 'dialog',
|
||||
|
||||
+66
-27
@@ -6,12 +6,14 @@
|
||||
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';
|
||||
import type { ComponentPropsWithRef } from 'react';
|
||||
import { DetailedHTMLProps } from 'react';
|
||||
import type { DialogTriggerProps as DialogTriggerProps_2 } from 'react-aria-components';
|
||||
import type { DisclosureGroupProps } from 'react-aria-components';
|
||||
import type { DisclosurePanelProps } from 'react-aria-components';
|
||||
import type { DisclosureProps } from 'react-aria-components';
|
||||
import type { ElementType } from 'react';
|
||||
import { ForwardRefExoticComponent } from 'react';
|
||||
import type { HeadingProps } from 'react-aria-components';
|
||||
@@ -51,6 +53,57 @@ import type { TextFieldProps as TextFieldProps_2 } from 'react-aria-components';
|
||||
import { TooltipProps as TooltipProps_2 } from 'react-aria-components';
|
||||
import { TooltipTriggerComponentProps } from 'react-aria-components';
|
||||
|
||||
// @public (undocumented)
|
||||
export const Accordion: ForwardRefExoticComponent<
|
||||
AccordionProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const AccordionGroup: ForwardRefExoticComponent<
|
||||
AccordionGroupProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface AccordionGroupProps extends DisclosureGroupProps {
|
||||
allowsMultiple?: boolean;
|
||||
// (undocumented)
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const AccordionPanel: ForwardRefExoticComponent<
|
||||
AccordionPanelProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface AccordionPanelProps extends DisclosurePanelProps {
|
||||
// (undocumented)
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface AccordionProps extends DisclosureProps {
|
||||
// (undocumented)
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const AccordionTrigger: ForwardRefExoticComponent<
|
||||
AccordionTriggerProps & RefAttributes<HTMLHeadingElement>
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface AccordionTriggerProps extends HeadingProps {
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
// (undocumented)
|
||||
className?: string;
|
||||
// (undocumented)
|
||||
subtitle?: string;
|
||||
// (undocumented)
|
||||
title?: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type AlignItems = 'stretch' | 'start' | 'center' | 'end';
|
||||
|
||||
@@ -279,25 +332,6 @@ export interface CheckboxProps extends CheckboxProps_2 {
|
||||
// @public
|
||||
export type ClassNamesMap = Record<string, string>;
|
||||
|
||||
// @public
|
||||
export const Collapsible: {
|
||||
Root: ForwardRefExoticComponent<
|
||||
Omit<Collapsible_2.Root.Props & RefAttributes<HTMLDivElement>, 'ref'> &
|
||||
RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
Trigger: ForwardRefExoticComponent<
|
||||
Omit<
|
||||
Collapsible_2.Trigger.Props & RefAttributes<HTMLButtonElement>,
|
||||
'ref'
|
||||
> &
|
||||
RefAttributes<HTMLButtonElement>
|
||||
>;
|
||||
Panel: ForwardRefExoticComponent<
|
||||
Omit<Collapsible_2.Panel.Props & RefAttributes<HTMLButtonElement>, 'ref'> &
|
||||
RefAttributes<HTMLButtonElement>
|
||||
>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const Column: (props: ColumnProps) => JSX_2.Element;
|
||||
|
||||
@@ -417,13 +451,6 @@ export const componentDefinitions: {
|
||||
readonly selected: readonly [true, false];
|
||||
};
|
||||
};
|
||||
readonly Collapsible: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-CollapsibleRoot';
|
||||
readonly trigger: 'bui-CollapsibleTrigger';
|
||||
readonly panel: 'bui-CollapsiblePanel';
|
||||
};
|
||||
};
|
||||
readonly Container: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-Container';
|
||||
@@ -440,6 +467,18 @@ export const componentDefinitions: {
|
||||
readonly footer: 'bui-DialogFooter';
|
||||
};
|
||||
};
|
||||
readonly Accordion: {
|
||||
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 FieldError: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-FieldError';
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@layer tokens, base, components, utilities;
|
||||
|
||||
@layer components {
|
||||
.bui-Accordion {
|
||||
width: 100%;
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
border-radius: var(--bui-radius-3);
|
||||
padding: var(--bui-space-3);
|
||||
}
|
||||
|
||||
.bui-AccordionTrigger {
|
||||
all: unset;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.bui-AccordionTriggerButton {
|
||||
all: unset;
|
||||
width: 100%;
|
||||
color: var(--bui-fg-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
transition: none;
|
||||
box-shadow: inset 0 0 0 2px var(--bui-ring);
|
||||
}
|
||||
|
||||
&[data-disabled='true'] {
|
||||
background-color: transparent;
|
||||
color: var(--bui-fg-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-AccordionTriggerTitle {
|
||||
font-size: var(--bui-font-size-4);
|
||||
font-weight: var(--bui-font-weight-bold);
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
.bui-AccordionTriggerSubtitle {
|
||||
font-size: var(--bui-font-size-2);
|
||||
line-height: 140%;
|
||||
color: var(--bui-fg-secondary);
|
||||
}
|
||||
|
||||
.bui-AccordionTriggerIcon {
|
||||
transition: transform 150ms ease-out;
|
||||
flex-shrink: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
[data-expanded='true'] & {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.bui-AccordionPanel {
|
||||
[data-expanded='true'] & {
|
||||
padding-top: var(--bui-space-1);
|
||||
}
|
||||
}
|
||||
|
||||
.bui-AccordionGroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--bui-space-3);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionTrigger,
|
||||
AccordionPanel,
|
||||
AccordionGroup,
|
||||
} from './Accordion';
|
||||
import { Box } from '../Box';
|
||||
import { Text } from '../Text';
|
||||
|
||||
const Content = () => (
|
||||
<Box>
|
||||
<Text as="p">
|
||||
It's the edge of the world and all of Western civilization
|
||||
</Text>
|
||||
<Text as="p">
|
||||
The sun may rise in the East, at least it settled in a final location
|
||||
</Text>
|
||||
<Text as="p">It's understood that Hollywood sells Californication</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const meta = {
|
||||
title: 'Backstage UI/Accordion',
|
||||
component: Accordion,
|
||||
} satisfies Meta<typeof Accordion>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Toggle Panel" />
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithSubtitle: Story = {
|
||||
render: () => (
|
||||
<Accordion>
|
||||
<AccordionTrigger
|
||||
title="Advanced Settings"
|
||||
subtitle="Configure additional options"
|
||||
/>
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
),
|
||||
};
|
||||
|
||||
export const CustomTrigger: Story = {
|
||||
render: () => (
|
||||
<Accordion>
|
||||
<AccordionTrigger>
|
||||
<Box>
|
||||
<Text as="div" variant="body-large" weight="bold">
|
||||
Custom Multi-line Trigger
|
||||
</Text>
|
||||
<Text as="div" variant="body-medium" color="secondary">
|
||||
Click to expand additional details and configuration options
|
||||
</Text>
|
||||
</Box>
|
||||
</AccordionTrigger>
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
),
|
||||
};
|
||||
|
||||
export const DefaultExpanded: Story = {
|
||||
render: () => (
|
||||
<Accordion defaultExpanded>
|
||||
<AccordionTrigger title="Toggle Panel" />
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
),
|
||||
};
|
||||
|
||||
export const GroupSingleOpen: Story = {
|
||||
render: () => (
|
||||
<AccordionGroup>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="First Panel" />
|
||||
<AccordionPanel>
|
||||
<Box>
|
||||
<Text as="p">
|
||||
It's the edge of the world and all of Western civilization
|
||||
</Text>
|
||||
</Box>
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Second Panel" />
|
||||
<AccordionPanel>
|
||||
<Box>
|
||||
<Text as="p">
|
||||
The sun may rise in the East, at least it settled in a final
|
||||
location
|
||||
</Text>
|
||||
</Box>
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Third Panel" />
|
||||
<AccordionPanel>
|
||||
<Box>
|
||||
<Text as="p">
|
||||
It's understood that Hollywood sells Californication
|
||||
</Text>
|
||||
</Box>
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
),
|
||||
};
|
||||
|
||||
export const GroupMultipleOpen: Story = {
|
||||
render: () => (
|
||||
<AccordionGroup allowsMultiple>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="First Panel" />
|
||||
<AccordionPanel>
|
||||
<Box>
|
||||
<Text as="p">
|
||||
It's the edge of the world and all of Western civilization
|
||||
</Text>
|
||||
</Box>
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Second Panel" />
|
||||
<AccordionPanel>
|
||||
<Box>
|
||||
<Text as="p">
|
||||
The sun may rise in the East, at least it settled in a final
|
||||
location
|
||||
</Text>
|
||||
</Box>
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Third Panel" />
|
||||
<AccordionPanel>
|
||||
<Box>
|
||||
<Text as="p">
|
||||
It's understood that Hollywood sells Californication
|
||||
</Text>
|
||||
</Box>
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { forwardRef } from 'react';
|
||||
import {
|
||||
Disclosure as RADisclosure,
|
||||
Button as RAButton,
|
||||
DisclosurePanel as RADisclosurePanel,
|
||||
DisclosureGroup as RADisclosureGroup,
|
||||
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 styles from './Accordion.module.css';
|
||||
import { Flex } from '../Flex';
|
||||
|
||||
/** @public */
|
||||
export const Accordion = forwardRef<
|
||||
React.ElementRef<typeof RADisclosure>,
|
||||
AccordionProps
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles('Accordion', props);
|
||||
|
||||
return (
|
||||
<RADisclosure
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
{...cleanedProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Accordion.displayName = 'Accordion';
|
||||
|
||||
/** @public */
|
||||
export const AccordionTrigger = forwardRef<
|
||||
React.ElementRef<typeof RAHeading>,
|
||||
AccordionTriggerProps
|
||||
>(({ className, title, subtitle, children, ...props }, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles('Accordion', props);
|
||||
|
||||
return (
|
||||
<RAHeading
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
classNames.trigger,
|
||||
styles[classNames.trigger],
|
||||
className,
|
||||
)}
|
||||
{...cleanedProps}
|
||||
>
|
||||
<RAButton
|
||||
slot="trigger"
|
||||
className={clsx(
|
||||
classNames.triggerButton,
|
||||
styles[classNames.triggerButton],
|
||||
)}
|
||||
>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<Flex gap="2" align="center">
|
||||
<span
|
||||
className={clsx(
|
||||
classNames.triggerTitle,
|
||||
styles[classNames.triggerTitle],
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
{subtitle && (
|
||||
<span
|
||||
className={clsx(
|
||||
classNames.triggerSubtitle,
|
||||
styles[classNames.triggerSubtitle],
|
||||
)}
|
||||
>
|
||||
{subtitle}
|
||||
</span>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<RiArrowDownSLine
|
||||
className={clsx(
|
||||
classNames.triggerIcon,
|
||||
styles[classNames.triggerIcon],
|
||||
)}
|
||||
size={16}
|
||||
/>
|
||||
</RAButton>
|
||||
</RAHeading>
|
||||
);
|
||||
});
|
||||
|
||||
AccordionTrigger.displayName = 'AccordionTrigger';
|
||||
|
||||
/** @public */
|
||||
export const AccordionPanel = forwardRef<
|
||||
React.ElementRef<typeof RADisclosurePanel>,
|
||||
AccordionPanelProps
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles('Accordion', props);
|
||||
|
||||
return (
|
||||
<RADisclosurePanel
|
||||
ref={ref}
|
||||
className={clsx(classNames.panel, styles[classNames.panel], className)}
|
||||
{...cleanedProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
AccordionPanel.displayName = 'AccordionPanel';
|
||||
|
||||
/** @public */
|
||||
export const AccordionGroup = forwardRef<
|
||||
React.ElementRef<typeof RADisclosureGroup>,
|
||||
AccordionGroupProps
|
||||
>(({ className, allowsMultiple = false, ...props }, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles('Accordion', props);
|
||||
|
||||
return (
|
||||
<RADisclosureGroup
|
||||
ref={ref}
|
||||
allowsMultipleExpanded={allowsMultiple}
|
||||
className={clsx(classNames.group, styles[classNames.group], className)}
|
||||
{...cleanedProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
AccordionGroup.displayName = 'AccordionGroup';
|
||||
+12
-1
@@ -14,4 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Collapsible } from './Collapsible';
|
||||
export {
|
||||
Accordion,
|
||||
AccordionTrigger,
|
||||
AccordionPanel,
|
||||
AccordionGroup,
|
||||
} from './Accordion';
|
||||
export type {
|
||||
AccordionProps,
|
||||
AccordionTriggerProps,
|
||||
AccordionPanelProps,
|
||||
AccordionGroupProps,
|
||||
} from './types';
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type {
|
||||
DisclosureProps as RADisclosureProps,
|
||||
HeadingProps as RAHeadingProps,
|
||||
DisclosurePanelProps as RADisclosurePanelProps,
|
||||
DisclosureGroupProps as RADisclosureGroupProps,
|
||||
} from 'react-aria-components';
|
||||
|
||||
/**
|
||||
* Props for the Accordion component.
|
||||
* @public
|
||||
*/
|
||||
export interface AccordionProps extends RADisclosureProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the AccordionTrigger component.
|
||||
* @public
|
||||
*/
|
||||
export interface AccordionTriggerProps extends RAHeadingProps {
|
||||
className?: string;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the AccordionPanel component.
|
||||
* @public
|
||||
*/
|
||||
export interface AccordionPanelProps extends RADisclosurePanelProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the AccordionGroup component.
|
||||
* @public
|
||||
*/
|
||||
export interface AccordionGroupProps extends RADisclosureGroupProps {
|
||||
className?: string;
|
||||
/**
|
||||
* Whether multiple accordions can be expanded at the same time.
|
||||
* @defaultValue false
|
||||
*/
|
||||
allowsMultiple?: boolean;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@layer tokens, base, components, utilities;
|
||||
|
||||
@layer components {
|
||||
.bui-CollapsiblePanel {
|
||||
display: flex;
|
||||
height: var(--collapsible-panel-height);
|
||||
overflow: hidden;
|
||||
transition: all 150ms ease-out;
|
||||
|
||||
&[data-starting-style],
|
||||
&[data-ending-style] {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { Collapsible } from './Collapsible';
|
||||
import { Button } from '../Button';
|
||||
import { Box } from '../Box';
|
||||
import { Text } from '../Text';
|
||||
import { RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react';
|
||||
|
||||
const meta = {
|
||||
title: 'Backstage UI/Collapsible',
|
||||
component: Collapsible.Root,
|
||||
} satisfies Meta<typeof Collapsible.Root>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 'var(--bui-space-2)',
|
||||
alignItems: 'center',
|
||||
},
|
||||
children: (
|
||||
<>
|
||||
<Collapsible.Trigger
|
||||
render={(props, state) => (
|
||||
<Button
|
||||
variant="secondary"
|
||||
iconEnd={state.open ? <RiArrowUpSLine /> : <RiArrowDownSLine />}
|
||||
{...props}
|
||||
>
|
||||
{state.open ? 'Close Panel' : 'Open Panel'}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
<Collapsible.Panel>
|
||||
<Box
|
||||
p="4"
|
||||
style={{
|
||||
border: '1px solid var(--bui-border)',
|
||||
backgroundColor: 'var(--bui-bg-surface-1)',
|
||||
color: 'var(--bui-fg-primary)',
|
||||
borderRadius: 'var(--bui-radius-2)',
|
||||
width: '460px',
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
It's the edge of the world and all of Western civilization
|
||||
</Text>
|
||||
<Text>
|
||||
The sun may rise in the East, at least it settled in a final
|
||||
location
|
||||
</Text>
|
||||
<Text>It's understood that Hollywood sells Californication</Text>
|
||||
</Box>
|
||||
</Collapsible.Panel>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const Open: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
defaultOpen: true,
|
||||
},
|
||||
};
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { forwardRef } from 'react';
|
||||
import { Collapsible as CollapsiblePrimitive } from '@base-ui-components/react/collapsible';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './Collapsible.module.css';
|
||||
|
||||
const CollapsibleRoot = forwardRef<
|
||||
React.ElementRef<typeof CollapsiblePrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.Root>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles('Collapsible', props);
|
||||
|
||||
return (
|
||||
<CollapsiblePrimitive.Root
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
{...cleanedProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
CollapsibleRoot.displayName = CollapsiblePrimitive.Root.displayName;
|
||||
|
||||
const CollapsibleTrigger = forwardRef<
|
||||
React.ElementRef<typeof CollapsiblePrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles('Collapsible', props);
|
||||
|
||||
return (
|
||||
<CollapsiblePrimitive.Trigger
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
classNames.trigger,
|
||||
styles[classNames.trigger],
|
||||
className,
|
||||
)}
|
||||
{...cleanedProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
CollapsibleTrigger.displayName = CollapsiblePrimitive.Trigger.displayName;
|
||||
|
||||
const CollapsiblePanel = forwardRef<
|
||||
React.ElementRef<typeof CollapsiblePrimitive.Panel>,
|
||||
React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.Panel>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles('Collapsible', props);
|
||||
|
||||
return (
|
||||
<CollapsiblePrimitive.Panel
|
||||
ref={ref}
|
||||
className={clsx(classNames.panel, styles[classNames.panel], className)}
|
||||
{...cleanedProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
CollapsiblePanel.displayName = CollapsiblePrimitive.Panel.displayName;
|
||||
|
||||
/**
|
||||
* Collapsible is a component that allows you to collapse and expand content.
|
||||
* It is a wrapper around the CollapsiblePrimitive component from base-ui-components.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const Collapsible = {
|
||||
Root: CollapsibleRoot,
|
||||
Trigger: CollapsibleTrigger,
|
||||
Panel: CollapsiblePanel,
|
||||
};
|
||||
@@ -30,7 +30,7 @@ export * from './components/Container';
|
||||
export * from './components/Avatar';
|
||||
export * from './components/Button';
|
||||
export * from './components/Card';
|
||||
export * from './components/Collapsible';
|
||||
export * from './components/Accordion';
|
||||
export * from './components/Dialog';
|
||||
export * from './components/FieldLabel';
|
||||
export * from './components/Header';
|
||||
|
||||
@@ -96,13 +96,6 @@ export const componentDefinitions = {
|
||||
selected: [true, false] as const,
|
||||
},
|
||||
},
|
||||
Collapsible: {
|
||||
classNames: {
|
||||
root: 'bui-CollapsibleRoot',
|
||||
trigger: 'bui-CollapsibleTrigger',
|
||||
panel: 'bui-CollapsiblePanel',
|
||||
},
|
||||
},
|
||||
Container: {
|
||||
classNames: {
|
||||
root: 'bui-Container',
|
||||
@@ -119,6 +112,18 @@ export const componentDefinitions = {
|
||||
footer: 'bui-DialogFooter',
|
||||
},
|
||||
},
|
||||
Accordion: {
|
||||
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',
|
||||
},
|
||||
},
|
||||
FieldError: {
|
||||
classNames: {
|
||||
root: 'bui-FieldError',
|
||||
|
||||
Reference in New Issue
Block a user