diff --git a/.storybook/components/DesignTokens/Tier1/Borders.jsx b/.storybook/components/DesignTokens/Tier1/Borders.jsx index a3f17f8cc..dd283e373 100755 --- a/.storybook/components/DesignTokens/Tier1/Borders.jsx +++ b/.storybook/components/DesignTokens/Tier1/Borders.jsx @@ -6,6 +6,7 @@ import { TokenSpecimen } from '../../TokenSpecimen/TokenSpecimen'; export class Tier1Borders extends Component { render() { + // TODO: what are the width tokens to document? return (
diff --git a/.storybook/components/DesignTokens/Tier1/Colors.tsx b/.storybook/components/DesignTokens/Tier1/Colors.tsx index 551e4f0b9..eaed870e2 100755 --- a/.storybook/components/DesignTokens/Tier1/Colors.tsx +++ b/.storybook/components/DesignTokens/Tier1/Colors.tsx @@ -12,31 +12,59 @@ export const Tier1Colors = () => { figmaToken: figmaTokenHeader + '/' + name.slice(name.lastIndexOf('-') + 1), })); + + // filter down to the neutral tokens which were introduced or redefined in the new brand + const nonLegacyNeutralItems = getListItems( + 'eds-color-neutral', + 'neutral', + ).filter((item) => { + // TODO: remove this filter once the deprecated neutral tokens are deleted + return [ + '025', + '050', + '100', + '150', + '200', + '250', + '350', + '450', + '550', + '650', + '750', + '800', + '850', + '900', + '950', + ].includes(item.figmaToken.split('/')[1]); + }); return (
-
+
+
+
+
+
+
+
+
+
+
+
+ +
+
-
- -
-
- -
-
- - - - +
+
); diff --git a/.storybook/components/DesignTokens/Tier1/Layout.jsx b/.storybook/components/DesignTokens/Tier1/Layout.jsx deleted file mode 100755 index f9aeb8377..000000000 --- a/.storybook/components/DesignTokens/Tier1/Layout.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { Component } from 'react'; -import Grid from '../../../../src/components/Grid'; -import Section from '../../../../src/components/Section'; -import filterTokens from '../../../util/filterTokens'; -import { TokenSpecimen } from '../../TokenSpecimen/TokenSpecimen'; - -export class Tier1Layout extends Component { - render() { - return ( -
-
- - {filterTokens(`eds-l-`).map(function (listItem) { - return ( - - - - ); - })} - -
-
- ); - } -} diff --git a/.storybook/components/DesignTokens/Tier1/Sizes.jsx b/.storybook/components/DesignTokens/Tier1/Sizes.jsx index 00779da65..e4a2c58f7 100755 --- a/.storybook/components/DesignTokens/Tier1/Sizes.jsx +++ b/.storybook/components/DesignTokens/Tier1/Sizes.jsx @@ -7,49 +7,35 @@ import { TokenSpecimen } from '../../TokenSpecimen/TokenSpecimen'; export class Tier1Sizes extends Component { render() { return ( -
-
- - {filterTokens('eds-size-base').map((listItem) => ( - - - - ))} - -
-
- - {filterTokens(`eds-size`) - .filter((listItem) => !listItem.name.endsWith('base-unit')) - .map(function (listItem) { - return ( - - - - ); - })} - -
-
+
+ + {filterTokens(`eds-size`) + .filter((listItem) => !listItem.name.endsWith('base-unit')) + .map(function (listItem) { + return ( + + + + ); + })} + +
); } } diff --git a/.storybook/components/DesignTokens/Tier1/Tier1Tokens.stories.jsx b/.storybook/components/DesignTokens/Tier1/Tier1Tokens.stories.jsx index b62c75efe..c61ee8454 100755 --- a/.storybook/components/DesignTokens/Tier1/Tier1Tokens.stories.jsx +++ b/.storybook/components/DesignTokens/Tier1/Tier1Tokens.stories.jsx @@ -2,7 +2,6 @@ import React from 'react'; import { Tier1Animation } from './Animation'; import { Tier1Borders } from './Borders'; import { Tier1Colors } from './Colors'; -import { Tier1Layout } from './Layout'; import { Tier1Shadows } from './Shadows'; import { Tier1Sizes } from './Sizes'; @@ -22,6 +21,8 @@ export default { }, }; +// TODO: Tokens to document: eds-color-opacity + export const Colors = { render: () => , }; @@ -38,10 +39,6 @@ export const Sizes = { render: () => , }; -export const Layout = { - render: () => , -}; - export const Shadows = { render: () => , }; diff --git a/.storybook/components/DesignTokens/Tier2/Borders.stories.tsx b/.storybook/components/DesignTokens/Tier2/Borders.stories.tsx index 4d888b0e4..4a1e0fb3a 100755 --- a/.storybook/components/DesignTokens/Tier2/Borders.stories.tsx +++ b/.storybook/components/DesignTokens/Tier2/Borders.stories.tsx @@ -15,20 +15,21 @@ export default { }, }; -export const Borders: StoryObj = { +export const BorderRadii: StoryObj = { render: () => (
-
+
- {filterTokens(`eds-theme-border-width`).map(function (listItem) { + {filterTokens(`eds-theme-border-radius`).map(function (listItem) { return ( (
-
+
@@ -51,15 +54,34 @@ export const Text: StoryObj = { ), }; -export const Icon: StoryObj = { +export const IconUtility: StoryObj = { render: () => (
-
+
+
+
+ ), +}; + +export const BackgroundUtility: StoryObj = { + render: () => ( +
+
+
@@ -67,15 +89,34 @@ export const Icon: StoryObj = { ), }; -export const Background: StoryObj = { +export const BackgroundBrand: StoryObj = { render: () => (
-
+
{ + // remove legacy primary tokens + return item.name.indexOf('primary') === -1; + })} + /> +
+
+ ), +}; + +export const BorderUtility: StoryObj = { + render: () => ( +
+
+
@@ -83,15 +124,18 @@ export const Background: StoryObj = { ), }; -export const Border: StoryObj = { +export const BorderBrand: StoryObj = { render: () => (
-
+
{ + // remove legacy primary tokens + return item.name.indexOf('primary') === -1; })} />
diff --git a/.storybook/components/DesignTokens/Tier3/Colors.stories.tsx b/.storybook/components/DesignTokens/Tier3/Colors.stories.tsx deleted file mode 100755 index 186ef24fa..000000000 --- a/.storybook/components/DesignTokens/Tier3/Colors.stories.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import type { StoryObj } from '@storybook/react'; -import React from 'react'; -import Section from '../../../../src/components/Section'; -import filterTokens from '../../../util/filterTokens'; -import { ColorList } from '../../ColorList/ColorList'; - -export default { - title: 'Design Tokens/Tier 3: Component/Colors', - parameters: { - axe: { - // For documentation purposes only - skip: true, - }, - }, -}; - -const getListItems = ({ - filterTerm, - figmaTokenHeader, - tailwindClassHeader, - getTailwindClassPrefix = (stringMatch: string) => { - if (stringMatch.includes('background')) { - return 'bg'; - } else if (stringMatch.includes('border')) { - return 'border'; - } else { - return 'text'; - } - }, -}: { - filterTerm: string; - figmaTokenHeader: string; - tailwindClassHeader: string; - getTailwindClassPrefix?: (stringMatch: string) => string; -}) => - filterTokens(filterTerm).map(({ name, value }) => { - const specifier = name.slice( - name.indexOf(filterTerm) + filterTerm.length + 1, - ); - return { - name, - value, - figmaToken: figmaTokenHeader + '/' + specifier, - tailwindClass: - getTailwindClassPrefix(specifier) + - '-' + - tailwindClassHeader + - '-' + - specifier, - }; - }); - -export const Button: StoryObj = { - // story is too large to be snapped - // TODO: when button colors are more in sync with mocks, should be less colors so might fit then - parameters: { - chromatic: { - disableSnapshot: true, - }, - }, - render: () => ( -
-
- -
-
- ), -}; - -export const Checkbox: StoryObj = { - render: () => ( -
-
- -
-
- ), -}; - -export const DataBar: StoryObj = { - render: () => ( -
-
- -
-
- ), -}; - -export const Form: StoryObj = { - render: () => ( -
-
- -
-
- ), -}; - -export const Link: StoryObj = { - render: () => ( -
-
- -
-
- ), -}; - -export const Modal: StoryObj = { - render: () => ( -
-
- -
-
- ), -}; - -export const ProgressBar: StoryObj = { - render: () => ( -
-
- -
-
- ), -}; - -export const Toggle: StoryObj = { - render: () => ( -
-
- -
-
- ), -}; - -export const FocusRing: StoryObj = { - render: () => ( -
-
- '(outline/border)', - })} - /> -
-
- ), -}; diff --git a/.storybook/components/DesignTokens/Tier3/TypographyComponents.stories.tsx b/.storybook/components/DesignTokens/Tier3/TypographyComponents.stories.tsx deleted file mode 100755 index 42daeee6d..000000000 --- a/.storybook/components/DesignTokens/Tier3/TypographyComponents.stories.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import type { StoryObj } from '@storybook/react'; -import { at, capitalize, forEach, merge } from 'lodash'; - -import React from 'react'; -import Grid from '../../../../src/components/Grid'; -import Section from '../../../../src/components/Section'; -import breadcrumb from '../../../../src/design-tokens/tier-3-component/breadcrumb-typography.json'; -import button from '../../../../src/design-tokens/tier-3-component/buttons-typography.json'; -import form from '../../../../src/design-tokens/tier-3-component/forms-typography.json'; -import link from '../../../../src/design-tokens/tier-3-component/link-typography.json'; -import tab from '../../../../src/design-tokens/tier-3-component/tab-typography.json'; -import tag from '../../../../src/design-tokens/tier-3-component/tag-typography.json'; - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore importing of a legacy utility file results in some 'any's, which is acceptable for this docs page -import flatten from '../../../util/flattenToken'; -import { TokenSpecimen } from '../../TokenSpecimen/TokenSpecimen'; - -export default { - title: 'Design Tokens/Tier 3: Component', - parameters: { - axe: { - // For documentation purposes only - skip: true, - }, - }, -}; - -export const Typography: StoryObj = { - render: () => { - const values: { - [key: string]: { - [key: string]: string; - }; - } = {}; - const componentTypography = merge(breadcrumb, button, form, link, tab, tag); - forEach(at(componentTypography, 'eds.theme.typography')[0], (_, key) => { - values[key] = flatten( - at(componentTypography, `eds.theme.typography.${key}`)[0], - `eds-theme-typography-${key}-`, - ); - }); - - // Flatten all the tier 3 tokens, group them by component, and print specimens for the results - return ( -
- {Object.keys(values).map((section) => { - return ( -
- - {Object.keys(values[section]).map((usage) => { - return ( - - ); - })} - -
- ); - })} -
- ); - }, -}; diff --git a/.storybook/data/tokens.json b/.storybook/data/tokens.json index b17c491aa..0d9e84a52 100644 --- a/.storybook/data/tokens.json +++ b/.storybook/data/tokens.json @@ -799,7 +799,10 @@ "eds-size-10": "80", "eds-size-11": "88", "eds-size-12": "96", - "eds-size-20": "320", + "eds-size-20": "160", + "eds-size-24": "192", + "eds-size-32": "256", + "eds-size-40": "320", "eds-size-base-unit": "8", "eds-size-half": "4", "eds-size-quarter": "2", diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index eef5e0473..d237fd96d 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -11,14 +11,14 @@ import '../src/design-tokens/tier-1-definitions/fonts.css'; // Import storybook-specific CSS import './css/styleguide-only.css'; -import type { Preview, Story } from '@storybook/react'; +import type { Preview, StoryFn } from '@storybook/react'; import React from 'react'; import Theme from './Theme'; import { storybookViewports } from '../src/util/viewports'; export const decorators = [ - (Story: Story) => ( + (Story: StoryFn) => (
@@ -68,19 +68,6 @@ export const parameters: Preview['parameters'] = { }, backgrounds: { values: [ - // TODO: remove the following three when the last 1.0 components have been migrated - { - name: 'eds-color-neutral-white', - value: '#ffffff', - }, - { - name: 'eds-color-neutral-100', - value: '#f4f6f8', - }, - { - name: 'eds-color-neutral-700', - value: '#21272D', - }, { name: 'background-utility-default-high-emphasis', value: '#0F2163', diff --git a/src/components/Accordion/Accordion-v2.module.css b/src/components/Accordion/Accordion-v2.module.css deleted file mode 100644 index 09d38152e..000000000 --- a/src/components/Accordion/Accordion-v2.module.css +++ /dev/null @@ -1,137 +0,0 @@ -@import '../../design-tokens/mixins.css'; - -/*------------------------------------*\ - # ACCORDION -\*------------------------------------*/ -/** - * TODO: Icon inherits color from the surrounding text, but should use the matching -icon- tokens from below - */ - -/** - * Accordion Panel that expands and collapses. - * Reveals itself below the associated Accordion Button. - */ - .accordion-panel { - padding: 0 3.5rem 1rem 1rem; - - font: var(--eds-theme-typography-body-sm); - - &.accordion-panel--leading-icon { - padding-left: 3.5rem; - } - - color: var(--eds-theme-color-text-utility-default-primary); -} - -/** - * Accordion Button, wraps the heading and open indicator icon. - */ -.accordion-button { - display: flex; - justify-content: flex-start; - gap: 1rem; - width: 100%; - - border: 0; - text-align: left; - - border-radius: calc(var(--eds-theme-border-radius-objects-sm) * 1px); - - &:hover { - background-color: var(--eds-theme-color-background-utility-default-no-emphasis-hover); - } - - &:active { - background-color: var(--eds-theme-color-background-utility-default-no-emphasis-active); - } -} - -.accordion-button--empty { - pointer-events: none; -} - -/** - * Size variants - */ -.accordion-button--sm, -.accordion-button--md { - padding: 0.5rem 1rem; -} - -/** - * Expand more (chevron) icon indicates open or closed status. - * - * This non-rotated icon points down and represents closed status. - */ - .accordion-button__trailing-icon { - flex: 0 0 content; - transform: rotate(0); -} - -/** - * This rotated icon points up and represents open status. - */ - .accordion-button__trailing-icon--open { - transform: rotate(-180deg); -} - -/** - * Animates the icon rotation when opening and closing. - */ - .accordion-button > .accordion-button__trailing-icon { - transition: transform calc(var(--eds-anim-move-medium) * 1s) var(--eds-anim-ease); - - @media screen and (prefers-reduced-motion) { - transition: none; - } -} - -.accordion-panel--hidden { - padding: 0; -} - -.accordion-button__heading { - flex-grow: 2; - - & > * + * { - display: block; - } -} - -.accordion-button:focus-visible { - outline: 0.125rem solid var(--eds-theme-color-border-utility-focus) -} - -.accordion-button__leading-icon { - color: var(--eds-theme-color-icon-utility-default-secondary); - - /* Targeting NumberIcons and other images used in this specific context */ - & [role='img'] { - display: inline-flex; - } -} - -.accordion-button__title { - color: var(--eds-theme-color-text-utility-default-primary); -} - -.accordion-button__subtitle { - color: var(--eds-theme-color-text-utility-default-secondary); -} - - -.accordion-panel--md { - font: var(--eds-theme-typography-body-md); -} - -.accordion-panel--sm { - font: var(--eds-theme-typography-body-sm); -} - -/** - * Accordion Row houses one Accordion Button subcomponent and its relevant Accordion Panel subcomponent. - */ -.accordion-row { - border-bottom: calc(var(--eds-border-width-sm) * 1px) solid - var(--eds-theme-color-border-utility-default-low-emphasis); -} diff --git a/src/components/Accordion/Accordion-v2.stories.tsx b/src/components/Accordion/Accordion-v2.stories.tsx deleted file mode 100644 index e43e0ca5d..000000000 --- a/src/components/Accordion/Accordion-v2.stories.tsx +++ /dev/null @@ -1,412 +0,0 @@ -import type { StoryObj, Meta } from '@storybook/react'; -import React from 'react'; - -import { Accordion } from './Accordion-v2'; -import { chromaticViewports } from '../../util/viewports'; -import Icon from '../Icon'; -import { NumberIconV2 as NumberIcon } from '../NumberIcon'; -import Text from '../Text'; - -type Args = React.ComponentProps; - -export default { - title: 'Components/V2/Accordion', - component: Accordion, - parameters: { - layout: 'centered', - badges: ['intro-1.2', 'current-2.0'], - }, - args: { - headingAs: 'h2', - className: 'w-96', - children: ( - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - ), - }, - argTypes: { - children: { - control: { - type: null, - }, - }, - }, - decorators: [(Story) =>
{Story()}
], -} as Meta; - -type Story = StoryObj; - -export const Default: Story = {}; - -export const TitleAndSubtitle: Story = { - args: { - ...Default.args, - children: ( - - "Quam lacus maecenas nibh malesuada."} - title="Massa quam egestas massa." - > - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - ), - }, -}; - -export const HasLeadingIcon: Story = { - args: { - ...Default.args, - children: ( - - - } - subtitle="Quam lacus maecenas nibh malesuada." - title="Massa quam egestas massa." - > - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - ), - }, -}; - -/** - * You can add in `NumberIcon` or any other icon in the button for the accordion row. - */ -export const HasLeadingNumberIcon: Story = { - args: { - ...Default.args, - children: ( - - - } - subtitle="Quam lacus maecenas nibh malesuada." - title="Massa quam egestas massa." - > - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - ), - }, -}; - -/** - * Default `Accordion` using the `small` size. - */ -export const Small: Story = { - args: { - size: 'sm', - }, -}; - -/** - * This demonstrates how one can combine multiple `Accordion` rows, where any of the rows can - * be defaulted to open (using `defaultOpen`). - */ -export const Stacked: Story = { - args: { - children: ( - <> - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - ), - }, -}; - -export const StackedSmall: Story = { - args: { - ...Stacked.args, - size: 'sm', - }, -}; - -/** - * This demonstrates how to specify that a section is not currently expandable using `isExpandable`. - */ -export const EmptyStackedOpen: Story = { - args: { - children: ( - <> - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - ), - }, -}; - -export const StackedOpen: Story = { - args: { - children: ( - <> - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - ), - }, -}; - -export const StackedSmallOpen: Story = { - args: { - ...StackedOpen.args, - size: 'sm', - }, -}; - -/** - * - * This shows how to use a render prop for the row, to allow controlling render based on component state. - * - * **NOTE**: Visual regression testing unhelpful since story value is in interaction and as a code example. - */ -export const UsingRenderProp: Story = { - render: () => ( - - - {({ open }) => ( - <> - - Accordion Button {(open && 'open') || 'closed'} - - Accordion Panel - - )} - - - ), - parameters: { - chromatic: { disableSnapshot: true }, - }, -}; - -/** - * Although headings should provide limited text, we allow for text to span multiple lines, preserving - * the size of the state caret. - */ -export const WithLargeHeader: Story = { - parameters: { - chromatic: { - viewports: [chromaticViewports.ipadMini], - }, - }, - args: { - children: ( - - - Massa quam egestas massa. Massa quam egestas massa. Massa quam egestas - massa. Massa quam egestas massa. Massa quam egestas massa. - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - ), - }, -}; - -/** - * You can use other EDS components within the `Accordion.Button` to allow for custom, non-text headers. - * - * **Example**: using `Text` and `Icon` in the `Accordion.Button`. - */ -export const UsingComplexHeaders: Story = { - parameters: { - badges: ['intro-1.2', 'implementationExample', 'current-2.0'], - }, - args: { - children: ( - <> - - - - - Step 1 - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - - - - Step 2 - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - - - - ), - }, -}; diff --git a/src/components/Accordion/Accordion-v2.test.tsx b/src/components/Accordion/Accordion-v2.test.tsx deleted file mode 100644 index 4bc9444a2..000000000 --- a/src/components/Accordion/Accordion-v2.test.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { generateSnapshots } from '@chanzuckerberg/story-utils'; -import type { StoryFile } from '@storybook/testing-react'; -import { composeStories } from '@storybook/testing-react'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { Accordion } from './Accordion-v2'; -import * as stories from './Accordion-v2.stories'; - -const { Default } = composeStories(stories); - -describe('', () => { - generateSnapshots(stories as StoryFile); - - it('should open and close Accordion panel clicking Accordion button', async () => { - const user = userEvent.setup(); - render(); - expect(screen.queryByTestId('accordion-panel')).not.toBeInTheDocument(); - const accordionButton = screen.getByTestId('accordion-button'); - - await user.click(accordionButton); - expect(screen.getByTestId('accordion-panel')).toBeInTheDocument(); - - await user.click(accordionButton); - expect(screen.queryByTestId('accordion-panel')).not.toBeInTheDocument(); - }); - - it('should open and close Accordion panel with space and enter keys on the Accordion button', async () => { - const user = userEvent.setup(); - render(); - const accordionButton = screen.getByTestId('accordion-button'); - accordionButton.focus(); - - await user.keyboard(' '); - expect(screen.getByTestId('accordion-panel')).toBeInTheDocument(); - - await user.keyboard(' '); - expect(screen.queryByTestId('accordion-panel')).not.toBeInTheDocument(); - - await user.keyboard('{enter}'); - expect(screen.getByTestId('accordion-panel')).toBeInTheDocument(); - - await user.keyboard('{enter}'); - expect(screen.queryByTestId('accordion-panel')).not.toBeInTheDocument(); - }); - - it('should call onClose callback when accordion closes', async () => { - const user = userEvent.setup(); - const onClose = jest.fn(); - render( - - - - Accordion Button - - Accordion Panel - - , - ); - const accordionButton = screen.getByTestId('accordion-button'); - - await user.click(accordionButton); - expect(onClose).toHaveBeenCalledTimes(1); - }); - - it('should call onOpen callback when accordion opens', async () => { - const user = userEvent.setup(); - const onClose = jest.fn(); - const onOpen = jest.fn(); - render( - - - - Accordion Button - - Accordion Panel - - , - ); - const accordionButton = screen.getByRole('button'); - - await user.click(accordionButton); - expect(onOpen).toHaveBeenCalledTimes(1); - expect(onClose).not.toHaveBeenCalled(); - }); - - it('should not call onOpen callback when accordion opens on an empty row', async () => { - const user = userEvent.setup(); - const onClose = jest.fn(); - const onOpen = jest.fn(); - render( - - - - Accordion Button - - Accordion Panel - - , - ); - const accordionButton = screen.getByRole('button'); - - await user.click(accordionButton); - expect(onOpen).not.toHaveBeenCalled(); - expect(onClose).not.toHaveBeenCalled(); - }); -}); diff --git a/src/components/Accordion/Accordion-v2.tsx b/src/components/Accordion/Accordion-v2.tsx deleted file mode 100644 index 54e82dde0..000000000 --- a/src/components/Accordion/Accordion-v2.tsx +++ /dev/null @@ -1,305 +0,0 @@ -import { Disclosure } from '@headlessui/react'; -import clsx from 'clsx'; -import React, { createContext, useContext } from 'react'; -import type { ReactNode } from 'react'; -import { ENTER_KEYCODE, SPACEBAR_KEYCODE } from '../../util/keycodes'; -import type { Size } from '../../util/variant-types'; - -import Heading, { type HeadingElement } from '../Heading'; -import { type IconNameV2 as IconName, IconV2 as Icon } from '../Icon'; -import Text from '../Text'; - -import styles from './Accordion-v2.module.css'; - -type AccordionProps = { - // Component API - /** - * Child node(s) that can be nested inside component. - */ - children: ReactNode; - /** - * Additional classnames passed in for styling. - */ - className?: string; - // Design API - /** - * Used to specify which heading element should be rendered for each `Accordion.Title` child. - * - * **Default is `"h2"`**. - */ - headingAs: HeadingElement; - /** - * Various sizes supported by the `Accordion`. - * - * **Default is `"md"`**. - */ - size?: Extract; -}; - -type AccordionButtonProps = { - // Component API - /** - * Child node(s) that can be nested inside component. - */ - children?: ReactNode; - /** - * Additional classnames passed in for styling - */ - className?: string; - /** - * Callback for when accordion is closed. - */ - onClose?: () => void; - /** - * Callback for when according is opened. - */ - onOpen?: () => void; - // Design API - /** - * Used to specify which heading element should be rendered for the title. - * If provided, overrides parent headingAs prop. - */ - headingAs?: HeadingElement; - /** - * Icon to preceed the text in an accordion header - */ - leadingIcon?: ReactNode; - /** - * Secondary text used to describe the content in more detail - */ - subtitle?: ReactNode; - /** - * The title/heading of the component - */ - title?: string; - /** - * Icon override for component's expand/collapse indicator. - * - * **Default is `"expand-more"`**. - */ - trailingIcon?: Extract; -}; - -type AccordionPanelProps = { - /** - * Child node(s) that can be nested inside component. - */ - children: ReactNode; - /** - * Additional class names passed in for styling - */ - className?: string; -}; - -type AccordionRowProps = { - /** - * Child node(s) that can be nested inside component. - */ - children: ReactNode | (({ open }: { open: boolean }) => ReactNode); - /** - * Additional class names passed in for styling. - */ - className?: string; - /** - * Whether panel is expanded by default. - */ - defaultOpen?: boolean; - /** - * Whether the row can show expandable content - */ - isExpandable?: boolean; - /** - * Whether the row has a leading icon on the row's trigger - */ - hasLeadingIcon?: boolean; -}; - -const AccordionContext = createContext<{ - headingAs: HeadingElement; - size?: AccordionProps['size']; -}>({ - headingAs: 'h2', -}); - -const AccordionRowContext = createContext< - Pick ->({ - isExpandable: true, - hasLeadingIcon: false, -}); - -/** - * `import {Accordion} from "@chanzuckerberg/eds;` - * - * Displays one or more headers stacked on top of one another that can reveal or hide associated content. - * This component is based on the [Disclosure](https://headlessui.com/react/disclosure) component, provided by HeadlessUI. - * - * @see https://headlessui.com/react/disclosure - * - */ -export const Accordion = ({ - children, - className, - headingAs, - size = 'md', - ...other -}: AccordionProps) => { - return ( - -
- {children} -
-
- ); -}; - -const AccordionButton = ({ - children, - className, - headingAs, - leadingIcon, - title, - trailingIcon = 'chevron-down', - subtitle, - onClose, - onOpen, - ...other -}: AccordionButtonProps) => { - const { headingAs: contextHeadingAs, size } = useContext(AccordionContext); - - const { isExpandable } = useContext(AccordionRowContext); - - const componentClassName = clsx( - styles['accordion-button'], - size && styles[`accordion-button--${size}`], - !isExpandable && styles['accordion-button--empty'], - className, - ); - - return ( - - {({ open }) => ( - - )} - - ); -}; - -const AccordionPanel = ({ - className, - children, - ...other -}: AccordionPanelProps) => { - const { size } = useContext(AccordionContext); - const { isExpandable, hasLeadingIcon } = useContext(AccordionRowContext); - - const componentClassName = clsx( - styles['accordion-panel'], - size === 'sm' && styles['accordion-panel--sm'], - !isExpandable && styles['accordion-panel--hidden'], - hasLeadingIcon && styles['accordion-panel--leading-icon'], - className, - ); - - return ( - - {isExpandable && children} - - ); -}; - -const AccordionRow = ({ - className, - defaultOpen, - children, - isExpandable = true, - hasLeadingIcon, - ...other -}: AccordionRowProps) => { - const componentClassName = clsx(styles['accordion-row'], className); - return ( - - - {({ open }) => ( -
- {typeof children === 'function' ? children({ open }) : children} -
- )} -
-
- ); -}; - -Accordion.displayName = 'Accordion'; -AccordionButton.displayName = 'Accordion.Button'; -AccordionPanel.displayName = 'Accordion.Panel'; -AccordionRow.displayName = 'Accordion.Row'; - -Accordion.Button = AccordionButton; -Accordion.Panel = AccordionPanel; -Accordion.Row = AccordionRow; diff --git a/src/components/Accordion/Accordion.module.css b/src/components/Accordion/Accordion.module.css index a4a532e2e..571720ea1 100644 --- a/src/components/Accordion/Accordion.module.css +++ b/src/components/Accordion/Accordion.module.css @@ -3,16 +3,24 @@ /*------------------------------------*\ # ACCORDION \*------------------------------------*/ - /** - * Accordion is one or multiple interactive headings that reveal or hide associated content. - * - * Outline variant. + * TODO: Icon inherits color from the surrounding text, but should use the matching -icon- tokens from below */ -.accordion--outline { - border: var(--eds-theme-color-border-neutral-subtle) solid - calc(var(--eds-border-width-sm) * 1px); - border-radius: calc(var(--eds-border-radius-md) * 1px); + +/** + * Accordion Panel that expands and collapses. + * Reveals itself below the associated Accordion Button. + */ + .accordion-panel { + padding: 0 calc(var(--eds-size-7) / 16 * 1rem) calc(var(--eds-size-2) / 16 * 1rem) calc(var(--eds-size-2) / 16 * 1rem); + + font: var(--eds-theme-typography-body-sm); + + &.accordion-panel--leading-icon { + padding-left: calc(var(--eds-size-7) / 16 * 1rem); + } + + color: var(--eds-theme-color-text-utility-default-primary); } /** @@ -20,57 +28,34 @@ */ .accordion-button { display: flex; - justify-content: space-between; + justify-content: flex-start; + gap: calc(var(--eds-size-2) / 16 * 1rem); + width: 100%; border: 0; - border-radius: 0; - - padding: calc(var(--eds-size-1-and-half) / 16 * 1rem) calc(var(--eds-size-1) / 16 * 1rem); - height: unset; - min-height: 3.375rem; - text-align: left; -} - -.accordion-button--empty { - pointer-events: none; -} -/** - * Small variant. - */ -.accordion-button--sm { - padding: calc(var(--eds-size-half) / 16 * 1rem) calc(var(--eds-size-1) / 16 * 1rem); - min-height: 2.25rem; -} + border-radius: calc(var(--eds-theme-border-radius-objects-sm) * 1px); -/** - * Outline variant. - */ -.accordion-button--outline { - padding: calc(var(--eds-size-1-and-half) / 16 * 1rem) calc(var(--eds-size-3) / 16 * 1rem); -} + &:hover { + background-color: var(--eds-theme-color-background-utility-default-no-emphasis-hover); + } -/** - * Small outline variant. - */ -.accordion-button--sm.accordion-button--outline { - padding: calc(var(--eds-size-half) / 16 * 1rem) calc(var(--eds-size-2) / 16 * 1rem); + &:active { + background-color: var(--eds-theme-color-background-utility-default-no-emphasis-active); + } } -/** - * The heading text. - */ -.accordion-button__heading { - font: var(--eds-theme-typography-body-sm); - color: var(--eds-theme-color-text-neutral-strong); +.accordion-button--empty { + pointer-events: none; } /** - * Small variant of the heading text. + * Size variants */ -.accordion-button__heading--sm { - font: var(--eds-theme-typography-body-xs); +.accordion-button--sm, +.accordion-button--md { + padding: calc(var(--eds-size-1) / 16 * 1rem) calc(var(--eds-size-2) / 16 * 1rem); } /** @@ -78,21 +63,22 @@ * * This non-rotated icon points down and represents closed status. */ -.accordion-button__icon { + .accordion-button__trailing-icon { flex: 0 0 content; transform: rotate(0); } + /** * This rotated icon points up and represents open status. */ -.accordion-button__icon--open { - transform: rotate(180deg); + .accordion-button__trailing-icon--open { + transform: rotate(-180deg); } /** * Animates the icon rotation when opening and closing. */ -.accordion-button > .accordion-button__icon { + .accordion-button > .accordion-button__trailing-icon { transition: transform calc(var(--eds-anim-move-medium) * 1s) var(--eds-anim-ease); @media screen and (prefers-reduced-motion) { @@ -100,44 +86,46 @@ } } -/** - * Accordion Panel that expands and collapses. - * Reveals itself below the associated Accordion Button. - */ -.accordion-panel { - padding-left: calc(var(--eds-size-1) / 16 * 1rem); - padding-right: calc(var(--eds-size-3) / 16 * 1rem); - padding-bottom: 1.625rem; +.accordion-panel--hidden { + padding: 0; +} - font: var(--eds-theme-typography-body-sm); - color: var(--eds-theme-color-text-neutral-strong); +.accordion-button__heading { + flex-grow: 2; + + & > * + * { + display: block; + } } -.accordion-panel--hidden { - padding: 0; +.accordion-button:focus-visible { + outline: calc(var(--eds-size-quarter) / 16 * 1rem)) solid var(--eds-theme-color-border-utility-focus); } -/** - * Small variant. - */ -.accordion-panel--sm { - font: var(--eds-theme-typography-body-xs); +.accordion-button__leading-icon { + color: var(--eds-theme-color-icon-utility-default-secondary); + + /* Targeting NumberIcons and other images used in this specific context */ + & [role='img'] { + display: inline-flex; + } } -/** - * Outline variant. - */ -.accordion-panel--outline { - padding-left: calc(var(--eds-size-3) / 16 * 1rem); - padding-right: calc(var(--eds-size-5) / 16 * 1rem); +.accordion-button__title { + color: var(--eds-theme-color-text-utility-default-primary); } -/** - * Small outline variant. - */ -.accordion-panel--sm.accordion-panel--outline { - padding-left: calc(var(--eds-size-2) / 16 * 1rem); - padding-right: calc(var(--eds-size-4) / 16 * 1rem); +.accordion-button__subtitle { + color: var(--eds-theme-color-text-utility-default-secondary); +} + + +.accordion-panel--md { + font: var(--eds-theme-typography-body-md); +} + +.accordion-panel--sm { + font: var(--eds-theme-typography-body-sm); } /** @@ -145,13 +133,5 @@ */ .accordion-row { border-bottom: calc(var(--eds-border-width-sm) * 1px) solid - var(--eds-theme-color-border-neutral-subtle); -} - -/** - * Variant with outline. - * Removes bottom border on the last Accordion Row so there aren't two borders at the bottom of the Accordion. - */ -.accordion-row--outline:last-of-type { - border-bottom: 0; + var(--eds-theme-color-border-utility-default-low-emphasis); } diff --git a/src/components/Accordion/Accordion.stories.tsx b/src/components/Accordion/Accordion.stories.tsx index 9aa97c1c1..b84ff3c26 100644 --- a/src/components/Accordion/Accordion.stories.tsx +++ b/src/components/Accordion/Accordion.stories.tsx @@ -14,15 +14,17 @@ export default { component: Accordion, parameters: { layout: 'centered', - badges: ['intro-1.2'], + badges: ['intro-1.2', 'current-2.0'], }, args: { headingAs: 'h2', + className: 'w-96', children: ( - - Massa quam egestas massa. - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At @@ -39,13 +41,90 @@ export default { }, }, }, - decorators: [(Story) =>
{Story()}
], + decorators: [(Story) =>
{Story()}
], } as Meta; type Story = StoryObj; export const Default: Story = {}; +export const TitleAndSubtitle: Story = { + args: { + ...Default.args, + children: ( + + "Quam lacus maecenas nibh malesuada."} + title="Massa quam egestas massa." + > + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, + massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At + tristique et ullamcorper rhoncus amet pharetra aliquet tortor. + Suscipit dui, nunc sit dui tellus massa laoreet tellus. + + + ), + }, +}; + +export const HasLeadingIcon: Story = { + args: { + ...Default.args, + children: ( + + + } + subtitle="Quam lacus maecenas nibh malesuada." + title="Massa quam egestas massa." + > + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, + massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At + tristique et ullamcorper rhoncus amet pharetra aliquet tortor. + Suscipit dui, nunc sit dui tellus massa laoreet tellus. + + + ), + }, +}; + +/** + * You can add in `NumberIcon` or any other icon in the button for the accordion row. + */ +export const HasLeadingNumberIcon: Story = { + args: { + ...Default.args, + children: ( + + + } + subtitle="Quam lacus maecenas nibh malesuada." + title="Massa quam egestas massa." + > + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, + massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At + tristique et ullamcorper rhoncus amet pharetra aliquet tortor. + Suscipit dui, nunc sit dui tellus massa laoreet tellus. + + + ), + }, +}; + /** * Default `Accordion` using the `small` size. */ @@ -64,9 +143,10 @@ export const Stacked: Story = { children: ( <> - - Massa quam egestas massa. - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At @@ -75,9 +155,10 @@ export const Stacked: Story = { - - Massa quam egestas massa. - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At @@ -86,9 +167,10 @@ export const Stacked: Story = { - - Massa quam egestas massa. - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At @@ -97,9 +179,10 @@ export const Stacked: Story = { - - Massa quam egestas massa. - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At @@ -119,21 +202,6 @@ export const StackedSmall: Story = { }, }; -export const StackedOutline: Story = { - args: { - ...Stacked.args, - hasOutline: true, - }, -}; - -export const StackedSmallOutline: Story = { - args: { - ...Stacked.args, - size: 'sm', - hasOutline: true, - }, -}; - /** * This demonstrates how to specify that a section is not currently expandable using `isExpandable`. */ @@ -142,7 +210,7 @@ export const EmptyStackedOpen: Story = { children: ( <> - Massa quam egestas massa. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At @@ -151,7 +219,7 @@ export const EmptyStackedOpen: Story = { - Massa quam egestas massa. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At @@ -160,7 +228,7 @@ export const EmptyStackedOpen: Story = { - Massa quam egestas massa. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At @@ -169,7 +237,7 @@ export const EmptyStackedOpen: Story = { - Massa quam egestas massa. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At @@ -187,7 +255,7 @@ export const StackedOpen: Story = { children: ( <> - Massa quam egestas massa. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At @@ -196,7 +264,7 @@ export const StackedOpen: Story = { - Massa quam egestas massa. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At @@ -205,7 +273,7 @@ export const StackedOpen: Story = { - Massa quam egestas massa. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At @@ -214,7 +282,7 @@ export const StackedOpen: Story = { - Massa quam egestas massa. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At @@ -234,21 +302,6 @@ export const StackedSmallOpen: Story = { }, }; -export const StackedOutlineOpen: Story = { - args: { - ...StackedOpen.args, - hasOutline: true, - }, -}; - -export const StackedSmallOutlineOpen: Story = { - args: { - ...StackedOpen.args, - size: 'sm', - hasOutline: true, - }, -}; - /** * * This shows how to use a render prop for the row, to allow controlling render based on component state. @@ -310,7 +363,7 @@ export const WithLargeHeader: Story = { */ export const UsingComplexHeaders: Story = { parameters: { - badges: ['intro-1.2', 'implementationExample'], + badges: ['intro-1.2', 'implementationExample', 'current-2.0'], }, args: { children: ( @@ -320,7 +373,7 @@ export const UsingComplexHeaders: Story = { @@ -339,7 +392,7 @@ export const UsingComplexHeaders: Story = { @@ -357,48 +410,3 @@ export const UsingComplexHeaders: Story = { ), }, }; - -/** - * You can use other EDS components within the `Accordion.Button` to allow for custom, non-text headers. - * - * **Example**: using `Text` and `NumberIcon` in the `Accordion.Button`. - */ -export const UsingNumberIconInHeaders: Story = { - parameters: { - badges: ['intro-1.2', 'implementationExample'], - }, - args: { - children: ( - <> - - -
- - Step 1 -
-
- - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - -
- - -
- - Step 2 -
-
- - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, - massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At - tristique et ullamcorper rhoncus amet pharetra aliquet tortor. - Suscipit dui, nunc sit dui tellus massa laoreet tellus. - -
- - ), - }, -}; diff --git a/src/components/Accordion/Accordion.tsx b/src/components/Accordion/Accordion.tsx index c2f5d0900..9c7f10f57 100644 --- a/src/components/Accordion/Accordion.tsx +++ b/src/components/Accordion/Accordion.tsx @@ -4,12 +4,15 @@ import React, { createContext, useContext } from 'react'; import type { ReactNode } from 'react'; import { ENTER_KEYCODE, SPACEBAR_KEYCODE } from '../../util/keycodes'; import type { Size } from '../../util/variant-types'; -import Button from '../Button'; + import Heading, { type HeadingElement } from '../Heading'; import Icon, { type IconName } from '../Icon'; +import Text from '../Text'; + import styles from './Accordion.module.css'; type AccordionProps = { + // Component API /** * Child node(s) that can be nested inside component. */ @@ -18,12 +21,7 @@ type AccordionProps = { * Additional classnames passed in for styling. */ className?: string; - /** - * Outline variant adds adjusts the `Accordion` style by defining a containing border and other layout adjustments. - * - * **Default is `false`**. - */ - hasOutline?: boolean; + // Design API /** * Used to specify which heading element should be rendered for each `Accordion.Title` child. * @@ -39,33 +37,47 @@ type AccordionProps = { }; type AccordionButtonProps = { + // Component API /** * Child node(s) that can be nested inside component. */ - children: ReactNode; + children?: ReactNode; /** * Additional classnames passed in for styling */ className?: string; /** - * Icon override for component. - * - * **Default is `"expand-more"`**. + * Callback for when accordion is closed. */ - icon?: Extract; + onClose?: () => void; + /** + * Callback for when according is opened. + */ + onOpen?: () => void; + // Design API /** * Used to specify which heading element should be rendered for the title. * If provided, overrides parent headingAs prop. */ headingAs?: HeadingElement; /** - * Callback for when accordion is closed. + * Icon to preceed the text in an accordion header */ - onClose?: () => void; + leadingIcon?: ReactNode; /** - * Callback for when according is opened. + * Secondary text used to describe the content in more detail */ - onOpen?: () => void; + subtitle?: ReactNode; + /** + * The title/heading of the component + */ + title?: string; + /** + * Icon override for component's expand/collapse indicator. + * + * **Default is `"expand-more"`**. + */ + trailingIcon?: Extract; }; type AccordionPanelProps = { @@ -74,7 +86,7 @@ type AccordionPanelProps = { */ children: ReactNode; /** - * Additional classnames passed in for styling + * Additional class names passed in for styling */ className?: string; }; @@ -85,7 +97,7 @@ type AccordionRowProps = { */ children: ReactNode | (({ open }: { open: boolean }) => ReactNode); /** - * Additional classnames passed in for styling. + * Additional class names passed in for styling. */ className?: string; /** @@ -96,18 +108,24 @@ type AccordionRowProps = { * Whether the row can show expandable content */ isExpandable?: boolean; + /** + * Whether the row has a leading icon on the row's trigger + */ + hasLeadingIcon?: boolean; }; const AccordionContext = createContext<{ headingAs: HeadingElement; - hasOutline?: AccordionProps['hasOutline']; size?: AccordionProps['size']; }>({ headingAs: 'h2', }); -const AccordionRowContext = createContext<{ isExpandable?: boolean }>({ +const AccordionRowContext = createContext< + Pick +>({ isExpandable: true, + hasLeadingIcon: false, }); /** @@ -122,18 +140,13 @@ const AccordionRowContext = createContext<{ isExpandable?: boolean }>({ export const Accordion = ({ children, className, - hasOutline, headingAs, size = 'md', ...other }: AccordionProps) => { - const componentClassName = clsx( - hasOutline && styles['accordion--outline'], - className, - ); return ( - -
+ +
{children}
@@ -144,38 +157,30 @@ const AccordionButton = ({ children, className, headingAs, - icon = 'expand-more', + leadingIcon, + title, + trailingIcon = 'chevron-down', + subtitle, onClose, onOpen, ...other }: AccordionButtonProps) => { - const { - hasOutline, - headingAs: contextHeadingAs, - size, - } = useContext(AccordionContext); + const { headingAs: contextHeadingAs, size } = useContext(AccordionContext); const { isExpandable } = useContext(AccordionRowContext); const componentClassName = clsx( styles['accordion-button'], - size === 'sm' && styles['accordion-button--sm'], - hasOutline && styles['accordion-button--outline'], + size && styles[`accordion-button--${size}`], !isExpandable && styles['accordion-button--empty'], className, ); - const headingClassName = clsx( - styles['accordion-button__heading'], - size === 'sm' && styles['accordion-button__heading--sm'], - ); - return ( {({ open }) => ( - + )} ); @@ -227,14 +255,14 @@ const AccordionPanel = ({ children, ...other }: AccordionPanelProps) => { - const { hasOutline, size } = useContext(AccordionContext); - const { isExpandable } = useContext(AccordionRowContext); + const { size } = useContext(AccordionContext); + const { isExpandable, hasLeadingIcon } = useContext(AccordionRowContext); const componentClassName = clsx( styles['accordion-panel'], size === 'sm' && styles['accordion-panel--sm'], - hasOutline && styles['accordion-panel--outline'], !isExpandable && styles['accordion-panel--hidden'], + hasLeadingIcon && styles['accordion-panel--leading-icon'], className, ); @@ -250,16 +278,12 @@ const AccordionRow = ({ defaultOpen, children, isExpandable = true, + hasLeadingIcon, ...other }: AccordionRowProps) => { - const { hasOutline } = useContext(AccordionContext); - const componentClassName = clsx( - styles['accordion-row'], - hasOutline && styles['accordion-row--outline'], - className, - ); + const componentClassName = clsx(styles['accordion-row'], className); return ( - + {({ open }) => (
diff --git a/src/components/Accordion/__snapshots__/Accordion-v2.test.tsx.snap b/src/components/Accordion/__snapshots__/Accordion-v2.test.tsx.snap deleted file mode 100644 index fe16b5aa8..000000000 --- a/src/components/Accordion/__snapshots__/Accordion-v2.test.tsx.snap +++ /dev/null @@ -1,1437 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` Default story renders snapshot 1`] = ` -
-
-
- -
-
-
-`; - -exports[` EmptyStackedOpen story renders snapshot 1`] = ` -
-
-
- -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. -
-
-
-
-`; - -exports[` HasLeadingIcon story renders snapshot 1`] = ` -
-
-
- -
-
-
-`; - -exports[` HasLeadingNumberIcon story renders snapshot 1`] = ` -
-
-
- -
-
-
-`; - -exports[` Small story renders snapshot 1`] = ` -
-
-
- -
-
-
-`; - -exports[` Stacked story renders snapshot 1`] = ` -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. -
-
-
- -
-
- -
-
- -
-
-
-`; - -exports[` StackedOpen story renders snapshot 1`] = ` -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. -
-
-
-
-`; - -exports[` StackedSmall story renders snapshot 1`] = ` -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. -
-
-
- -
-
- -
-
- -
-
-
-`; - -exports[` StackedSmallOpen story renders snapshot 1`] = ` -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. -
-
-
-
-`; - -exports[` TitleAndSubtitle story renders snapshot 1`] = ` -
-
-
- -
-
-
-`; - -exports[` UsingComplexHeaders story renders snapshot 1`] = ` -
-
-
- -
-
- -
-
-
-`; - -exports[` UsingRenderProp story renders snapshot 1`] = ` -
-
-
- -
-
-
-`; - -exports[` WithLargeHeader story renders snapshot 1`] = ` -
-
-
- -
-
-
-`; diff --git a/src/components/Accordion/__snapshots__/Accordion.test.tsx.snap b/src/components/Accordion/__snapshots__/Accordion.test.tsx.snap index 8fd60bd3c..fe16b5aa8 100644 --- a/src/components/Accordion/__snapshots__/Accordion.test.tsx.snap +++ b/src/components/Accordion/__snapshots__/Accordion.test.tsx.snap @@ -2,35 +2,39 @@ exports[` Default story renders snapshot 1`] = `