From ea3238bd1e43ae51e0412503db0ecc3d80446bde Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 21 Mar 2025 13:11:06 -0400 Subject: [PATCH 01/38] added aria-expanded and aria-controls to Disclosure --- .../src/Disclosure/DisclosureBody/index.tsx | 2 ++ packages/gamut/src/Disclosure/index.tsx | 28 +++++++++++-------- packages/gamut/src/Disclosure/types.ts | 5 ++++ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/gamut/src/Disclosure/DisclosureBody/index.tsx b/packages/gamut/src/Disclosure/DisclosureBody/index.tsx index 587bb9e651a..c129736e4b2 100644 --- a/packages/gamut/src/Disclosure/DisclosureBody/index.tsx +++ b/packages/gamut/src/Disclosure/DisclosureBody/index.tsx @@ -13,6 +13,7 @@ export const DisclosureBody: React.FC = ({ ctaText, hasPanelBg = false, href, + id, spacing = 'normal', }) => { const buttonRequirements = ctaText && ctaCallback; @@ -26,6 +27,7 @@ export const DisclosureBody: React.FC = ({ mb={verticalSpacing} mt={4} mx={horizontalSpacing} + id={id} > {body} diff --git a/packages/gamut/src/Disclosure/index.tsx b/packages/gamut/src/Disclosure/index.tsx index eae846c22b2..08c589f572f 100644 --- a/packages/gamut/src/Disclosure/index.tsx +++ b/packages/gamut/src/Disclosure/index.tsx @@ -1,5 +1,5 @@ import { AnimatePresence } from 'framer-motion'; -import { useState } from 'react'; +import { useId, useState } from 'react'; import * as React from 'react'; import { ExpandInCollapseOut } from '../Animation'; @@ -29,36 +29,40 @@ export const Disclosure: React.FC = ({ variant, }) => { const [isExpanded, setIsExpanded] = useState(initiallyExpanded); + const bodyId = useId(); return ( onClick?.()} - as={isListItem ? 'li' : undefined} + variant={variant} > {isExpanded && ( )} diff --git a/packages/gamut/src/Disclosure/types.ts b/packages/gamut/src/Disclosure/types.ts index 62654c140e7..7d8db9fa626 100644 --- a/packages/gamut/src/Disclosure/types.ts +++ b/packages/gamut/src/Disclosure/types.ts @@ -52,6 +52,11 @@ export interface DisclosureBodyProps extends DisclosureBodyWrapperStyles { */ ctaText?: string; href?: string; + /** + * This `id` is used to link the DisclosureButton to the DisclosureBody. + * It is needed for the `aria-controls` attribute to work properly for accessibility. + */ + id: string; /** * Determines the size of the heading text and the space between text in the body. */ From c1991ca9d38c4c751dc9c9c3972a89902dca2927 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 21 Mar 2025 13:46:08 -0400 Subject: [PATCH 02/38] update infotip aria attributes --- packages/gamut/src/Tip/InfoTip/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/gamut/src/Tip/InfoTip/index.tsx b/packages/gamut/src/Tip/InfoTip/index.tsx index 61eb5095a97..fc51cd68b1d 100644 --- a/packages/gamut/src/Tip/InfoTip/index.tsx +++ b/packages/gamut/src/Tip/InfoTip/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useId, useRef, useState } from 'react'; import { FloatingTip } from '../shared/FloatingTip'; import { InlineTip } from '../shared/InlineTip'; @@ -88,6 +88,8 @@ export const InfoTip: React.FC = ({ const Tip = loaded && isFloating ? FloatingTip : InlineTip; + const textId = useId(); + const tipProps = { alignment, escapeKeyPressHandler, @@ -101,6 +103,7 @@ export const InfoTip: React.FC = ({ {!isTipHidden ? info : `\xa0`} @@ -109,6 +112,8 @@ export const InfoTip: React.FC = ({ const tip = ( clickHandler()} From b488938119400b4f8cf859fd80f4093d6b752037 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Mon, 24 Mar 2025 16:49:18 -0400 Subject: [PATCH 03/38] added controls to listrow and expandcontrol --- .../gamut/src/DataList/Controls/ExpandControl.tsx | 1 + .../gamut/src/DataList/Tables/Rows/TableRow.tsx | 1 + packages/gamut/src/List/ListRow.tsx | 13 +++++++++---- .../Organisms/Lists & Tables/List/List.stories.tsx | 7 +++++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/gamut/src/DataList/Controls/ExpandControl.tsx b/packages/gamut/src/DataList/Controls/ExpandControl.tsx index 657fc45c57e..51c7e25aa26 100644 --- a/packages/gamut/src/DataList/Controls/ExpandControl.tsx +++ b/packages/gamut/src/DataList/Controls/ExpandControl.tsx @@ -32,6 +32,7 @@ export const ExpandControl: React.FC = ({ }} aria-label={`Expand ${id} Row`} aria-expanded={expanded} + aria-controls={id} > diff --git a/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx b/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx index 38784942c45..0db757f2b80 100644 --- a/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx +++ b/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx @@ -70,6 +70,7 @@ export const TableRow: DataRow = ({ return ( >> { header?: boolean; + /** Used to link expandable content with the component that does the expanding, i.e. it's used to set the value for aria-controls */ + id?: string; /** This is an internal prop that is largely only used for the DataTable component */ numOfColumns?: number; /** This is an internal prop that is largely only used for the DataTable component */ @@ -45,12 +47,13 @@ const DivExpand = styled(motion.div)(expandStyles); const TDExpand = styled(motion.td)(expandStyles); const ExpandInCollapseOut: React.FC< - WithChildrenProp & { as: 'td' | 'div' } -> = ({ as, children }) => { + WithChildrenProp & { as: 'td' | 'div', id: string } +> = ({ as, children, id }) => { const ResponsiveExpand = as === 'td' ? TDExpand : DivExpand; return ( ( ( { + id, children, expanded, expandedRowAriaLabel, @@ -101,6 +105,7 @@ export const ListRow = forwardRef( ( {content} {expanded && ( - - + + {renderExpanded?.()} diff --git a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx index 8133db2e641..7388813faa6 100644 --- a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx +++ b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx @@ -650,7 +650,8 @@ const ExpandableButtonClickRow: React.FC<{ } + renderExpanded={() => } > @@ -699,7 +700,9 @@ export const ExpandableRowClick: React.FC = ({ expanded={isExpanded} key={key} onClick={() => setExpanded(!isExpanded)} - renderExpanded={() => } + id={`${name}-row`} + renderExpanded={() => } > From e7ba3babe67c0e4bcb3b0f30ae7d4907cd51ef9d Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Mon, 24 Mar 2025 16:50:24 -0400 Subject: [PATCH 04/38] formatted --- packages/gamut/src/List/ListRow.tsx | 4 ++-- .../src/lib/Organisms/Lists & Tables/List/List.stories.tsx | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/gamut/src/List/ListRow.tsx b/packages/gamut/src/List/ListRow.tsx index 715fc410c2f..77538325816 100644 --- a/packages/gamut/src/List/ListRow.tsx +++ b/packages/gamut/src/List/ListRow.tsx @@ -47,7 +47,7 @@ const DivExpand = styled(motion.div)(expandStyles); const TDExpand = styled(motion.td)(expandStyles); const ExpandInCollapseOut: React.FC< - WithChildrenProp & { as: 'td' | 'div', id: string } + WithChildrenProp & { as: 'td' | 'div'; id: string } > = ({ as, children, id }) => { const ResponsiveExpand = as === 'td' ? TDExpand : DivExpand; @@ -147,7 +147,7 @@ export const ListRow = forwardRef( {expanded && ( - + {renderExpanded?.()} diff --git a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx index 7388813faa6..2cf39647e17 100644 --- a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx +++ b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx @@ -650,8 +650,7 @@ const ExpandableButtonClickRow: React.FC<{ } + renderExpanded={() => } > @@ -701,8 +700,7 @@ export const ExpandableRowClick: React.FC = ({ key={key} onClick={() => setExpanded(!isExpanded)} id={`${name}-row`} - renderExpanded={() => } + renderExpanded={() => } > From 375c843802cafd206e3634fd4fa4c2588cbf0735 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Thu, 27 Mar 2025 15:29:04 -0400 Subject: [PATCH 05/38] fix list story --- .../Lists & Tables/List/List.stories.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx index 2cf39647e17..fab650a51bb 100644 --- a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx +++ b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx @@ -1,5 +1,6 @@ import { Box, + ExpandControl, FillButton, FlexBox, IconButton, @@ -648,6 +649,7 @@ const ExpandableButtonClickRow: React.FC<{ return ( } @@ -655,11 +657,12 @@ const ExpandableButtonClickRow: React.FC<{ Hail - setExpanded(!isExpanded)}> - - - - + setExpanded(!isExpanded)} + id={`${name}-${role}-${ship}`} + disabled={false} + /> ); @@ -699,7 +702,7 @@ export const ExpandableRowClick: React.FC = ({ expanded={isExpanded} key={key} onClick={() => setExpanded(!isExpanded)} - id={`${name}-row`} + id={`${name}-${role}-${ship}`} renderExpanded={() => } > From c5c7894113fad679a781c7531dccbcbbf1915d6c Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Thu, 27 Mar 2025 16:08:38 -0400 Subject: [PATCH 06/38] remove minichevron icon from list stores --- .../src/lib/Organisms/Lists & Tables/List/List.stories.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx index fab650a51bb..324141b65e7 100644 --- a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx +++ b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx @@ -15,7 +15,6 @@ import { import { ArrowChevronDownIcon, HouseEntranceIcon, - MiniChevronDownIcon, MiniDeleteIcon, MiniKebabMenuIcon, StarIcon, From af644bdca48e4beb8a5fca745fb2c45523bbc417 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Thu, 27 Mar 2025 16:09:11 -0400 Subject: [PATCH 07/38] updated drawer and flyout --- packages/gamut/src/Drawer/index.tsx | 10 +++++++-- packages/gamut/src/Flyout/index.tsx | 11 ++++++++-- .../src/lib/Atoms/Drawer/Drawer.mdx | 22 +++++++++++++++++++ .../src/lib/Atoms/Drawer/Drawer.stories.tsx | 7 ++++-- .../src/lib/Molecules/Flyout/Flyout.mdx | 20 +++++++++++++++++ .../lib/Molecules/Flyout/Flyout.stories.tsx | 10 +++++++-- 6 files changed, 72 insertions(+), 8 deletions(-) diff --git a/packages/gamut/src/Drawer/index.tsx b/packages/gamut/src/Drawer/index.tsx index 36796f6078e..d5ee27e5199 100644 --- a/packages/gamut/src/Drawer/index.tsx +++ b/packages/gamut/src/Drawer/index.tsx @@ -20,12 +20,18 @@ export interface DrawerProps extends Omit { * Which edge of the drawer content container is aligned to during the animation. */ alignContentContainer?: 'left' | 'right'; + /** + * This `id` is used to link the element that expands the Drawer to the Drawer itself. + * It is needed for the `aria-controls` attribute to work properly for accessibility. + */ + id: string; } export const Drawer: React.FC = ({ + alignContentContainer = 'right', children, expanded, - alignContentContainer = 'right', + id, ...props }) => { const drawerRef = useRef(null); @@ -41,9 +47,9 @@ export const Drawer: React.FC = ({ {expanded ? ( = ({ + bg = 'background', children, closeLabel = 'Close', + id, expanded, openFrom = 'left', onClose, title, - bg = 'background', }) => { return ( = ({ > F Our Drawers are [controlled components](https://reactjs.org/docs/forms.html#controlled-components), so their checked value must be controlled by an external state passed in as `expanded`. +The component that expands the `Drawer` should have an `aria-expanded` and `aria-controls` attribute. The `aria-controls` attribute should point to the `id` of the `Drawer`. The `aria-expanded` attribute should be set to the value of the `expanded` prop passed to the `Drawer`. + +```tsx +const ExampleDrawer: React.FC = () => { + const [expanded, setExpanded] = useState(false); + const drawerId = useId(); + + return ( + + Example Drawer + setExpanded((previousExpanded) => !previousExpanded)} + aria-expanded={expanded} + aria-controls={drawerId} + > + Set the proper aria-labels! + + + ); +} +``` + ## Playground diff --git a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx index acfb94e4bcd..421a1c5c712 100644 --- a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx +++ b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx @@ -1,6 +1,6 @@ import { Drawer, FlexBox, StrokeButton } from '@codecademy/gamut'; import type { Meta } from '@storybook/react'; -import { useState } from 'react'; +import { useId, useState } from 'react'; const meta: Meta = { component: Drawer, @@ -11,10 +11,13 @@ export default meta; export const Default: React.FC = () => { const [expanded, setExpanded] = useState(false); + const drawerId = useId(); return ( - Drawer content in here! + Drawer content in here! setExpanded((previousExpanded) => !previousExpanded)} > Toggle Drawer diff --git a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx index 81c1bb925bb..04fc3079931 100644 --- a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx +++ b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx @@ -30,6 +30,26 @@ Internally, Flyout is a combination of Ove Our Flyouts are [controlled components](https://reactjs.org/docs/forms.html#controlled-components), so their checked value must be controlled by an external state passed in as `expanded` and `onChange`: +```tsx +const ExampleFlyout: React.FC = () => { + const [expanded, setExpanded] = useState(false); + const flyoutId = useId(); + + return ( + + Example Flyout + setExpanded((previousExpanded) => !previousExpanded)} + > + Set the proper aria-labels! + + + ); +} +``` + ## Playground diff --git a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx index 0d7d229340e..d09d3abd295 100644 --- a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx +++ b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx @@ -5,7 +5,9 @@ import { useState } from 'react'; const meta: Meta = { component: Flyout, - args: {}, + args: { + id: 'flyout-example' + }, }; export default meta; @@ -35,7 +37,11 @@ export const FlyoutExample: Story = { hurricane... - setExpanded(true)}> + setExpanded(true)} + aria-expanded={expanded} + aria-controls={args.id} + > Tell me more?! From e3a654224d5b45bb1ee49cff9b8ba393540d4376 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Mon, 31 Mar 2025 11:49:29 -0400 Subject: [PATCH 08/38] formatted --- packages/gamut/src/Drawer/index.tsx | 4 ++-- packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx | 8 +++++--- .../styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx | 4 +++- packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx | 8 +++++--- .../src/lib/Molecules/Flyout/Flyout.stories.tsx | 2 +- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/gamut/src/Drawer/index.tsx b/packages/gamut/src/Drawer/index.tsx index d5ee27e5199..0a1de54ba74 100644 --- a/packages/gamut/src/Drawer/index.tsx +++ b/packages/gamut/src/Drawer/index.tsx @@ -20,11 +20,11 @@ export interface DrawerProps extends Omit { * Which edge of the drawer content container is aligned to during the animation. */ alignContentContainer?: 'left' | 'right'; - /** + /** * This `id` is used to link the element that expands the Drawer to the Drawer itself. * It is needed for the `aria-controls` attribute to work properly for accessibility. */ - id: string; + id: string; } export const Drawer: React.FC = ({ diff --git a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx index 7fefe45712f..d15e5fb50a9 100644 --- a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx +++ b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx @@ -42,8 +42,10 @@ const ExampleDrawer: React.FC = () => { const drawerId = useId(); return ( - - Example Drawer + + + Example Drawer + setExpanded((previousExpanded) => !previousExpanded)} aria-expanded={expanded} @@ -53,7 +55,7 @@ const ExampleDrawer: React.FC = () => { ); -} +}; ``` ## Playground diff --git a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx index 421a1c5c712..9c041360e22 100644 --- a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx +++ b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx @@ -14,7 +14,9 @@ export const Default: React.FC = () => { const drawerId = useId(); return ( - Drawer content in here! + + Drawer content in here! + { const flyoutId = useId(); return ( - - Example Flyout + + + Example Flyout + { ); -} +}; ``` ## Playground diff --git a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx index d09d3abd295..0b61bbbe519 100644 --- a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx +++ b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx @@ -6,7 +6,7 @@ import { useState } from 'react'; const meta: Meta = { component: Flyout, args: { - id: 'flyout-example' + id: 'flyout-example', }, }; From 09dafab93c7cb4bae685c852cb2617e45ef0f04d Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Tue, 1 Apr 2025 10:40:59 -0400 Subject: [PATCH 09/38] updated drawer and flyout stories --- packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx | 2 +- packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx index d15e5fb50a9..81bc6274c3b 100644 --- a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx +++ b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx @@ -34,7 +34,7 @@ An example of a molecule that uses Drawer is the F Our Drawers are [controlled components](https://reactjs.org/docs/forms.html#controlled-components), so their checked value must be controlled by an external state passed in as `expanded`. -The component that expands the `Drawer` should have an `aria-expanded` and `aria-controls` attribute. The `aria-controls` attribute should point to the `id` of the `Drawer`. The `aria-expanded` attribute should be set to the value of the `expanded` prop passed to the `Drawer`. +The component that toggles the `Drawer` should include both `aria-expanded` and `aria-controls` attributes. The `aria-controls` attribute must reference the `id` of the `Drawer`. The `aria-expanded` attribute should reflect the value of the `expanded` prop passed to the `Drawer`. ```tsx const ExampleDrawer: React.FC = () => { diff --git a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx index f28ad8de9a1..26544d7e229 100644 --- a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx +++ b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx @@ -28,7 +28,9 @@ On button click, a container animates in from the left or right side of the wind Internally, Flyout is a combination of Overlay and Drawer. -Our Flyouts are [controlled components](https://reactjs.org/docs/forms.html#controlled-components), so their checked value must be controlled by an external state passed in as `expanded` and `onChange`: +Our Flyouts are [controlled components](https://reactjs.org/docs/forms.html#controlled-components), so their checked value must be controlled by an external state passed in as `expanded` and `onChange`. + +The component that toggles the `Flyout` should include both `aria-expanded` and `aria-controls` attributes. The `aria-controls` attribute must reference the `id` of the `Flyout`. The `aria-expanded` attribute should reflect the value of the `expanded` prop passed to the `Flyout`. ```tsx const ExampleFlyout: React.FC = () => { From 7b8a1b0c30548be4da5a4e1efcfac691b7b4b95b Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Tue, 1 Apr 2025 13:00:40 -0400 Subject: [PATCH 10/38] updated guidance on listrow --- .../src/DataList/Controls/ExpandControl.tsx | 2 +- packages/gamut/src/List/ListRow.tsx | 6 ++--- .../Organisms/Lists & Tables/List/List.mdx | 22 +++++++++++++------ .../Lists & Tables/List/List.stories.tsx | 9 ++------ 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/gamut/src/DataList/Controls/ExpandControl.tsx b/packages/gamut/src/DataList/Controls/ExpandControl.tsx index 51c7e25aa26..f2620bf5b98 100644 --- a/packages/gamut/src/DataList/Controls/ExpandControl.tsx +++ b/packages/gamut/src/DataList/Controls/ExpandControl.tsx @@ -32,7 +32,7 @@ export const ExpandControl: React.FC = ({ }} aria-label={`Expand ${id} Row`} aria-expanded={expanded} - aria-controls={id} + aria-controls={id ?? undefined} > diff --git a/packages/gamut/src/List/ListRow.tsx b/packages/gamut/src/List/ListRow.tsx index 77538325816..3826dfabc06 100644 --- a/packages/gamut/src/List/ListRow.tsx +++ b/packages/gamut/src/List/ListRow.tsx @@ -47,7 +47,7 @@ const DivExpand = styled(motion.div)(expandStyles); const TDExpand = styled(motion.td)(expandStyles); const ExpandInCollapseOut: React.FC< - WithChildrenProp & { as: 'td' | 'div'; id: string } + WithChildrenProp & { as: 'td' | 'div'; id: string | undefined } > = ({ as, children, id }) => { const ResponsiveExpand = as === 'td' ? TDExpand : DivExpand; @@ -105,7 +105,7 @@ export const ListRow = forwardRef( ( {content} {expanded && ( - + {renderExpanded?.()} diff --git a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.mdx b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.mdx index 1740c8606d1..ce353d575f9 100644 --- a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.mdx +++ b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.mdx @@ -216,6 +216,9 @@ You can define collapsible content by passing an `expanded` prop and the `React. #### Expand on button click +The `ListRow` component has access to the `ExpandControl` component which can be used to toggle the expanded state of the row. The `ExpandControl` component will automatically handle the rotation of the icon based on the expanded state. +Ensure that the `ListRow` has a unique `id` prop to ensure that the `ExpandControl` can set the correct aria attributes. + ```tsx export const ExpandableRow: React.FC<{ header: string; @@ -226,6 +229,7 @@ export const ExpandableRow: React.FC<{ return ( Surprise} @@ -233,11 +237,12 @@ export const ExpandableRow: React.FC<{ {header} {content} - setExpanded(!isExpanded)}> - - - - + setExpanded(!isExpanded)} + id={idOfRow} + disabled={false} + /> ); @@ -246,6 +251,8 @@ export const ExpandableRow: React.FC<{ #### Expand on row click +Make sure you pass in an `id` prop to the `ListRow` component so that the correct aria attributes are set on the row. + ```tsx export const ExpandableRow: React.FC<{ header: string; @@ -256,6 +263,7 @@ export const ExpandableRow: React.FC<{ return ( setExpanded(!isExpanded)} @@ -293,13 +301,13 @@ You can combine variants and spacing to your needs. ### Expandable buttom -Buttons can be configured to expand the row content. +Buttons can be configured to expand the row content. Check over the "Expand on button click" section for more guidance. ### Expanded row -An entire row can be made interactive to expand the row content. +An entire row can be made interactive to expand the row content. Check over the "Expand on row click" section for more guidance. diff --git a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx index 324141b65e7..0e2393affa1 100644 --- a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx +++ b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx @@ -234,7 +234,7 @@ const HorizontalScrollingExample: React.FC = (args) => { return ( {rows.map(({ name, role, ship }, i, _, key = `example-row-${i}`) => ( - + {name} @@ -288,12 +288,7 @@ const HorizontalScrollingExample: React.FC = (args) => { tip="Options" tipProps={{ alignment: 'bottom-center', placement: 'floating' }} /> - + ))} From d4851a7c29f56670e8d3c6dab61efb0fa67af0e1 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Tue, 1 Apr 2025 14:47:18 -0400 Subject: [PATCH 11/38] formatted --- packages/gamut/src/List/ListRow.tsx | 5 ++++- .../src/lib/Organisms/Lists & Tables/List/List.stories.tsx | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/gamut/src/List/ListRow.tsx b/packages/gamut/src/List/ListRow.tsx index 3826dfabc06..976da0df534 100644 --- a/packages/gamut/src/List/ListRow.tsx +++ b/packages/gamut/src/List/ListRow.tsx @@ -146,7 +146,10 @@ export const ListRow = forwardRef( {content} {expanded && ( - + {renderExpanded?.()} diff --git a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx index 0e2393affa1..1566618d913 100644 --- a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx +++ b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx @@ -288,7 +288,6 @@ const HorizontalScrollingExample: React.FC = (args) => { tip="Options" tipProps={{ alignment: 'bottom-center', placement: 'floating' }} /> - ))} From b9d903e13d179eed246d3180d4d882153ebb8f4c Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Tue, 1 Apr 2025 15:22:00 -0400 Subject: [PATCH 12/38] omit id from DisclosureProps --- packages/gamut/src/Disclosure/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gamut/src/Disclosure/types.ts b/packages/gamut/src/Disclosure/types.ts index 7d8db9fa626..d081ef8f96f 100644 --- a/packages/gamut/src/Disclosure/types.ts +++ b/packages/gamut/src/Disclosure/types.ts @@ -64,7 +64,7 @@ export interface DisclosureBodyProps extends DisclosureBodyWrapperStyles { } export interface DisclosureProps - extends DisclosureButtonProps, + extends Omit, DisclosureBodyProps, DisclosureWrapperStyles { /** From 970247fddbc2f3410117b30a9848f466ad463769 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Wed, 2 Apr 2025 16:28:19 -0400 Subject: [PATCH 13/38] updated disclosure types --- packages/gamut/src/Disclosure/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gamut/src/Disclosure/types.ts b/packages/gamut/src/Disclosure/types.ts index d081ef8f96f..156c3eeb9c5 100644 --- a/packages/gamut/src/Disclosure/types.ts +++ b/packages/gamut/src/Disclosure/types.ts @@ -64,8 +64,8 @@ export interface DisclosureBodyProps extends DisclosureBodyWrapperStyles { } export interface DisclosureProps - extends Omit, - DisclosureBodyProps, + extends DisclosureButtonProps, + Omit , DisclosureWrapperStyles { /** * Determines whether or not the Disclosure is expanded upon load. From 416098d7f85bb4fba2f0905a59e5031fe0da7d38 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Wed, 2 Apr 2025 16:52:16 -0400 Subject: [PATCH 14/38] formatted --- packages/gamut/src/Disclosure/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gamut/src/Disclosure/types.ts b/packages/gamut/src/Disclosure/types.ts index 156c3eeb9c5..de235829fc7 100644 --- a/packages/gamut/src/Disclosure/types.ts +++ b/packages/gamut/src/Disclosure/types.ts @@ -65,7 +65,7 @@ export interface DisclosureBodyProps extends DisclosureBodyWrapperStyles { export interface DisclosureProps extends DisclosureButtonProps, - Omit , + Omit, DisclosureWrapperStyles { /** * Determines whether or not the Disclosure is expanded upon load. From f6dc0bc9aa72afb44c5c2a2c094ab5cef31a6ee4 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Thu, 3 Apr 2025 12:15:00 -0400 Subject: [PATCH 15/38] updated disclosure to correct props and types --- packages/gamut/src/Disclosure/DisclosureButton/index.tsx | 2 ++ packages/gamut/src/Disclosure/index.tsx | 3 +-- packages/gamut/src/Disclosure/types.ts | 6 +++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/gamut/src/Disclosure/DisclosureButton/index.tsx b/packages/gamut/src/Disclosure/DisclosureButton/index.tsx index 0b6db3e8659..b62502d2812 100644 --- a/packages/gamut/src/Disclosure/DisclosureButton/index.tsx +++ b/packages/gamut/src/Disclosure/DisclosureButton/index.tsx @@ -12,6 +12,7 @@ import { getRotationSize, getSpacing, getTitleSize } from '../helpers'; import { DisclosureButtonProps } from '../types'; export const DisclosureButton: React.FC = ({ + ariaControlsId, disabled = false, heading, headingLevel = 'h3', @@ -36,6 +37,7 @@ export const DisclosureButton: React.FC = ({ = ({ variant={variant} > , Omit, DisclosureWrapperStyles { /** From 2fe31e184e3ce5f65df4d5f3ace0e6f6a45c403e Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Thu, 3 Apr 2025 12:25:32 -0400 Subject: [PATCH 16/38] fix typo --- .../styleguide/src/lib/Organisms/Lists & Tables/List/List.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.mdx b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.mdx index ce353d575f9..f84977ffeb0 100644 --- a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.mdx +++ b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.mdx @@ -299,7 +299,7 @@ You can combine variants and spacing to your needs. -### Expandable buttom +### Expandable button Buttons can be configured to expand the row content. Check over the "Expand on button click" section for more guidance. From bba3aaf3244720f68a9794744e62fc0bcd687243 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Thu, 3 Apr 2025 15:14:31 -0400 Subject: [PATCH 17/38] clean up of useId --- packages/gamut/src/Disclosure/index.tsx | 2 +- packages/gamut/src/Tip/InfoTip/index.tsx | 2 +- packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx | 2 +- packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx | 2 +- .../lib/Organisms/Lists & Tables/List/List.stories.tsx | 8 +++++++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/gamut/src/Disclosure/index.tsx b/packages/gamut/src/Disclosure/index.tsx index 8b7803acbcf..1010a8b3262 100644 --- a/packages/gamut/src/Disclosure/index.tsx +++ b/packages/gamut/src/Disclosure/index.tsx @@ -29,7 +29,7 @@ export const Disclosure: React.FC = ({ variant, }) => { const [isExpanded, setIsExpanded] = useState(initiallyExpanded); - const bodyId = useId(); + const bodyId = `disclosure-body-${useId()}`; return ( = ({ const Tip = loaded && isFloating ? FloatingTip : InlineTip; - const textId = useId(); + const textId = `tooltip-text-${useId()}`; const tipProps = { alignment, diff --git a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx index 81bc6274c3b..f44f9ef4ff7 100644 --- a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx +++ b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx @@ -39,7 +39,7 @@ The component that toggles the `Drawer` should include both `aria-expanded` and ```tsx const ExampleDrawer: React.FC = () => { const [expanded, setExpanded] = useState(false); - const drawerId = useId(); + const drawerId = 'example-drawer-id' return ( diff --git a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx index 26544d7e229..299ccb32c8f 100644 --- a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx +++ b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx @@ -35,7 +35,7 @@ The component that toggles the `Flyout` should include both `aria-expanded` and ```tsx const ExampleFlyout: React.FC = () => { const [expanded, setExpanded] = useState(false); - const flyoutId = useId(); + const flyoutId = 'example-flyout-id'; return ( diff --git a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx index 1566618d913..324141b65e7 100644 --- a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx +++ b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx @@ -234,7 +234,7 @@ const HorizontalScrollingExample: React.FC = (args) => { return ( {rows.map(({ name, role, ship }, i, _, key = `example-row-${i}`) => ( - + {name} @@ -288,6 +288,12 @@ const HorizontalScrollingExample: React.FC = (args) => { tip="Options" tipProps={{ alignment: 'bottom-center', placement: 'floating' }} /> + ))} From c1799a7669c692e4be31b45fd40d557b1dd4123a Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Thu, 3 Apr 2025 15:19:56 -0400 Subject: [PATCH 18/38] formatted --- packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx index f44f9ef4ff7..0c4a7d85089 100644 --- a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx +++ b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx @@ -39,7 +39,7 @@ The component that toggles the `Drawer` should include both `aria-expanded` and ```tsx const ExampleDrawer: React.FC = () => { const [expanded, setExpanded] = useState(false); - const drawerId = 'example-drawer-id' + const drawerId = 'example-drawer-id'; return ( From dd23d8f3867da7e5c57fd8da6a7e0c5ed2fe8360 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Thu, 3 Apr 2025 16:25:11 -0400 Subject: [PATCH 19/38] updated disclosure body id --- packages/gamut/src/Disclosure/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gamut/src/Disclosure/index.tsx b/packages/gamut/src/Disclosure/index.tsx index 1010a8b3262..ae996fb5d78 100644 --- a/packages/gamut/src/Disclosure/index.tsx +++ b/packages/gamut/src/Disclosure/index.tsx @@ -1,5 +1,5 @@ import { AnimatePresence } from 'framer-motion'; -import { useId, useState } from 'react'; +import { useState } from 'react'; import * as React from 'react'; import { ExpandInCollapseOut } from '../Animation'; @@ -29,7 +29,7 @@ export const Disclosure: React.FC = ({ variant, }) => { const [isExpanded, setIsExpanded] = useState(initiallyExpanded); - const bodyId = `disclosure-body-${useId()}`; + const bodyId = `disclosure-body-${heading}}`; return ( Date: Thu, 3 Apr 2025 18:31:30 -0400 Subject: [PATCH 20/38] fixed typo in id --- packages/gamut/src/Disclosure/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gamut/src/Disclosure/index.tsx b/packages/gamut/src/Disclosure/index.tsx index ae996fb5d78..c1b3f1a2d76 100644 --- a/packages/gamut/src/Disclosure/index.tsx +++ b/packages/gamut/src/Disclosure/index.tsx @@ -29,7 +29,7 @@ export const Disclosure: React.FC = ({ variant, }) => { const [isExpanded, setIsExpanded] = useState(initiallyExpanded); - const bodyId = `disclosure-body-${heading}}`; + const bodyId = `disclosure-body-${heading}`; return ( Date: Thu, 3 Apr 2025 19:52:29 -0400 Subject: [PATCH 21/38] pushing for build From b64c25fc0945ce3bb01e31db160e91826211da1e Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 4 Apr 2025 08:30:44 -0400 Subject: [PATCH 22/38] updated disclosure to conditionally set aria-controls --- packages/gamut/src/Disclosure/DisclosureButton/index.tsx | 2 +- packages/gamut/src/Disclosure/index.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/gamut/src/Disclosure/DisclosureButton/index.tsx b/packages/gamut/src/Disclosure/DisclosureButton/index.tsx index b62502d2812..84329dabb21 100644 --- a/packages/gamut/src/Disclosure/DisclosureButton/index.tsx +++ b/packages/gamut/src/Disclosure/DisclosureButton/index.tsx @@ -37,7 +37,7 @@ export const DisclosureButton: React.FC = ({ = ({ variant, }) => { const [isExpanded, setIsExpanded] = useState(initiallyExpanded); - const bodyId = `disclosure-body-${heading}`; + // const bodyId = `disclosure-body-${heading}`; + const bodyId = useId(); return ( Date: Fri, 4 Apr 2025 09:11:38 -0400 Subject: [PATCH 23/38] re-implement useId() --- packages/gamut/src/Tip/InfoTip/index.tsx | 2 +- packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx | 2 +- packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/gamut/src/Tip/InfoTip/index.tsx b/packages/gamut/src/Tip/InfoTip/index.tsx index 6b0b846b931..fc51cd68b1d 100644 --- a/packages/gamut/src/Tip/InfoTip/index.tsx +++ b/packages/gamut/src/Tip/InfoTip/index.tsx @@ -88,7 +88,7 @@ export const InfoTip: React.FC = ({ const Tip = loaded && isFloating ? FloatingTip : InlineTip; - const textId = `tooltip-text-${useId()}`; + const textId = useId(); const tipProps = { alignment, diff --git a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx index 0c4a7d85089..81bc6274c3b 100644 --- a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx +++ b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx @@ -39,7 +39,7 @@ The component that toggles the `Drawer` should include both `aria-expanded` and ```tsx const ExampleDrawer: React.FC = () => { const [expanded, setExpanded] = useState(false); - const drawerId = 'example-drawer-id'; + const drawerId = useId(); return ( diff --git a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx index 299ccb32c8f..26544d7e229 100644 --- a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx +++ b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx @@ -35,7 +35,7 @@ The component that toggles the `Flyout` should include both `aria-expanded` and ```tsx const ExampleFlyout: React.FC = () => { const [expanded, setExpanded] = useState(false); - const flyoutId = 'example-flyout-id'; + const flyoutId = useId(); return ( From 9bafa406b11cc1de0f928e03ed0f4f595ac0bd65 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 4 Apr 2025 09:41:14 -0400 Subject: [PATCH 24/38] update drawer/flyout examples --- packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx | 4 ++-- packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx | 2 +- packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx | 2 +- .../styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx index 81bc6274c3b..b6b9ab5c1a2 100644 --- a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx +++ b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx @@ -47,9 +47,9 @@ const ExampleDrawer: React.FC = () => { Example Drawer setExpanded((previousExpanded) => !previousExpanded)} + aria-controls={expanded ? drawerId : undefined} aria-expanded={expanded} - aria-controls={drawerId} + onClick={() => setExpanded((previousExpanded) => !previousExpanded)} > Set the proper aria-labels! diff --git a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx index 9c041360e22..310eaee1c5d 100644 --- a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx +++ b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx @@ -18,8 +18,8 @@ export const Default: React.FC = () => { Drawer content in here! setExpanded((previousExpanded) => !previousExpanded)} > Toggle Drawer diff --git a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx index 26544d7e229..70eef64fd66 100644 --- a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx +++ b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx @@ -44,7 +44,7 @@ const ExampleFlyout: React.FC = () => { setExpanded((previousExpanded) => !previousExpanded)} > Set the proper aria-labels! diff --git a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx index 0b61bbbe519..e40fcfd7557 100644 --- a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx +++ b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx @@ -38,9 +38,9 @@ export const FlyoutExample: Story = { setExpanded(true)} + aria-controls={expanded ? args.id : undefined} aria-expanded={expanded} - aria-controls={args.id} + onClick={() => setExpanded(true)} > Tell me more?! From 0499920e7833511e6b175f1c4df2f8c4b2839bcf Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 4 Apr 2025 15:04:12 -0400 Subject: [PATCH 25/38] removed old comment and updated another --- packages/gamut/src/Disclosure/index.tsx | 1 - packages/gamut/src/Disclosure/types.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/gamut/src/Disclosure/index.tsx b/packages/gamut/src/Disclosure/index.tsx index 8b1f795b4e7..8b7803acbcf 100644 --- a/packages/gamut/src/Disclosure/index.tsx +++ b/packages/gamut/src/Disclosure/index.tsx @@ -29,7 +29,6 @@ export const Disclosure: React.FC = ({ variant, }) => { const [isExpanded, setIsExpanded] = useState(initiallyExpanded); - // const bodyId = `disclosure-body-${heading}`; const bodyId = useId(); return ( Date: Mon, 7 Apr 2025 15:57:35 -0400 Subject: [PATCH 26/38] removed aria-controls from disclosure --- .../gamut/src/Disclosure/DisclosureButton/index.tsx | 2 -- packages/gamut/src/Disclosure/index.tsx | 4 +--- packages/gamut/src/Disclosure/types.ts | 13 ++----------- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/packages/gamut/src/Disclosure/DisclosureButton/index.tsx b/packages/gamut/src/Disclosure/DisclosureButton/index.tsx index 84329dabb21..0b6db3e8659 100644 --- a/packages/gamut/src/Disclosure/DisclosureButton/index.tsx +++ b/packages/gamut/src/Disclosure/DisclosureButton/index.tsx @@ -12,7 +12,6 @@ import { getRotationSize, getSpacing, getTitleSize } from '../helpers'; import { DisclosureButtonProps } from '../types'; export const DisclosureButton: React.FC = ({ - ariaControlsId, disabled = false, heading, headingLevel = 'h3', @@ -37,7 +36,6 @@ export const DisclosureButton: React.FC = ({ = ({ variant, }) => { const [isExpanded, setIsExpanded] = useState(initiallyExpanded); - const bodyId = useId(); + return ( = ({ variant={variant} > = ({ ctaText={ctaText} hasPanelBg={hasPanelBg} href={href} - id={bodyId} spacing={spacing} /> diff --git a/packages/gamut/src/Disclosure/types.ts b/packages/gamut/src/Disclosure/types.ts index cd1497c4ff7..62654c140e7 100644 --- a/packages/gamut/src/Disclosure/types.ts +++ b/packages/gamut/src/Disclosure/types.ts @@ -4,10 +4,6 @@ import { } from './elements'; export interface DisclosureButtonProps { - /** - * This is the value of the `id` that is used to link the DisclosureButton to the DisclosureBody. - */ - ariaControlsId: string; /** * Renders the Disclosure unclickable. */ @@ -56,11 +52,6 @@ export interface DisclosureBodyProps extends DisclosureBodyWrapperStyles { */ ctaText?: string; href?: string; - /** - * This `id` is used to link the DisclosureButton to the DisclosureBody. - * It is needed for the `aria-controls` attribute to work properly for accessibility. - */ - id: string; /** * Determines the size of the heading text and the space between text in the body. */ @@ -68,8 +59,8 @@ export interface DisclosureBodyProps extends DisclosureBodyWrapperStyles { } export interface DisclosureProps - extends Omit, - Omit, + extends DisclosureButtonProps, + DisclosureBodyProps, DisclosureWrapperStyles { /** * Determines whether or not the Disclosure is expanded upon load. From 269659e813204be7a49483d66761923c660f43b9 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Mon, 7 Apr 2025 16:31:50 -0400 Subject: [PATCH 27/38] leftover code cleanup --- packages/gamut/src/Disclosure/DisclosureBody/index.tsx | 2 -- packages/gamut/src/Disclosure/index.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/gamut/src/Disclosure/DisclosureBody/index.tsx b/packages/gamut/src/Disclosure/DisclosureBody/index.tsx index c129736e4b2..587bb9e651a 100644 --- a/packages/gamut/src/Disclosure/DisclosureBody/index.tsx +++ b/packages/gamut/src/Disclosure/DisclosureBody/index.tsx @@ -13,7 +13,6 @@ export const DisclosureBody: React.FC = ({ ctaText, hasPanelBg = false, href, - id, spacing = 'normal', }) => { const buttonRequirements = ctaText && ctaCallback; @@ -27,7 +26,6 @@ export const DisclosureBody: React.FC = ({ mb={verticalSpacing} mt={4} mx={horizontalSpacing} - id={id} > {body} diff --git a/packages/gamut/src/Disclosure/index.tsx b/packages/gamut/src/Disclosure/index.tsx index 5ed8fdc9e7e..aa2b9a78a38 100644 --- a/packages/gamut/src/Disclosure/index.tsx +++ b/packages/gamut/src/Disclosure/index.tsx @@ -1,5 +1,5 @@ import { AnimatePresence } from 'framer-motion'; -import { useId, useState } from 'react'; +import { useState } from 'react'; import * as React from 'react'; import { ExpandInCollapseOut } from '../Animation'; From 93828deff66a7a48205b7b3db24111a4ef7aa0ee Mon Sep 17 00:00:00 2001 From: Amy Resnik Date: Mon, 21 Apr 2025 10:32:38 -0400 Subject: [PATCH 28/38] feat(Modal)!: add multiview options for back and danger variant Adds bi-directional option for multiview modals, refactors the CTAs in multiview modals, and adds danger variant. Adds unit tests and additional stories. --- packages/gamut/src/Modals/ImageContainer.tsx | 4 +- packages/gamut/src/Modals/Modal.tsx | 93 ++-- .../gamut/src/Modals/__tests__/Modal.test.tsx | 446 ++++++++++++++++-- .../src/lib/Molecules/Modals/Modal/Modal.mdx | 73 ++- .../Molecules/Modals/Modal/Modal.stories.tsx | 278 +++++++---- 5 files changed, 690 insertions(+), 204 deletions(-) diff --git a/packages/gamut/src/Modals/ImageContainer.tsx b/packages/gamut/src/Modals/ImageContainer.tsx index 7934577ff2c..e5b8d10fec7 100644 --- a/packages/gamut/src/Modals/ImageContainer.tsx +++ b/packages/gamut/src/Modals/ImageContainer.tsx @@ -2,9 +2,9 @@ import { ComponentProps } from 'react'; import { Box } from '../Box'; import { ModalContainer } from './elements'; -import { ModalView } from './Modal'; +import { ModalBaseProps } from './types'; -interface ImageProps extends Pick { +interface ImageProps extends Pick { size: Exclude< ComponentProps['size'], false | undefined diff --git a/packages/gamut/src/Modals/Modal.tsx b/packages/gamut/src/Modals/Modal.tsx index f6f4ba42266..e03efc6368b 100644 --- a/packages/gamut/src/Modals/Modal.tsx +++ b/packages/gamut/src/Modals/Modal.tsx @@ -16,13 +16,38 @@ interface DialogButtonProps { onClick?: ButtonProps['onClick']; disabled?: boolean; } -export interface ModalView + +type SecondaryDialogButtonProps = DialogButtonProps & { + actionType: 'back' | 'cancel'; +}; + +type ModalNextProps = { + primaryCta: DialogButtonProps & { + actionType: 'next'; + }; + secondaryCta: SecondaryDialogButtonProps; +}; + +type ModalConfirmProps = { + primaryCta: DialogButtonProps & { + actionType: 'confirm'; + variant?: Extract< + ComponentProps['variant'], + 'primary' | 'danger' + >; + }; + secondaryCta?: SecondaryDialogButtonProps; +}; + +type ModalButtonProps = ModalNextProps | ModalConfirmProps; + +interface ModalView extends Omit { children: React.ReactNode; - nextCta?: DialogButtonProps; - confirmCta?: DialogButtonProps; - cancelCta?: DialogButtonProps; } + +export type ModalViewProps = ModalView & ModalButtonProps; + export interface SingleViewModalProps extends ModalBaseProps { size?: ComponentProps['size']; /** @@ -56,7 +81,7 @@ export interface MultiViewModalProps /** * Optional array of multiple screens */ - views: ModalView[]; + views: ModalViewProps[]; /** * Whether to disable X button at top right of modal */ @@ -81,7 +106,8 @@ export const Modal: React.FC = ({ ...rest }) => { const [currentView, setCurrentView] = useState(0); - const image = (views?.[currentView].image || rest?.image) ?? null; + const view = views?.[currentView]; + const image = (view?.image || rest?.image) ?? null; return ( = ({ aria-label={ariaLabel} aria-hidden="false" > - {(title || views?.[currentView].title) && ( + {(title || view?.title) && ( = ({ gridArea="title" aria-live="assertive" > - {title || views?.[currentView].title} + {title || view?.title} )} {!hideCloseButton && ( @@ -130,37 +156,48 @@ export const Modal: React.FC = ({ data-testid="modal-content" > {image && size && } - {views?.[currentView].children || children} + {view?.children || children} - {views?.[currentView].cancelCta && ( + {view?.secondaryCta && ( ) => { - views?.[currentView].cancelCta?.onClick?.(e); - onRequestClose(); + if (view?.secondaryCta?.actionType === 'back') { + setCurrentView(currentView - 1); + } + if (view?.secondaryCta?.actionType === 'cancel') { + onRequestClose(); + } + view?.secondaryCta?.onClick?.(e); }} justifySelf="end" gridArea="cancel" /> )} - {views?.[currentView].nextCta && ( - ) => { - setCurrentView(currentView + 1); - views?.[currentView].nextCta?.onClick?.(e); - }} - gridArea="confirm" - /> - )} - {views?.[currentView].confirmCta && ( + {view?.primaryCta && ( ) => { - views?.[currentView].confirmCta?.onClick?.(e); + if (view?.primaryCta?.actionType === 'next') { + setCurrentView(currentView + 1); + } + view?.primaryCta?.onClick?.(e); }} gridArea="confirm" /> diff --git a/packages/gamut/src/Modals/__tests__/Modal.test.tsx b/packages/gamut/src/Modals/__tests__/Modal.test.tsx index 804fdc1b0c2..a4b55d3d546 100644 --- a/packages/gamut/src/Modals/__tests__/Modal.test.tsx +++ b/packages/gamut/src/Modals/__tests__/Modal.test.tsx @@ -1,72 +1,416 @@ import { theme } from '@codecademy/gamut-styles'; -import { ThemeProvider } from '@emotion/react'; -import { fireEvent, render, screen } from '@testing-library/react'; - -import { Modal, ModalProps } from '..'; - -const renderModal = (props?: Partial) => { - return render( - - - - ); -}; - -describe('ModalDeprecated', () => { - it('renders children when isOpen is true', () => { - const children = 'Hey'; - const { baseElement } = renderModal({ children }); - expect(baseElement).toHaveTextContent(children); - }); +import { setupRtl } from '@codecademy/gamut-tests'; +import { fireEvent } from '@testing-library/react'; + +import { Modal } from '..'; + +const mockRequestClose = jest.fn(); +const renderView = setupRtl(Modal, { + title: 'Title', + children: 'Hello world', + isOpen: true, + onRequestClose: mockRequestClose, +}); - it('does not render when isOpen is false', () => { - const children = 'Hey'; - const { baseElement } = renderModal({ isOpen: false, children }); - expect(baseElement).not.toHaveTextContent(children); +describe('Modal', () => { + it('renders children and title when isOpen is true', () => { + const { view } = renderView(); + view.getByText('Hello world'); + view.getByText('Title'); }); - it('does not render its close button if hideDefaultCloseButton is true', () => { - renderModal({ - hideCloseButton: true, - }); + it('does not render children and title when isOpen is false', () => { + const { view } = renderView({ isOpen: false }); + expect(view.queryByText('Hello world')).toBe(null); + expect(view.queryByText('Title')).toBe(null); + }); - expect(screen.queryAllByRole('button').length).toBe(0); + it('does not render its close button if hideCloseButton is true', () => { + const { view } = renderView({ hideCloseButton: true }); + expect(view.queryAllByRole('button').length).toBe(0); }); it('renders its close button if hideCloseButton is false', () => { - renderModal(); - - expect(screen.queryAllByRole('button').length).toBe(1); + const { view } = renderView({ hideCloseButton: false }); + view.getByRole('button'); }); it('triggers onRequestClose callback when escape key is triggered', () => { - const onRequestClose = jest.fn(); - const { baseElement } = renderModal({ - isOpen: true, - onRequestClose, - }); - fireEvent.keyDown(baseElement, { key: 'Escape', code: 'Escape' }); - expect(onRequestClose.mock.calls.length).toBe(1); + const { view } = renderView(); + const modal = view.getByTestId('overlay-content-container'); + fireEvent.keyDown(modal, { key: 'Escape', code: 'Escape' }); + expect(mockRequestClose).toHaveBeenCalledTimes(1); }); it('triggers onRequestClose callback when clicking outside when clickOutsideCloses is true', () => { - const onRequestClose = jest.fn(); - const { baseElement } = renderModal({ - isOpen: true, - onRequestClose, - }); + const { view } = renderView(); // focus-trap listens to mouseDown, not click - fireEvent.mouseDown(baseElement); - expect(onRequestClose.mock.calls.length).toBe(1); + fireEvent.mouseDown(view.getByTestId('overlay-content-container')); + expect(mockRequestClose).toHaveBeenCalledTimes(1); }); it('does not trigger onRequestClose callback when clicking inside', () => { - const onRequestClose = jest.fn(); - renderModal({ - isOpen: true, - onRequestClose, + const { view } = renderView(); + fireEvent.mouseDown(view.getByTestId('modal-content')); + expect(mockRequestClose).not.toHaveBeenCalled(); + }); + + it('renders an image if passed in', () => { + const { view } = renderView({ + image: test, + }); + view.getByAltText('test'); + }); + + describe('Multi view', () => { + it('renders modal title and view children when modal title is provided', () => { + const { view } = renderView({ + views: [ + { + title: 'Multi view', + children: <>View 1, + primaryCta: { actionType: 'confirm', children: 'Confirm' }, + }, + ], + }); + view.getByText('View 1'); + view.getByText('Title'); + expect(view.queryByText('Multi view')).toBe(null); + }); + + it('renders view title and children when modal title is not provided', () => { + const { view } = renderView({ + title: undefined, + views: [ + { + title: 'Multi view', + children: <>View 1, + primaryCta: { actionType: 'confirm', children: 'Confirm' }, + }, + ], + }); + view.getByText('View 1'); + view.getByText('Multi view'); + expect(view.queryByText('Title')).toBe(null); + }); + + it('moves to the next view when next is clicked', () => { + const mockOnClick = jest.fn(); + const { view } = renderView({ + views: [ + { + title: 'Multi view', + children: <>View 1, + primaryCta: { + actionType: 'next', + children: 'Next', + onClick: mockOnClick, + }, + secondaryCta: { actionType: 'cancel', children: 'Cancel' }, + }, + { + title: 'Multi view', + children: <>View 2, + primaryCta: { actionType: 'next', children: 'Next' }, + secondaryCta: { actionType: 'back', children: 'Back' }, + }, + ], + }); + + view.getByText('View 1'); + expect(view.queryByText('View 2')).toBe(null); + + fireEvent.click(view.getByText('Next')); + + view.getByText('View 2'); + expect(view.queryByText('View 1')).toBe(null); + expect(mockOnClick).toHaveBeenCalledTimes(1); + }); + + it('moves to the previous view when back is clicked', () => { + const mockOnClick = jest.fn(); + const { view } = renderView({ + views: [ + { + title: 'Multi view', + children: <>View 1, + primaryCta: { actionType: 'next', children: 'Next' }, + secondaryCta: { actionType: 'cancel', children: 'Cancel' }, + }, + { + title: 'Multi view', + children: <>View 2, + primaryCta: { actionType: 'next', children: 'Next' }, + secondaryCta: { + actionType: 'back', + children: 'Back', + onClick: mockOnClick, + }, + }, + ], + }); + + fireEvent.click(view.getByText('Next')); + + view.getByText('View 2'); + expect(view.queryByText('View 1')).toBe(null); + + fireEvent.click(view.getByText('Back')); + + view.getByText('View 1'); + expect(view.queryByText('View 2')).toBe(null); + expect(mockOnClick).toHaveBeenCalledTimes(1); + }); + + it('renders back button as disabled if it is the first view', () => { + const { view } = renderView({ + views: [ + { + title: 'Multi view', + children: <>View 1, + primaryCta: { actionType: 'next', children: 'Next' }, + secondaryCta: { actionType: 'back', children: 'Back' }, + }, + ], + }); + + const backButton = view.getByRole('button', { + name: 'Back', + }); + expect(backButton).toBeDisabled(); + }); + + it('renders next button as disabled if it is the last view', () => { + const { view } = renderView({ + views: [ + { + title: 'Multi view', + children: <>View 1, + primaryCta: { actionType: 'next', children: 'Next' }, + secondaryCta: { actionType: 'cancel', children: 'Cancel' }, + }, + ], + }); + + const nextButton = view.getByRole('button', { + name: 'Next', + }); + expect(nextButton).toBeDisabled(); + }); + + it('renders button as disabled if passed in', () => { + const { view } = renderView({ + views: [ + { + title: 'Multi view', + children: <>View 1, + primaryCta: { actionType: 'next', children: 'Next' }, + secondaryCta: { + actionType: 'cancel', + children: 'Cancel', + disabled: true, + }, + }, + { + title: 'Multi view', + children: <>View 2, + primaryCta: { + actionType: 'confirm', + children: 'Done', + disabled: true, + }, + secondaryCta: { + actionType: 'back', + children: 'Back', + }, + }, + ], + }); + + const cancelButton = view.getByRole('button', { + name: 'Cancel', + }); + expect(cancelButton).toBeDisabled(); + + fireEvent.click(view.getByText('Next')); + + const doneButton = view.getByRole('button', { + name: 'Done', + }); + expect(doneButton).toBeDisabled(); + }); + + it('renders primary confirm button as primary variant by default', () => { + const { view } = renderView({ + views: [ + { + title: 'Multi view', + children: <>View 1, + primaryCta: { actionType: 'confirm', children: 'Confirm' }, + secondaryCta: { + actionType: 'cancel', + children: 'Cancel', + }, + }, + ], + }); + + const primaryButton = view.getByRole('button', { + name: 'Confirm', + }); + expect(primaryButton).toHaveStyle( + `background-color: ${theme.colors.primary}` + ); + }); + + it('renders primary confirm button as primary variant when passed in', () => { + const { view } = renderView({ + views: [ + { + title: 'Multi view', + children: <>View 1, + primaryCta: { + actionType: 'confirm', + children: 'Confirm', + variant: 'primary', + }, + secondaryCta: { + actionType: 'cancel', + children: 'Cancel', + }, + }, + ], + }); + + const primaryButton = view.getByRole('button', { + name: 'Confirm', + }); + expect(primaryButton).toHaveStyle( + `background-color: ${theme.colors.primary}` + ); + }); + + it('renders primary next button as primary variant by default', () => { + const { view } = renderView({ + views: [ + { + title: 'Multi view', + children: <>View 1, + primaryCta: { actionType: 'next', children: 'Next' }, + secondaryCta: { + actionType: 'cancel', + children: 'Cancel', + }, + }, + ], + }); + + const primaryButton = view.getByRole('button', { + name: 'Next', + }); + expect(primaryButton).toHaveStyle( + `background-color: ${theme.colors.primary}` + ); + }); + + it('renders primary confirm button as danger variant when passed in', () => { + const { view } = renderView({ + views: [ + { + title: 'Multi view', + children: <>View 1, + primaryCta: { + actionType: 'confirm', + children: 'Confirm', + variant: 'danger', + }, + secondaryCta: { + actionType: 'cancel', + children: 'Cancel', + }, + }, + ], + }); + + const primaryButton = view.getByRole('button', { + name: 'Confirm', + }); + expect(primaryButton).toHaveStyle( + `background-color: ${theme.colors.danger}` + ); + }); + + it('calls onRequestClose and onClick when cancel is clicked', () => { + const mockOnClick = jest.fn(); + const { view } = renderView({ + views: [ + { + title: 'Multi view', + children: <>View 1, + primaryCta: { actionType: 'next', children: 'Next' }, + secondaryCta: { + actionType: 'cancel', + children: 'Cancel', + onClick: mockOnClick, + }, + }, + ], + }); + + fireEvent.click(view.getByText('Cancel')); + expect(mockRequestClose).toHaveBeenCalledTimes(1); + expect(mockOnClick).toHaveBeenCalledTimes(1); + }); + + it('calls onClick when confirm is clicked', () => { + const mockOnClick = jest.fn(); + const { view } = renderView({ + views: [ + { + title: 'Multi view', + children: <>View 1, + primaryCta: { + actionType: 'confirm', + children: 'Confirm', + onClick: mockOnClick, + }, + secondaryCta: { + actionType: 'cancel', + children: 'Cancel', + onClick: mockOnClick, + }, + }, + ], + }); + + fireEvent.click(view.getByText('Confirm')); + expect(mockOnClick).toHaveBeenCalledTimes(1); + }); + + it('renders an image if passed in', () => { + const mockOnClick = jest.fn(); + const { view } = renderView({ + views: [ + { + title: 'Multi view', + image: test, + children: <>View 1, + primaryCta: { + actionType: 'confirm', + children: 'Confirm', + onClick: mockOnClick, + }, + + secondaryCta: { + actionType: 'cancel', + children: 'Cancel', + onClick: mockOnClick, + }, + }, + ], + }); + + view.getByAltText('test'); }); - fireEvent.mouseDown(screen.getByTestId('modal-content')); - expect(onRequestClose.mock.calls.length).toBe(0); }); }); diff --git a/packages/styleguide/src/lib/Molecules/Modals/Modal/Modal.mdx b/packages/styleguide/src/lib/Molecules/Modals/Modal/Modal.mdx index 23412a45b87..be4dcf5aa50 100644 --- a/packages/styleguide/src/lib/Molecules/Modals/Modal/Modal.mdx +++ b/packages/styleguide/src/lib/Molecules/Modals/Modal/Modal.mdx @@ -26,27 +26,6 @@ export const parameters = { Use a Modal to create a dialog on sits on top of a full screen overlay. -## Customizing close behavior - -### Creating a custom close button - -By default, the Modal will render a close button. -You can disable the default close button with the `hideCloseButton` prop by setting it to false. -You can then use the `isOpen` prop to create other controls that toggle the Modal on and off. - - - -### Alternative closing methods - -Other than creating buttons that can close the Modal, there are other ways a Modal can be closed by the user. - -1. Clicking the content outside of the Modal -2. Clicking the escape key on their keyboard - -All of these methods default to `true` for accessibility reasons, and rely on setting the required `onRequestClose` prop and making sure it toggles the `isOpen` prop on the Modal. - - - ## Size variants ### Fluid @@ -73,25 +52,67 @@ A Modal with `size` `'small'` has a width of `400px` and a minimum height of `17 +## Close behavior + +### Creating a custom close button + +By default, the Modal will render a close button. +You can disable the default close button with the `hideCloseButton` prop by setting it to false. +You can then use the `isOpen` prop to create other controls that toggle the Modal on and off. + + + +### Alternative closing methods + +Other than creating buttons that can close the Modal, there are other ways a Modal can be closed by the user. + +1. Clicking the content outside of the Modal +2. Clicking the escape key on their keyboard + +All of these methods default to `true` for accessibility reasons, and rely on setting the required `onRequestClose` prop and making sure it toggles the `isOpen` prop on the Modal. + + + ## Scrollable A Modal can be made scrollable by including large content inside. +## Adding an image + +The `image` prop can be used to include a full-width product image or illustration with a 16:9 aspect ratio. This image should help clarify the message of the modal. + +Overflow for images will be cut off, so make sure the image is correctly sized for the modal. + + + ## Multiple views -A single Modal can be used to navigate between different content within itself. +A single `Modal` can be used to navigate between different content within itself by passing an array of `views` including `children`, `primaryCta`, and `secondaryCta`. + +Each view can also take a `title` that renders as a heading. However, if the `Modal` already has a `title` prop passed then that value will be the title rendered instead of the view's `title`. + +`primaryCta` and `secondaryCta` both take an `actionType` prop to determine the action of the button: + +- `primaryCta` can be `actionType="next"` to move to the next view or `actionType="confirm"` to do some action on the final view. +- `secondaryCta` can be `actionType="back"` to move to the previous view or `actionType="cancel"` to close the modal. + +Try clicking all the buttons in the example to see them in action: -## Adding an image +### Disabled -The `image` prop can be used to include a full-width product image or illustration with a 16:9 aspect ratio. This image should help clarify the message of the modal. +The `primaryCta` and `secondaryCta` can be disabled, via the `disabled` prop, based on some logic or until some action has been done. Here the Next button is disabled until the checkbox is checked. -Overflow for images will be cut off, so make sure the image is correctly sized for the modal. + - +### Danger variant + +The `primaryCta` with `actionType="confirm"` can add a `danger` variant, which will change the color of the button to red. This is useful for actions that are destructive or irreversible. + + ## Playground diff --git a/packages/styleguide/src/lib/Molecules/Modals/Modal/Modal.stories.tsx b/packages/styleguide/src/lib/Molecules/Modals/Modal/Modal.stories.tsx index fa3cabcc9e4..6423fcc6775 100644 --- a/packages/styleguide/src/lib/Molecules/Modals/Modal/Modal.stories.tsx +++ b/packages/styleguide/src/lib/Molecules/Modals/Modal/Modal.stories.tsx @@ -1,15 +1,23 @@ -import { Box, FillButton, FlexBox, Modal, Text } from '@codecademy/gamut'; +import { + Box, + Checkbox, + FillButton, + FlexBox, + Modal, + Text, +} from '@codecademy/gamut'; import { CodeCelebration } from '@codecademy/gamut-illustrations'; import type { Meta, StoryObj } from '@storybook/react'; -import { useState } from 'react'; +import React, { useState } from 'react'; +const defaultProps = { + title: 'Modal Modality', + size: 'small' as const, + children: 'Waffles a la modal', +}; const meta: Meta = { component: Modal, - args: { - title: 'Modal Modality', - size: 'small', - children: 'Waffles a la modal', - }, + args: defaultProps, }; export default meta; @@ -34,23 +42,33 @@ export const Default: Story = { render: (args) => , }; -export const CustomClose: Story = { - args: { - hideCloseButton: true, - }, - render: (args) => , +export const CustomClose: React.FC = () => { + const [isOpen, setIsOpen] = useState(false); + return ( + <> + setIsOpen(true)}>Open Modal + setIsOpen(false)} + > + Close the Modal! + + + ); }; -const ClickOutsideExample = (args: WithoutViews) => { +export const ClickOutside: React.FC = () => { const [isOpen, setIsOpen] = useState(false); return ( <> setIsOpen(true)}>Open Modal setIsOpen(false)} - hideCloseButton > setIsOpen(false)}> @@ -62,10 +80,6 @@ const ClickOutsideExample = (args: WithoutViews) => { ); }; -export const ClickOutside: Story = { - render: (args) => , -}; - type StringOrNumber = string | number; const GridContentPlaceholder = ({ @@ -91,81 +105,81 @@ const GridContentPlaceholder = ({ ); }; -const FluidExample = (args: WithoutViews) => { +export const Fluid: React.FC = () => { const [isOpen, setIsOpen] = useState(false); return ( <> setIsOpen(true)}>Open Modal - setIsOpen(false)}> + setIsOpen(false)} + > ); }; -export const Fluid: Story = { - args: { size: 'fluid' }, - render: (args) => , -}; - -const LargeExample = (args: WithoutViews) => { +export const Large: React.FC = () => { const [isOpen, setIsOpen] = useState(false); return ( <> setIsOpen(true)}>Open Modal - setIsOpen(false)}> + setIsOpen(false)} + > ); }; -export const Large: Story = { - args: { size: 'large' }, - render: (args) => , -}; - -const MediumExample = (args: WithoutViews) => { +export const Medium: React.FC = () => { const [isOpen, setIsOpen] = useState(false); return ( <> setIsOpen(true)}>Open Modal - setIsOpen(false)}> + setIsOpen(false)} + > ); }; -export const Medium: Story = { - args: { size: 'medium' }, - render: (args) => , -}; - -const SmallExample = (args: WithoutViews) => { +export const Small: React.FC = () => { const [isOpen, setIsOpen] = useState(false); return ( <> setIsOpen(true)}>Open Modal - setIsOpen(false)}> + setIsOpen(false)} + > ); }; -export const Small: Story = { - args: { size: 'small' }, - render: (args) => , -}; - -const ScrollableExample = (args: WithoutViews) => { +export const Scrollable: React.FC = () => { const [isOpen, setIsOpen] = useState(false); return ( <> setIsOpen(true)}>Open Modal { ); }; -export const Scrollable: Story = { - render: (args) => , +const ImageComponent = () => { + return ( + + + + ); }; -const multipleViewsArgs = { - title: undefined, - hideCloseButton: false, - size: 'medium' as const, - scrollable: true, - views: [ - { - title: 'First view', - nextCta: { children: 'Next' }, - cancelCta: { children: 'Close' }, - children: <>Hey for the first time, - }, - { - title: 'Second view', - nextCta: { children: 'Next' }, - cancelCta: { children: 'Close' }, - children: <>Hey for the second time, - }, - { - title: 'Third view', - confirmCta: { children: 'Done' }, - cancelCta: { children: 'Close' }, - children: <>Last one, - }, - ], +export const WithImage: React.FC = () => { + const [isOpen, setIsOpen] = useState(false); + return ( + <> + setIsOpen(true)}>Open Modal + } + isOpen={isOpen} + onRequestClose={() => setIsOpen(false)} + > + + Optional 1-2 lines of explanation that provides relevant details. + Lorem ipsum cras nulla massa odio ligula. + + + + ); }; -const MultipleViewsExample = (args: React.ComponentProps) => { +export const MultipleViews: React.FC = () => { const [isOpen, setIsOpen] = useState(false); + return ( <> setIsOpen(true)}>Open Modal setIsOpen(false)} + size="medium" + views={[ + { + title: 'First view', + primaryCta: { + actionType: 'next', + children: 'Next', + }, + secondaryCta: { actionType: 'cancel', children: 'Cancel' }, + children: <>Hey for the first time, + }, + { + title: 'Second view', + primaryCta: { actionType: 'next', children: 'Next' }, + secondaryCta: { actionType: 'back', children: 'Back' }, + children: <>Hey for the second time, + }, + { + title: 'Third view', + primaryCta: { + actionType: 'confirm', + children: 'Done', + onClick: () => setIsOpen(false), + }, + secondaryCta: { actionType: 'back', children: 'Back' }, + children: <>Last one, + }, + ]} /> ); }; -export const MultipleViews: Story = { - args: multipleViewsArgs, - render: (args) => , -}; +export const MultipleViewsDanger: React.FC = () => { + const [isOpen, setIsOpen] = useState(false); -const ImageComponent = () => { return ( - - - + <> + setIsOpen(true)}>Open Modal + setIsOpen(false)} + size="medium" + title="Danger!!" + views={[ + { + title: 'First view', + primaryCta: { + actionType: 'confirm', + children: 'Delete', + variant: 'danger', + onClick: () => setIsOpen(false), + }, + secondaryCta: { actionType: 'cancel', children: 'Cancel' }, + children: <>I use the danger variant for the confirm button, + }, + ]} + /> + ); }; -const WithImageExample = (args: WithoutViews) => { +export const MultipleViewsDisabled: React.FC = () => { const [isOpen, setIsOpen] = useState(false); + const [isChecked, setIsChecked] = useState(false); + return ( <> setIsOpen(true)}>Open Modal - setIsOpen(false)}> - - Optional 1-2 lines of explanation that provides relevant details. - Lorem ipsum cras nulla massa odio ligula. - - + setIsOpen(false)} + size="medium" + views={[ + { + title: 'First view', + primaryCta: { + actionType: 'next', + children: 'Next', + disabled: !isChecked, + }, + secondaryCta: { actionType: 'cancel', children: 'Cancel' }, + children: ( + <> + setIsChecked(!isChecked)} + /> + + ), + }, + { + title: 'Second view', + primaryCta: { + actionType: 'confirm', + children: 'Done', + onClick: () => setIsOpen(false), + }, + secondaryCta: { actionType: 'back', children: 'Back' }, + children: <>Last one, + }, + ]} + /> ); }; - -export const WithImage: Story = { - render: (args) => } />, -}; From 75cd2aefaad58dc6696ad493227dd69ab6c80d84 Mon Sep 17 00:00:00 2001 From: codecademydev Date: Mon, 21 Apr 2025 14:36:59 +0000 Subject: [PATCH 29/38] chore(release): publish - @codecademy/gamut@62.0.0 - @codecademy/gamut-kit@0.6.497 - @codecademy/styleguide@73.0.0 --- packages/gamut-kit/CHANGELOG.md | 4 ++++ packages/gamut-kit/package.json | 4 ++-- packages/gamut/CHANGELOG.md | 10 ++++++++++ packages/gamut/package.json | 2 +- packages/styleguide/CHANGELOG.md | 10 ++++++++++ packages/styleguide/package.json | 2 +- 6 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/gamut-kit/CHANGELOG.md b/packages/gamut-kit/CHANGELOG.md index 5211239c02a..20d4d095852 100644 --- a/packages/gamut-kit/CHANGELOG.md +++ b/packages/gamut-kit/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [0.6.497](https://github.com/Codecademy/gamut/compare/@codecademy/gamut-kit@0.6.496...@codecademy/gamut-kit@0.6.497) (2025-04-21) + +**Note:** Version bump only for package @codecademy/gamut-kit + ### [0.6.496](https://github.com/Codecademy/gamut/compare/@codecademy/gamut-kit@0.6.495...@codecademy/gamut-kit@0.6.496) (2025-04-10) **Note:** Version bump only for package @codecademy/gamut-kit diff --git a/packages/gamut-kit/package.json b/packages/gamut-kit/package.json index 5b67b44651c..dc5f313b0f6 100644 --- a/packages/gamut-kit/package.json +++ b/packages/gamut-kit/package.json @@ -1,10 +1,10 @@ { "name": "@codecademy/gamut-kit", "description": "Styleguide & Component library for Codecademy", - "version": "0.6.496", + "version": "0.6.497", "author": "Codecademy Engineering ", "dependencies": { - "@codecademy/gamut": "61.1.2", + "@codecademy/gamut": "62.0.0", "@codecademy/gamut-icons": "9.42.0", "@codecademy/gamut-illustrations": "0.54.1", "@codecademy/gamut-patterns": "0.10.7", diff --git a/packages/gamut/CHANGELOG.md b/packages/gamut/CHANGELOG.md index 5f5bb13eaa0..7e580aa6a95 100644 --- a/packages/gamut/CHANGELOG.md +++ b/packages/gamut/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [62.0.0](https://github.com/Codecademy/gamut/compare/@codecademy/gamut@61.1.2...@codecademy/gamut@62.0.0) (2025-04-21) + +### ⚠ BREAKING CHANGES + +- **Modal:** add multiview options for back and danger variant + +### Features + +- **Modal:** add multiview options for back and danger variant ([567dcac](https://github.com/Codecademy/gamut/commit/567dcacf35a7a3bc9e2b6574693d0eebfabff906)) + ### [61.1.2](https://github.com/Codecademy/gamut/compare/@codecademy/gamut@61.1.1...@codecademy/gamut@61.1.2) (2025-04-10) **Note:** Version bump only for package @codecademy/gamut diff --git a/packages/gamut/package.json b/packages/gamut/package.json index fd433686d83..79b4294822a 100644 --- a/packages/gamut/package.json +++ b/packages/gamut/package.json @@ -1,7 +1,7 @@ { "name": "@codecademy/gamut", "description": "Styleguide & Component library for Codecademy", - "version": "61.1.2", + "version": "62.0.0", "author": "Codecademy Engineering ", "dependencies": { "@codecademy/gamut-icons": "9.42.0", diff --git a/packages/styleguide/CHANGELOG.md b/packages/styleguide/CHANGELOG.md index e5bfb77c665..8a94f3fe6f7 100644 --- a/packages/styleguide/CHANGELOG.md +++ b/packages/styleguide/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [73.0.0](https://github.com/Codecademy/gamut/compare/@codecademy/styleguide@72.1.0...@codecademy/styleguide@73.0.0) (2025-04-21) + +### ⚠ BREAKING CHANGES + +- **Modal:** add multiview options for back and danger variant + +### Features + +- **Modal:** add multiview options for back and danger variant ([567dcac](https://github.com/Codecademy/gamut/commit/567dcacf35a7a3bc9e2b6574693d0eebfabff906)) + ## [72.1.0](https://github.com/Codecademy/gamut/compare/@codecademy/styleguide@72.0.0...@codecademy/styleguide@72.1.0) (2025-04-08) ### Features diff --git a/packages/styleguide/package.json b/packages/styleguide/package.json index 4779d0f26b8..300488510c4 100644 --- a/packages/styleguide/package.json +++ b/packages/styleguide/package.json @@ -1,7 +1,7 @@ { "name": "@codecademy/styleguide", "description": "Styleguide & Component library for codecademy.com", - "version": "72.1.0", + "version": "73.0.0", "author": "Codecademy Engineering", "license": "MIT", "publishConfig": { From 97e6c09a27eca2e471b7951cd1f45e6cee48fe3d Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Mon, 21 Apr 2025 11:07:19 -0400 Subject: [PATCH 30/38] fix(modal + dialog): updating aria attributes per a11y team feedback Applying changes to the Modal and Dialog components per feedback from a11y team. Also adding a zIndex --- packages/gamut/src/BodyPortal/index.tsx | 17 +++++++++-- packages/gamut/src/Modals/Dialog.tsx | 14 ++++++--- packages/gamut/src/Modals/Modal.tsx | 30 +++++++++++-------- packages/gamut/src/Overlay/index.tsx | 8 ++++- .../src/lib/Molecules/Modals/Modal/Modal.mdx | 2 +- 5 files changed, 49 insertions(+), 22 deletions(-) diff --git a/packages/gamut/src/BodyPortal/index.tsx b/packages/gamut/src/BodyPortal/index.tsx index 6b8f00d528d..fec975b8c80 100644 --- a/packages/gamut/src/BodyPortal/index.tsx +++ b/packages/gamut/src/BodyPortal/index.tsx @@ -9,7 +9,6 @@ const PortalWrapper = styled .div( system.css({ position: 'absolute', - zIndex: 1, top: 0, left: 0, right: 0, @@ -18,7 +17,19 @@ const PortalWrapper = styled ) .withComponent(ColorMode); -export const BodyPortal: React.FC = ({ children }) => { +interface BodyPortalProps { + /** + * TEMPORARY: a stopgap solution to avoid zIndex conflicts - + * will be reworked with: GM-624 + * previously, zIndex was set to 1 in the CSS function + */ + zIndex?: number; +} + +export const BodyPortal: React.FC> = ({ + children, + zIndex = 1, +}) => { const [ready, setReady] = useState(false); const mode = useCurrentMode(); @@ -30,7 +41,7 @@ export const BodyPortal: React.FC = ({ children }) => { if (!ready) return null; return ReactDOM.createPortal( - + {children} , document.body diff --git a/packages/gamut/src/Modals/Dialog.tsx b/packages/gamut/src/Modals/Dialog.tsx index 33933e7fea8..b3fc206f45a 100644 --- a/packages/gamut/src/Modals/Dialog.tsx +++ b/packages/gamut/src/Modals/Dialog.tsx @@ -26,6 +26,11 @@ export interface DialogProps extends ModalBaseProps { confirmCta: DialogButtonProps; cancelCta?: DialogButtonProps; closeButtonTipText?: string; + /** + * TEMPORARY: a stopgap solution to avoid zIndex conflicts - + * will be reworked with: GM-624 + */ + zIndex?: number; } export const Dialog: React.FC = ({ @@ -57,14 +62,15 @@ export const Dialog: React.FC = ({ return ( void} {...rest}> {title} diff --git a/packages/gamut/src/Modals/Modal.tsx b/packages/gamut/src/Modals/Modal.tsx index e03efc6368b..38aa9a66b4b 100644 --- a/packages/gamut/src/Modals/Modal.tsx +++ b/packages/gamut/src/Modals/Modal.tsx @@ -86,13 +86,17 @@ export interface MultiViewModalProps * Whether to disable X button at top right of modal */ closeDisabled?: boolean; + /** + * TEMPORARY: a stopgap solution to avoid zIndex conflicts - + * will be reworked with: GM-624 + */ + zIndex?: number; } export type ModalProps = SingleViewModalProps | MultiViewModalProps; export const Modal: React.FC = ({ 'aria-label': ariaLabel, - 'aria-live': ariaLive = 'polite', children, className, headingLevel = 'h2', @@ -109,6 +113,7 @@ export const Modal: React.FC = ({ const view = views?.[currentView]; const image = (view?.image || rest?.image) ?? null; + const titleText = title || views?.[currentView].title; return ( = ({ {...rest} > 0 ? 'dialog' : 'standard'} - data-autofocus - className={className} - aria-modal="true" - aria-live={ariaLive} - aria-label={ariaLabel} aria-hidden="false" + aria-label={ariaLabel} + aria-labelledby={titleText ? String(titleText) : undefined} + aria-modal="true" + className={className} + data-autofocus + layout={views && views?.length > 0 ? 'dialog' : 'standard'} + role="dialog" + size={size} + tabIndex={-1} > - {(title || view?.title) && ( + {titleText && ( - {title || view?.title} + {titleText} )} {!hideCloseButton && ( diff --git a/packages/gamut/src/Overlay/index.tsx b/packages/gamut/src/Overlay/index.tsx index 0eb548a9043..d1839cfb367 100644 --- a/packages/gamut/src/Overlay/index.tsx +++ b/packages/gamut/src/Overlay/index.tsx @@ -33,6 +33,11 @@ export type OverlayProps = { shroud?: boolean; /** Whether the overlay allows scroll */ allowScroll?: boolean; + /** + * TEMPORARY: a stopgap solution to avoid zIndex conflicts - + * will be reworked with: GM-624 + */ + zIndex?: number; }; const OverlayContainer = styled(FlexBox)( @@ -56,6 +61,7 @@ export const Overlay: React.FC = ({ onRequestClose, isOpen, allowScroll = false, + zIndex = 1, }) => { const handleOutsideClick = useCallback(() => { if (clickOutsideCloses) { @@ -94,5 +100,5 @@ export const Overlay: React.FC = ({ if (inline) return content; - return {content}; + return {content}; }; diff --git a/packages/styleguide/src/lib/Molecules/Modals/Modal/Modal.mdx b/packages/styleguide/src/lib/Molecules/Modals/Modal/Modal.mdx index be4dcf5aa50..3a1d9630839 100644 --- a/packages/styleguide/src/lib/Molecules/Modals/Modal/Modal.mdx +++ b/packages/styleguide/src/lib/Molecules/Modals/Modal/Modal.mdx @@ -10,7 +10,7 @@ export const parameters = { type: 'figma', url: 'https://www.figma.com/file/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=2449%3A3770', }, - status: 'updating', + status: 'current', source: { repo: 'gamut', githubLink: From 588ac4ba4f89aea53d14005a3f4062a057f08fac Mon Sep 17 00:00:00 2001 From: codecademydev Date: Mon, 21 Apr 2025 15:11:52 +0000 Subject: [PATCH 31/38] chore(release): publish - @codecademy/gamut@62.0.1 - @codecademy/gamut-kit@0.6.498 - @codecademy/styleguide@73.0.1 --- packages/gamut-kit/CHANGELOG.md | 4 ++++ packages/gamut-kit/package.json | 4 ++-- packages/gamut/CHANGELOG.md | 6 ++++++ packages/gamut/package.json | 2 +- packages/styleguide/CHANGELOG.md | 6 ++++++ packages/styleguide/package.json | 2 +- 6 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/gamut-kit/CHANGELOG.md b/packages/gamut-kit/CHANGELOG.md index 20d4d095852..1db42f9d610 100644 --- a/packages/gamut-kit/CHANGELOG.md +++ b/packages/gamut-kit/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [0.6.498](https://github.com/Codecademy/gamut/compare/@codecademy/gamut-kit@0.6.497...@codecademy/gamut-kit@0.6.498) (2025-04-21) + +**Note:** Version bump only for package @codecademy/gamut-kit + ### [0.6.497](https://github.com/Codecademy/gamut/compare/@codecademy/gamut-kit@0.6.496...@codecademy/gamut-kit@0.6.497) (2025-04-21) **Note:** Version bump only for package @codecademy/gamut-kit diff --git a/packages/gamut-kit/package.json b/packages/gamut-kit/package.json index dc5f313b0f6..6ef55f6e25e 100644 --- a/packages/gamut-kit/package.json +++ b/packages/gamut-kit/package.json @@ -1,10 +1,10 @@ { "name": "@codecademy/gamut-kit", "description": "Styleguide & Component library for Codecademy", - "version": "0.6.497", + "version": "0.6.498", "author": "Codecademy Engineering ", "dependencies": { - "@codecademy/gamut": "62.0.0", + "@codecademy/gamut": "62.0.1", "@codecademy/gamut-icons": "9.42.0", "@codecademy/gamut-illustrations": "0.54.1", "@codecademy/gamut-patterns": "0.10.7", diff --git a/packages/gamut/CHANGELOG.md b/packages/gamut/CHANGELOG.md index 7e580aa6a95..42a70ede173 100644 --- a/packages/gamut/CHANGELOG.md +++ b/packages/gamut/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [62.0.1](https://github.com/Codecademy/gamut/compare/@codecademy/gamut@62.0.0...@codecademy/gamut@62.0.1) (2025-04-21) + +### Bug Fixes + +- **modal + dialog:** updating aria attributes per a11y team feedback ([36b0fd0](https://github.com/Codecademy/gamut/commit/36b0fd0524aba879d2dee23be589933748acde88)) + ## [62.0.0](https://github.com/Codecademy/gamut/compare/@codecademy/gamut@61.1.2...@codecademy/gamut@62.0.0) (2025-04-21) ### ⚠ BREAKING CHANGES diff --git a/packages/gamut/package.json b/packages/gamut/package.json index 79b4294822a..cfc7802f333 100644 --- a/packages/gamut/package.json +++ b/packages/gamut/package.json @@ -1,7 +1,7 @@ { "name": "@codecademy/gamut", "description": "Styleguide & Component library for Codecademy", - "version": "62.0.0", + "version": "62.0.1", "author": "Codecademy Engineering ", "dependencies": { "@codecademy/gamut-icons": "9.42.0", diff --git a/packages/styleguide/CHANGELOG.md b/packages/styleguide/CHANGELOG.md index 8a94f3fe6f7..e15f28690cb 100644 --- a/packages/styleguide/CHANGELOG.md +++ b/packages/styleguide/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [73.0.1](https://github.com/Codecademy/gamut/compare/@codecademy/styleguide@73.0.0...@codecademy/styleguide@73.0.1) (2025-04-21) + +### Bug Fixes + +- **modal + dialog:** updating aria attributes per a11y team feedback ([36b0fd0](https://github.com/Codecademy/gamut/commit/36b0fd0524aba879d2dee23be589933748acde88)) + ## [73.0.0](https://github.com/Codecademy/gamut/compare/@codecademy/styleguide@72.1.0...@codecademy/styleguide@73.0.0) (2025-04-21) ### ⚠ BREAKING CHANGES diff --git a/packages/styleguide/package.json b/packages/styleguide/package.json index 300488510c4..a95f7e08993 100644 --- a/packages/styleguide/package.json +++ b/packages/styleguide/package.json @@ -1,7 +1,7 @@ { "name": "@codecademy/styleguide", "description": "Styleguide & Component library for codecademy.com", - "version": "73.0.0", + "version": "73.0.1", "author": "Codecademy Engineering", "license": "MIT", "publishConfig": { From 6f2f7a0d10b7a28fe0c68e5ace0632ae3e7c01cd Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Tue, 22 Apr 2025 10:50:30 -0400 Subject: [PATCH 32/38] cleaned up aria-controls --- packages/gamut/src/DataList/Controls/ExpandControl.tsx | 1 - packages/gamut/src/Drawer/index.tsx | 8 -------- packages/gamut/src/Flyout/index.tsx | 7 ------- packages/gamut/src/List/ListRow.tsx | 10 ++-------- packages/gamut/src/Tip/InfoTip/index.tsx | 4 ---- packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx | 4 +--- .../styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx | 4 +--- .../styleguide/src/lib/Molecules/Flyout/Flyout.mdx | 4 +--- .../src/lib/Molecules/Flyout/Flyout.stories.tsx | 5 +---- 9 files changed, 6 insertions(+), 41 deletions(-) diff --git a/packages/gamut/src/DataList/Controls/ExpandControl.tsx b/packages/gamut/src/DataList/Controls/ExpandControl.tsx index f2620bf5b98..657fc45c57e 100644 --- a/packages/gamut/src/DataList/Controls/ExpandControl.tsx +++ b/packages/gamut/src/DataList/Controls/ExpandControl.tsx @@ -32,7 +32,6 @@ export const ExpandControl: React.FC = ({ }} aria-label={`Expand ${id} Row`} aria-expanded={expanded} - aria-controls={id ?? undefined} > diff --git a/packages/gamut/src/Drawer/index.tsx b/packages/gamut/src/Drawer/index.tsx index 0a1de54ba74..44d9d958934 100644 --- a/packages/gamut/src/Drawer/index.tsx +++ b/packages/gamut/src/Drawer/index.tsx @@ -15,23 +15,16 @@ export interface DrawerProps extends Omit { * Whether the drawer should be open. */ expanded?: boolean; - /** * Which edge of the drawer content container is aligned to during the animation. */ alignContentContainer?: 'left' | 'right'; - /** - * This `id` is used to link the element that expands the Drawer to the Drawer itself. - * It is needed for the `aria-controls` attribute to work properly for accessibility. - */ - id: string; } export const Drawer: React.FC = ({ alignContentContainer = 'right', children, expanded, - id, ...props }) => { const drawerRef = useRef(null); @@ -49,7 +42,6 @@ export const Drawer: React.FC = ({ animate={{ width: fullWidth }} bg="background-current" exit={{ width: 0 }} - id={id} initial={{ width: 0 }} overflow="clip" position="relative" diff --git a/packages/gamut/src/Flyout/index.tsx b/packages/gamut/src/Flyout/index.tsx index dca1fa19020..1c901ed2164 100644 --- a/packages/gamut/src/Flyout/index.tsx +++ b/packages/gamut/src/Flyout/index.tsx @@ -35,18 +35,12 @@ export interface FlyoutProps extends WithChildrenProp { */ title: React.ReactNode; bg?: Colors; - /** - * This `id` is used to link the element that expands the Drawer to the Drawer itself. - * It is needed for the `aria-controls` attribute to work properly for accessibility. - */ - id: string; } export const Flyout: React.FC = ({ bg = 'background', children, closeLabel = 'Close', - id, expanded, openFrom = 'left', onClose, @@ -67,7 +61,6 @@ export const Flyout: React.FC = ({ display="flex" expanded={expanded} flexDirection={openFrom === 'left' ? 'row' : 'row-reverse'} - id={id} position="fixed" top={0} {...{ [openFrom]: 0 }} diff --git a/packages/gamut/src/List/ListRow.tsx b/packages/gamut/src/List/ListRow.tsx index 976da0df534..ad90ab8561c 100644 --- a/packages/gamut/src/List/ListRow.tsx +++ b/packages/gamut/src/List/ListRow.tsx @@ -14,8 +14,6 @@ import { getGridTemplateColumns } from './utils'; export interface RowProps extends Partial>> { header?: boolean; - /** Used to link expandable content with the component that does the expanding, i.e. it's used to set the value for aria-controls */ - id?: string; /** This is an internal prop that is largely only used for the DataTable component */ numOfColumns?: number; /** This is an internal prop that is largely only used for the DataTable component */ @@ -47,13 +45,12 @@ const DivExpand = styled(motion.div)(expandStyles); const TDExpand = styled(motion.td)(expandStyles); const ExpandInCollapseOut: React.FC< - WithChildrenProp & { as: 'td' | 'div'; id: string | undefined } -> = ({ as, children, id }) => { + WithChildrenProp & { as: 'td' | 'div'; } +> = ({ as, children }) => { const ResponsiveExpand = as === 'td' ? TDExpand : DivExpand; return ( ( ( { - id, children, expanded, expandedRowAriaLabel, @@ -105,7 +101,6 @@ export const ListRow = forwardRef( ( {expanded && ( {renderExpanded?.()} diff --git a/packages/gamut/src/Tip/InfoTip/index.tsx b/packages/gamut/src/Tip/InfoTip/index.tsx index fc51cd68b1d..954edd3e174 100644 --- a/packages/gamut/src/Tip/InfoTip/index.tsx +++ b/packages/gamut/src/Tip/InfoTip/index.tsx @@ -88,8 +88,6 @@ export const InfoTip: React.FC = ({ const Tip = loaded && isFloating ? FloatingTip : InlineTip; - const textId = useId(); - const tipProps = { alignment, escapeKeyPressHandler, @@ -103,7 +101,6 @@ export const InfoTip: React.FC = ({ {!isTipHidden ? info : `\xa0`} @@ -112,7 +109,6 @@ export const InfoTip: React.FC = ({ const tip = ( F Our Drawers are [controlled components](https://reactjs.org/docs/forms.html#controlled-components), so their checked value must be controlled by an external state passed in as `expanded`. -The component that toggles the `Drawer` should include both `aria-expanded` and `aria-controls` attributes. The `aria-controls` attribute must reference the `id` of the `Drawer`. The `aria-expanded` attribute should reflect the value of the `expanded` prop passed to the `Drawer`. +The component that toggles the `Drawer` should include the `aria-expanded` attribute. The `aria-expanded` attribute should reflect the value of the `expanded` prop passed to the `Drawer`. In the example below, the `StrokeButton` controls the expansion of the `Drawer`, so the button has the `aria-expanded` attribute. ```tsx const ExampleDrawer: React.FC = () => { const [expanded, setExpanded] = useState(false); - const drawerId = useId(); return ( @@ -47,7 +46,6 @@ const ExampleDrawer: React.FC = () => { Example Drawer setExpanded((previousExpanded) => !previousExpanded)} > diff --git a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx index 310eaee1c5d..f30170dc7dd 100644 --- a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx +++ b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx @@ -11,14 +11,12 @@ export default meta; export const Default: React.FC = () => { const [expanded, setExpanded] = useState(false); - const drawerId = useId(); return ( - + Drawer content in here! setExpanded((previousExpanded) => !previousExpanded)} > diff --git a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx index 70eef64fd66..89afe953e0d 100644 --- a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx +++ b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx @@ -30,12 +30,11 @@ Internally, Flyout is a combination of Ove Our Flyouts are [controlled components](https://reactjs.org/docs/forms.html#controlled-components), so their checked value must be controlled by an external state passed in as `expanded` and `onChange`. -The component that toggles the `Flyout` should include both `aria-expanded` and `aria-controls` attributes. The `aria-controls` attribute must reference the `id` of the `Flyout`. The `aria-expanded` attribute should reflect the value of the `expanded` prop passed to the `Flyout`. +The component that toggles the `Flyout` should include the `aria-expanded` attribute. The `aria-expanded` attribute should reflect the value of the `expanded` prop passed to the `Flyout`. In the example below, the `StrokeButton` controls the expansion of the `Flyout`, so the button has the `aria-expanded` attribute. ```tsx const ExampleFlyout: React.FC = () => { const [expanded, setExpanded] = useState(false); - const flyoutId = useId(); return ( @@ -44,7 +43,6 @@ const ExampleFlyout: React.FC = () => { setExpanded((previousExpanded) => !previousExpanded)} > Set the proper aria-labels! diff --git a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx index e40fcfd7557..c373b010c93 100644 --- a/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx +++ b/packages/styleguide/src/lib/Molecules/Flyout/Flyout.stories.tsx @@ -5,9 +5,7 @@ import { useState } from 'react'; const meta: Meta = { component: Flyout, - args: { - id: 'flyout-example', - }, + args: {}, }; export default meta; @@ -38,7 +36,6 @@ export const FlyoutExample: Story = { setExpanded(true)} > From bb014625e9da5747d64253a2d6698aa2a10b5b7a Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Tue, 22 Apr 2025 10:58:39 -0400 Subject: [PATCH 33/38] more cleanup --- packages/gamut/src/List/ListRow.tsx | 2 +- packages/gamut/src/Tip/InfoTip/index.tsx | 2 +- packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/gamut/src/List/ListRow.tsx b/packages/gamut/src/List/ListRow.tsx index ad90ab8561c..81841a6c74e 100644 --- a/packages/gamut/src/List/ListRow.tsx +++ b/packages/gamut/src/List/ListRow.tsx @@ -45,7 +45,7 @@ const DivExpand = styled(motion.div)(expandStyles); const TDExpand = styled(motion.td)(expandStyles); const ExpandInCollapseOut: React.FC< - WithChildrenProp & { as: 'td' | 'div'; } + WithChildrenProp & { as: 'td' | 'div' } > = ({ as, children }) => { const ResponsiveExpand = as === 'td' ? TDExpand : DivExpand; diff --git a/packages/gamut/src/Tip/InfoTip/index.tsx b/packages/gamut/src/Tip/InfoTip/index.tsx index 954edd3e174..ab639c629b8 100644 --- a/packages/gamut/src/Tip/InfoTip/index.tsx +++ b/packages/gamut/src/Tip/InfoTip/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useId, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { FloatingTip } from '../shared/FloatingTip'; import { InlineTip } from '../shared/InlineTip'; diff --git a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx index f30170dc7dd..41d2631c15e 100644 --- a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx +++ b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx @@ -1,6 +1,6 @@ import { Drawer, FlexBox, StrokeButton } from '@codecademy/gamut'; import type { Meta } from '@storybook/react'; -import { useId, useState } from 'react'; +import { useState } from 'react'; const meta: Meta = { component: Drawer, From 118fffff4d4d2ba4f791fc1450dfe8f045ae82c5 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Tue, 22 Apr 2025 11:11:27 -0400 Subject: [PATCH 34/38] formatted --- packages/gamut/src/List/ListRow.tsx | 4 +--- packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/gamut/src/List/ListRow.tsx b/packages/gamut/src/List/ListRow.tsx index 81841a6c74e..45a86463b84 100644 --- a/packages/gamut/src/List/ListRow.tsx +++ b/packages/gamut/src/List/ListRow.tsx @@ -141,9 +141,7 @@ export const ListRow = forwardRef( {content} {expanded && ( - + {renderExpanded?.()} diff --git a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx index 41d2631c15e..1e189305290 100644 --- a/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx +++ b/packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx @@ -13,9 +13,7 @@ export const Default: React.FC = () => { const [expanded, setExpanded] = useState(false); return ( - - Drawer content in here! - + Drawer content in here! setExpanded((previousExpanded) => !previousExpanded)} From 5f8463df50a324f3243fccedffbbecb0b3c005a3 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Tue, 22 Apr 2025 16:51:22 -0400 Subject: [PATCH 35/38] clean up list stories --- .../src/lib/Organisms/Lists & Tables/List/List.mdx | 6 ------ .../src/lib/Organisms/Lists & Tables/List/List.stories.tsx | 3 --- 2 files changed, 9 deletions(-) diff --git a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.mdx b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.mdx index f84977ffeb0..eaf403e57f0 100644 --- a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.mdx +++ b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.mdx @@ -217,7 +217,6 @@ You can define collapsible content by passing an `expanded` prop and the `React. #### Expand on button click The `ListRow` component has access to the `ExpandControl` component which can be used to toggle the expanded state of the row. The `ExpandControl` component will automatically handle the rotation of the icon based on the expanded state. -Ensure that the `ListRow` has a unique `id` prop to ensure that the `ExpandControl` can set the correct aria attributes. ```tsx export const ExpandableRow: React.FC<{ @@ -229,7 +228,6 @@ export const ExpandableRow: React.FC<{ return ( Surprise} @@ -240,7 +238,6 @@ export const ExpandableRow: React.FC<{ setExpanded(!isExpanded)} - id={idOfRow} disabled={false} /> @@ -251,8 +248,6 @@ export const ExpandableRow: React.FC<{ #### Expand on row click -Make sure you pass in an `id` prop to the `ListRow` component so that the correct aria attributes are set on the row. - ```tsx export const ExpandableRow: React.FC<{ header: string; @@ -263,7 +258,6 @@ export const ExpandableRow: React.FC<{ return ( setExpanded(!isExpanded)} diff --git a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx index 324141b65e7..e5afe77980e 100644 --- a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx +++ b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx @@ -648,7 +648,6 @@ const ExpandableButtonClickRow: React.FC<{ return ( } @@ -659,7 +658,6 @@ const ExpandableButtonClickRow: React.FC<{ setExpanded(!isExpanded)} - id={`${name}-${role}-${ship}`} disabled={false} /> @@ -701,7 +699,6 @@ export const ExpandableRowClick: React.FC = ({ expanded={isExpanded} key={key} onClick={() => setExpanded(!isExpanded)} - id={`${name}-${role}-${ship}`} renderExpanded={() => } > From 6faca2484955ce6eaa5db671af64f1d60c4f3a27 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Tue, 22 Apr 2025 16:53:47 -0400 Subject: [PATCH 36/38] more clean up --- packages/gamut/src/DataList/Tables/Rows/TableRow.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx b/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx index 0db757f2b80..38784942c45 100644 --- a/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx +++ b/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx @@ -70,7 +70,6 @@ export const TableRow: DataRow = ({ return ( Date: Thu, 24 Apr 2025 11:48:21 -0400 Subject: [PATCH 37/38] update chevron color --- packages/gamut/src/DataList/Controls/ExpandControl.tsx | 2 +- .../src/lib/Organisms/Lists & Tables/List/List.stories.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gamut/src/DataList/Controls/ExpandControl.tsx b/packages/gamut/src/DataList/Controls/ExpandControl.tsx index 657fc45c57e..cc5058f9000 100644 --- a/packages/gamut/src/DataList/Controls/ExpandControl.tsx +++ b/packages/gamut/src/DataList/Controls/ExpandControl.tsx @@ -34,7 +34,7 @@ export const ExpandControl: React.FC = ({ aria-expanded={expanded} > - + diff --git a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx index e5afe77980e..89ed8956993 100644 --- a/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx +++ b/packages/styleguide/src/lib/Organisms/Lists & Tables/List/List.stories.tsx @@ -705,7 +705,7 @@ export const ExpandableRowClick: React.FC = ({ - + From 8a23092cfff87932b230fbe06c60fb3c24981c93 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Thu, 24 Apr 2025 12:01:24 -0400 Subject: [PATCH 38/38] formatted --- packages/gamut/src/DataList/Controls/ExpandControl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gamut/src/DataList/Controls/ExpandControl.tsx b/packages/gamut/src/DataList/Controls/ExpandControl.tsx index cc5058f9000..20123a3d1b4 100644 --- a/packages/gamut/src/DataList/Controls/ExpandControl.tsx +++ b/packages/gamut/src/DataList/Controls/ExpandControl.tsx @@ -34,7 +34,7 @@ export const ExpandControl: React.FC = ({ aria-expanded={expanded} > - +