From ae8dd5d8646067121aeecc7ebec61d1623f04c8b Mon Sep 17 00:00:00 2001 From: Richard Dinh <1038306+flacoman91@users.noreply.github.com> Date: Wed, 13 May 2026 15:20:57 -0700 Subject: [PATCH 1/6] Update Layout stories, styles, tests, and local DS documentation. --- docs/local-cfpb-ds.md | 47 +++++ .../Layout/layout-content.stories.tsx | 67 +++++-- src/components/Layout/layout-main.stories.tsx | 133 ++++++++------ .../Layout/layout-sidebar.stories.tsx | 20 +-- .../Layout/layout-wrapper.stories.tsx | 19 +- src/components/Layout/layout.scss | 57 ++++++ src/components/Layout/layout.test.tsx | 169 ++++++++++++++++++ 7 files changed, 433 insertions(+), 79 deletions(-) create mode 100644 docs/local-cfpb-ds.md create mode 100644 src/components/Layout/layout.test.tsx diff --git a/docs/local-cfpb-ds.md b/docs/local-cfpb-ds.md new file mode 100644 index 0000000000..5f62aff597 --- /dev/null +++ b/docs/local-cfpb-ds.md @@ -0,0 +1,47 @@ +# Pointing design-system-react at a local `@cfpb/cfpb-design-system` + +Use this when you have the [cfpb/design-system](https://github.com/cfpb/design-system) repo cloned next to this repo and want Storybook/tests to use your branch (e.g. layout fixes) before a release. + +## Layout + +Assume sibling directories: + +```text +projects/ + design-system/ # monorepo root; package lives in packages/cfpb-design-system/ + design-system-react/ # this repo +``` + +If your paths differ, adjust the `portal:` URL below. + +## Yarn (Berry) + +In **design-system-react** `package.json`, temporarily set the devDependency to the **portal** protocol (live symlink to source): + +```json +"@cfpb/cfpb-design-system": "portal:../design-system/packages/cfpb-design-system" +``` + +Then from **design-system-react**: + +```bash +yarn install +yarn storybook +# optional +yarn test +``` + +`portal:` keeps the dependency wired to your clone so SCSS/JS changes in `design-system` show up after save (no publish step). + +## After you’re done + +1. Remove the `portal:` line and restore the published version (e.g. `"5.3.2"`). +2. Run `yarn install` again. + +## Optional: trim duplicate Layout CSS here + +`src/components/Layout/layout.scss` in this repo duplicates some rules that belong in the DS once your PR ships. After you adopt a released `@cfpb/cfpb-design-system` that includes the layout fix, consider removing the overlapping blocks from `layout.scss` so overrides stay minimal. + +## Alternative: `yarn link` + +From `design-system/packages/cfpb-design-system` you can `yarn link`, then in design-system-react `yarn link @cfpb/cfpb-design-system`. Portal is usually simpler in a Yarn workspaces/monorepo workflow. diff --git a/src/components/Layout/layout-content.stories.tsx b/src/components/Layout/layout-content.stories.tsx index 48f42abed0..16106853a7 100644 --- a/src/components/Layout/layout-content.stories.tsx +++ b/src/components/Layout/layout-content.stories.tsx @@ -52,8 +52,17 @@ export const Content: Story = { flushAllOnSmall: false, }, render: (properties) => ( - + + +

Layout.Content

+

+ Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat + alias eum ut officiis optio similique explicabo cupiditate + architecto voluptatem nostrum recusandae, eaque consectetur iure, + veritatis eos, mollitia possimus error earum? +

+

Layout.Sidebar

@@ -62,17 +71,55 @@ export const Content: Story = {
  • Item 2
  • Item 3
  • +

    Layout.Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +

    Layout.Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +

    Layout.Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +

    Layout.Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +

    Layout.Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +

    Layout.Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +

    Layout.Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +

    Layout.Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    - -

    Layout.Content

    -

    - Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat - alias eum ut officiis optio similique explicabo cupiditate - architecto voluptatem nostrum recusandae, eaque consectetur iure, - veritatis eos, mollitia possimus error earum? -

    -
    ), diff --git a/src/components/Layout/layout-main.stories.tsx b/src/components/Layout/layout-main.stories.tsx index b570597409..3418fa6632 100644 --- a/src/components/Layout/layout-main.stories.tsx +++ b/src/components/Layout/layout-main.stories.tsx @@ -1,5 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; +import type { ReactElement } from 'react'; import { Layout } from '~/src/index'; +import type { LayoutMainProperties } from './layout-main'; const meta: Meta = { title: 'Components (Draft)/Layout/Main', @@ -40,6 +42,8 @@ import Layout from './Layout
        < /Layout.Sidebar >
      < /Layout.Wrapper >
    < /Layout.Main >
    + +**Note:** For \`layout="1-3"\` (sidebar on the left), put \`Layout.Sidebar\` **before** \`Layout.Content\` inside \`Layout.Wrapper\`. For \`layout="2-1"\`, put **main first**, then sidebar—matching the [CFPB markup](https://cfpb.github.io/design-system/development/main-content-and-sidebars). `, }, }, @@ -50,64 +54,93 @@ export default meta; type Story = StoryObj; +const exampleContent = ( + +

    Content

    +

    + Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat alias + eum ut officiis optio similique explicabo cupiditate architecto + voluptatem nostrum recusandae, eaque consectetur iure, veritatis eos, + mollitia possimus error earum? +

    +
    +); + +const exampleSidebar = ( + +
    +

    Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +

    Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +

    Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +

    Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +

    Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +

    Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +

    Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +
    +
    +); + +function renderMainLayout( + properties: LayoutMainProperties, +): ReactElement { + const layout = properties.layout ?? '2-1'; + const columnChildren = + layout === '1-3' + ? [exampleSidebar, exampleContent] + : [exampleContent, exampleSidebar]; + + return ( + + {columnChildren} + + ); +} + export const Layout_2_1: Story = { args: { layout: '2-1', }, - render: (properties) => ( - - - -

    Content

    -

    - Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat - alias eum ut officiis optio similique explicabo cupiditate - architecto voluptatem nostrum recusandae, eaque consectetur iure, - veritatis eos, mollitia possimus error earum? -

    -
    - -
    -

    Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -
    -
    -
    -
    - ), + render: (properties) => renderMainLayout(properties), }; export const Layout_1_3: Story = { args: { layout: '1-3', }, - render: (properties) => ( - - - -
    -

    Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -
    -
    - -

    Content

    -

    - Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat - alias eum ut officiis optio similique explicabo cupiditate - architecto voluptatem nostrum recusandae, eaque consectetur iure, - veritatis eos, mollitia possimus error earum? -

    -
    -
    -
    - ), + render: (properties) => renderMainLayout(properties), }; diff --git a/src/components/Layout/layout-sidebar.stories.tsx b/src/components/Layout/layout-sidebar.stories.tsx index c17026ab43..7271a3e9b1 100644 --- a/src/components/Layout/layout-sidebar.stories.tsx +++ b/src/components/Layout/layout-sidebar.stories.tsx @@ -53,8 +53,17 @@ export const Sidebar: Story = { flushAllOnSmall: false, }, render: (properties) => ( - + + +

    Layout.Content

    +

    + Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat + alias eum ut officiis optio similique explicabo cupiditate + architecto voluptatem nostrum recusandae, eaque consectetur iure, + veritatis eos, mollitia possimus error earum? +

    +

    Layout.Sidebar

    @@ -65,15 +74,6 @@ export const Sidebar: Story = {
    - -

    Layout.Content

    -

    - Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat - alias eum ut officiis optio similique explicabo cupiditate - architecto voluptatem nostrum recusandae, eaque consectetur iure, - veritatis eos, mollitia possimus error earum? -

    -
    ), diff --git a/src/components/Layout/layout-wrapper.stories.tsx b/src/components/Layout/layout-wrapper.stories.tsx index bfbd58278d..a371aca496 100644 --- a/src/components/Layout/layout-wrapper.stories.tsx +++ b/src/components/Layout/layout-wrapper.stories.tsx @@ -42,7 +42,17 @@ type Story = StoryObj; export const Wrapper: Story = { args: { + // Order matches default Layout.Main layout "2-1" (main first, then sidebar). children: [ + +

    Layout.Content

    +

    + Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat + alias eum ut officiis optio similique explicabo cupiditate architecto + voluptatem nostrum recusandae, eaque consectetur iure, veritatis eos, + mollitia possimus error earum? +

    +
    ,

    Layout.Sidebar

    @@ -53,15 +63,6 @@ export const Wrapper: Story = {
    , - -

    Layout.Content

    -

    - Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat - alias eum ut officiis optio similique explicabo cupiditate architecto - voluptatem nostrum recusandae, eaque consectetur iure, veritatis eos, - mollitia possimus error earum? -

    -
    , ], }, render: ({ children }) => ( diff --git a/src/components/Layout/layout.scss b/src/components/Layout/layout.scss index 2599629e9c..5e268314b0 100644 --- a/src/components/Layout/layout.scss +++ b/src/components/Layout/layout.scss @@ -5,3 +5,60 @@ margin-right: 0 !important; } } + +// At the two-column breakpoint, use flex so main and sidebar share the row height of the +// taller column. The vertical divider is drawn from `.content__main::after` with `bottom: 0`, +// so it only reaches the bottom of `.content__main`; stretching that box matches the divider +// to the full sidebar/main height (CFPB DS uses inline-block, which does not equalize height). +@media only screen and (width >= 56.3125em) { + .content--1-3 .wrapper, + .content--2-1 .wrapper { + align-items: stretch; + display: flex; + } + + .content--1-3 .wrapper > .content__main, + .content--1-3 .wrapper > .content__sidebar, + .content--2-1 .wrapper > .content__main, + .content--2-1 .wrapper > .content__sidebar { + display: block; + margin-right: 0 !important; + } + + .content--1-3 .wrapper > .content__sidebar { + flex: 0 0 25%; + max-width: 25%; + } + + .content--1-3 .wrapper > .content__main { + flex: 0 0 75%; + max-width: 75%; + } + + .content--2-1 .wrapper > .content__main { + flex: 0 0 66.6667%; + max-width: 66.6667%; + } + + .content--2-1 .wrapper > .content__sidebar { + flex: 0 0 33.3333%; + max-width: 33.3333%; + } + + // CFPB DS 5.3.2 defines the column divider via `.content__main::after` for `content--1-3` + // but only emits `right: -1.875em` for `content--2-1` (no `content`, border, or positioning). + // Mirror the 1-3 rule so the vertical rule appears between main and a right-hand sidebar. + .content--2-1 .content__main { + position: relative; + } + + .content--2-1 .content__main::after { + border-right: 1px solid var(--content-main-border); + bottom: 0; + content: ''; + position: absolute; + right: -1.875em; + top: 2.8125em; + width: 0; + } +} diff --git a/src/components/Layout/layout.test.tsx b/src/components/Layout/layout.test.tsx new file mode 100644 index 0000000000..71154cb31d --- /dev/null +++ b/src/components/Layout/layout.test.tsx @@ -0,0 +1,169 @@ +import '@testing-library/jest-dom'; +import { render, screen } from '@testing-library/react'; +import Layout from './layout'; + +describe('Layout.Main', () => { + it('renders main landmark with default 2-1 layout classes', () => { + render( + + child + , + ); + + const main = screen.getByRole('main'); + expect(main).toHaveClass('content', 'content--2-1'); + expect(main).toHaveAttribute('id', 'main'); + expect(screen.getByText('child')).toBeInTheDocument(); + }); + + it('applies 1-3 layout class when layout is 1-3', () => { + render( + + child + , + ); + + expect(screen.getByRole('main')).toHaveClass('content--1-3'); + }); + + it('accepts custom id and extra classes', () => { + render( + + child + , + ); + + const main = screen.getByRole('main'); + expect(main).toHaveAttribute('id', 'page-main'); + expect(main).toHaveClass('extra-class'); + }); +}); + +describe('Layout.Wrapper', () => { + it('renders wrapper class and passes through div attributes', () => { + render( + + inner + , + ); + + const wrap = screen.getByTestId('wrap'); + expect(wrap).toHaveClass('wrapper'); + expect(wrap).toHaveAttribute('aria-label', 'Page'); + expect(wrap).toHaveTextContent('inner'); + }); +}); + +describe('Layout.Content', () => { + it('renders content__main and optional flush modifiers', () => { + const { rerender } = render( + + body + , + ); + + let node = screen.getByTestId('content'); + expect(node).toHaveClass('content__main'); + expect(node).not.toHaveClass('content--flush-bottom'); + + rerender( + + body + , + ); + + node = screen.getByTestId('content'); + expect(node).toHaveClass( + 'content__main', + 'content--flush-bottom', + 'content--flush-top-on-small', + 'content--flush-all-on-small', + ); + }); +}); + +describe('Layout.Sidebar', () => { + it('renders aside with sidebar classes and optional flush modifiers', () => { + const { rerender } = render( + nav, + ); + + let aside = screen.getByTestId('side'); + expect(aside.tagName).toBe('ASIDE'); + expect(aside).toHaveClass('sidebar', 'content__sidebar', 'o-sidebar-content'); + expect(aside).not.toHaveClass('content--flush-bottom'); + + rerender( + + nav + , + ); + + aside = screen.getByTestId('side'); + expect(aside).toHaveClass( + 'content--flush-bottom', + 'content--flush-top-on-small', + 'content--flush-all-on-small', + ); + }); +}); + +describe('Layout composition (CFPB DOM order)', () => { + it('2-1: main column precedes sidebar in document order', () => { + render( + + + + Main + + + Side + + + , + ); + + const mainCol = screen.getByTestId('layout-main-col'); + const sidebar = screen.getByTestId('layout-sidebar-col'); + expect( + Boolean( + mainCol.compareDocumentPosition(sidebar) & + Node.DOCUMENT_POSITION_FOLLOWING, + ), + ).toBe(true); + }); + + it('1-3: sidebar precedes main column in document order', () => { + render( + + + + Side + + + Main + + + , + ); + + const mainCol = screen.getByTestId('layout-main-col'); + const sidebar = screen.getByTestId('layout-sidebar-col'); + expect( + Boolean( + sidebar.compareDocumentPosition(mainCol) & + Node.DOCUMENT_POSITION_FOLLOWING, + ), + ).toBe(true); + }); +}); From d523744ea9639537271e3de101da0929fcea2fa2 Mon Sep 17 00:00:00 2001 From: Richard Dinh <1038306+flacoman91@users.noreply.github.com> Date: Thu, 21 May 2026 11:14:58 -0700 Subject: [PATCH 2/6] consolidate stories --- .../Layout/layout-content.stories.tsx | 126 --------------- src/components/Layout/layout-main.stories.tsx | 146 ------------------ .../Layout/layout-sidebar.stories.tsx | 80 ---------- .../Layout/layout-stories-shared.tsx | 54 +++++++ .../Layout/layout-wrapper.stories.tsx | 73 --------- src/components/Layout/layout.scss | 3 + src/components/Layout/layout.stories.tsx | 49 ++++++ 7 files changed, 106 insertions(+), 425 deletions(-) delete mode 100644 src/components/Layout/layout-content.stories.tsx delete mode 100644 src/components/Layout/layout-main.stories.tsx delete mode 100644 src/components/Layout/layout-sidebar.stories.tsx create mode 100644 src/components/Layout/layout-stories-shared.tsx delete mode 100644 src/components/Layout/layout-wrapper.stories.tsx create mode 100644 src/components/Layout/layout.stories.tsx diff --git a/src/components/Layout/layout-content.stories.tsx b/src/components/Layout/layout-content.stories.tsx deleted file mode 100644 index 16106853a7..0000000000 --- a/src/components/Layout/layout-content.stories.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Layout } from '~/src/index'; - -const meta: Meta = { - title: 'Components (Draft)/Layout/Content', - tags: ['autodocs'], - component: Layout.Content, - parameters: { - docs: { - description: { - component: ` -### CFPB DS Layout.Content component - -Container for the primary page content within a layout. -
      -
    • [flushBottom](https://cfpb.github.io/design-system/development/main-content-and-sidebars#flush-bottom-modifier)
    • -
    • [flushTopOnSmall](https://cfpb.github.io/design-system/development/main-content-and-sidebars#flush-top-modifier-only-on-small-screens)
    • -
    • [flushAllOnSmall](https://cfpb.github.io/design-system/development/main-content-and-sidebars#flush-all-modifier-only-on-small-screens)
    • -
    - -Source: https://cfpb.github.io/design-system/development/main-content-and-sidebars - -### Usage - -import Layout from './Layout
    - -< Layout.Main >
    -  < Hero / >
    -  < Layout.Wrapper >
    -    < Layout.Content >
    -      Main Content
    -    < /Layout.Content >
    -    < Layout.Sidebar >
    -      Sidebar Content
    -    < /Layout.Sidebar >
    -  < /Layout.Wrapper >
    -< /Layout.Main >
    -`, - }, - }, - }, -}; - -export default meta; - -type Story = StoryObj; - -export const Content: Story = { - args: { - flushBottom: false, - flushTopOnSmall: false, - flushAllOnSmall: false, - }, - render: (properties) => ( - - - -

    Layout.Content

    -

    - Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat - alias eum ut officiis optio similique explicabo cupiditate - architecto voluptatem nostrum recusandae, eaque consectetur iure, - veritatis eos, mollitia possimus error earum? -

    -
    - -
    -

    Layout.Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -

    Layout.Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -

    Layout.Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -

    Layout.Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -

    Layout.Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -

    Layout.Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -

    Layout.Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -

    Layout.Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -

    Layout.Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -
    -
    -
    -
    - ), -}; diff --git a/src/components/Layout/layout-main.stories.tsx b/src/components/Layout/layout-main.stories.tsx deleted file mode 100644 index 3418fa6632..0000000000 --- a/src/components/Layout/layout-main.stories.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import type { ReactElement } from 'react'; -import { Layout } from '~/src/index'; -import type { LayoutMainProperties } from './layout-main'; - -const meta: Meta = { - title: 'Components (Draft)/Layout/Main', - tags: ['autodocs'], - component: Layout.Main, - parameters: { - docs: { - description: { - component: ` -### CFPB DS Layout.Main component - -Container for all of the content within a Layout. Used to configure the column structure ('layout') and whether the sidebar bleeds to the window edge ('bleedbar'). - - -
      -
    • layout
        -
      • [1-3](https://cfpb.github.io/design-system/development/main-content-and-sidebars#left-hand-sidebar-layout)
      • -
      • [2-1](https://cfpb.github.io/design-system/development/main-content-and-sidebars#right-hand-sidebar-layout)
      • -
    • - -
    - - -Source: https://cfpb.github.io/design-system/development/main-content-and-sidebars - -### Usage - -import Layout from './Layout
    - -< Layout.Main >
    -  < Hero / >
    -  < Layout.Wrapper >
    -    < Layout.Content >
    -      Main Content
    -    < /Layout.Content >
    -    < Layout.Sidebar >
    -      Sidebar Content
    -    < /Layout.Sidebar >
    -  < /Layout.Wrapper >
    -< /Layout.Main >
    - -**Note:** For \`layout="1-3"\` (sidebar on the left), put \`Layout.Sidebar\` **before** \`Layout.Content\` inside \`Layout.Wrapper\`. For \`layout="2-1"\`, put **main first**, then sidebar—matching the [CFPB markup](https://cfpb.github.io/design-system/development/main-content-and-sidebars). -`, - }, - }, - }, -}; - -export default meta; - -type Story = StoryObj; - -const exampleContent = ( - -

    Content

    -

    - Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat alias - eum ut officiis optio similique explicabo cupiditate architecto - voluptatem nostrum recusandae, eaque consectetur iure, veritatis eos, - mollitia possimus error earum? -

    -
    -); - -const exampleSidebar = ( - -
    -

    Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -

    Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -

    Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -

    Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -

    Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -

    Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -

    Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -
    -
    -); - -function renderMainLayout( - properties: LayoutMainProperties, -): ReactElement { - const layout = properties.layout ?? '2-1'; - const columnChildren = - layout === '1-3' - ? [exampleSidebar, exampleContent] - : [exampleContent, exampleSidebar]; - - return ( - - {columnChildren} - - ); -} - -export const Layout_2_1: Story = { - args: { - layout: '2-1', - }, - render: (properties) => renderMainLayout(properties), -}; - -export const Layout_1_3: Story = { - args: { - layout: '1-3', - }, - render: (properties) => renderMainLayout(properties), -}; diff --git a/src/components/Layout/layout-sidebar.stories.tsx b/src/components/Layout/layout-sidebar.stories.tsx deleted file mode 100644 index 7271a3e9b1..0000000000 --- a/src/components/Layout/layout-sidebar.stories.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Layout } from '~/src/index'; - -const meta: Meta = { - title: 'Components (Draft)/Layout/Sidebar', - tags: ['autodocs'], - component: Layout.Sidebar, - parameters: { - docs: { - description: { - component: ` -### CFPB DS Layout.Sidebar component - -Container for the sidebar content within a layout. - -
      -
    • [flushBottom](https://cfpb.github.io/design-system/development/main-content-and-sidebars#flush-bottom-modifier)
    • -
    • [flushTopOnSmall](https://cfpb.github.io/design-system/development/main-content-and-sidebars#flush-top-modifier-only-on-small-screens)
    • -
    • [flushAllOnSmall](https://cfpb.github.io/design-system/development/main-content-and-sidebars#flush-all-modifier-only-on-small-screens)
    • -
    - -Source: https://cfpb.github.io/design-system/development/main-content-and-sidebars - -### Usage - -import Layout from './Layout
    - -< Layout.Main >
    -  < Hero / >
    -  < Layout.Wrapper >
    -    < Layout.Content >
    -      Main Content
    -    < /Layout.Content >
    -    < Layout.Sidebar >
    -      Sidebar Content
    -    < /Layout.Sidebar >
    -  < /Layout.Wrapper >
    -< /Layout.Main >
    -`, - }, - }, - }, -}; - -export default meta; - -type Story = StoryObj; - -export const Sidebar: Story = { - args: { - flushBottom: false, - flushTopOnSmall: false, - flushAllOnSmall: false, - }, - render: (properties) => ( - - - -

    Layout.Content

    -

    - Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat - alias eum ut officiis optio similique explicabo cupiditate - architecto voluptatem nostrum recusandae, eaque consectetur iure, - veritatis eos, mollitia possimus error earum? -

    -
    - -
    -

    Layout.Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -
    -
    -
    -
    - ), -}; diff --git a/src/components/Layout/layout-stories-shared.tsx b/src/components/Layout/layout-stories-shared.tsx new file mode 100644 index 0000000000..f64353ec10 --- /dev/null +++ b/src/components/Layout/layout-stories-shared.tsx @@ -0,0 +1,54 @@ +import type { ReactElement } from 'react'; +import { Layout } from '~/src/index'; +import type { LayoutMainProperties } from './layout-main'; + +export const LAYOUT_DOCS_SOURCE = + 'https://cfpb.github.io/design-system/development/main-content-and-sidebars'; + +export const LAYOUT_DOCS = { + component: `Use \`Layout.Main\`, \`Layout.Wrapper\`, \`Layout.Content\`, and \`Layout.Sidebar\` together to structure page content and optional sidebars. + +Main is the container for all content within a layout and configures column structure (\`layout\`) and whether the sidebar bleeds to the window edge. Content is the main body of the page. Wrapper positions content and sidebar columns. Sidebar is a vertical region beside the main content. + +Source: ${LAYOUT_DOCS_SOURCE}`, +} as const; + +export const LAYOUT_EXAMPLE_LOREM = + 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat alias eum ut officiis optio similique explicabo cupiditate architecto voluptatem nostrum recusandae, eaque consectetur iure, veritatis eos, mollitia possimus error earum?'; + +export const LayoutExampleContent = (): ReactElement => ( + +

    Content

    +

    {LAYOUT_EXAMPLE_LOREM}

    +
    +); + +export const LayoutExampleSidebar = (): ReactElement => ( + +
    +

    Sidebar

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    +
    +
    +); + +export const renderLayoutTwoColumnExample = ({ + layout = '2-1', +}: { + layout?: LayoutMainProperties['layout']; +}): ReactElement => { + const contentNode = LayoutExampleContent(); + const sidebarNode = LayoutExampleSidebar(); + const columnChildren = + layout === '1-3' ? [sidebarNode, contentNode] : [contentNode, sidebarNode]; + + return ( + + {columnChildren} + + ); +}; diff --git a/src/components/Layout/layout-wrapper.stories.tsx b/src/components/Layout/layout-wrapper.stories.tsx deleted file mode 100644 index a371aca496..0000000000 --- a/src/components/Layout/layout-wrapper.stories.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Layout } from '~/src/index'; - -const meta: Meta = { - title: 'Components (Draft)/Layout/Wrapper', - tags: ['autodocs'], - component: Layout.Wrapper, - parameters: { - docs: { - description: { - component: ` -### CFPB DS Layout.Wrapper component - -Container to help position Content and Sidebar elements. - -Source: https://cfpb.github.io/design-system/development/main-content-and-sidebars - -### Usage - -import Layout from './Layout
    - -< Layout.Main >
    -  < Hero / >
    -  < Layout.Wrapper >
    -    < Layout.Content >
    -      Main Content
    -    < /Layout.Content >
    -    < Layout.Sidebar >
    -      Sidebar Content
    -    < /Layout.Sidebar >
    -  < /Layout.Wrapper >
    -< /Layout.Main >
    -`, - }, - }, - }, -}; - -export default meta; - -type Story = StoryObj; - -export const Wrapper: Story = { - args: { - // Order matches default Layout.Main layout "2-1" (main first, then sidebar). - children: [ - -

    Layout.Content

    -

    - Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat - alias eum ut officiis optio similique explicabo cupiditate architecto - voluptatem nostrum recusandae, eaque consectetur iure, veritatis eos, - mollitia possimus error earum? -

    -
    , - -
    -

    Layout.Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -
    -
    , - ], - }, - render: ({ children }) => ( - - {children} - - ), -}; diff --git a/src/components/Layout/layout.scss b/src/components/Layout/layout.scss index 5e268314b0..dd3c45ba3b 100644 --- a/src/components/Layout/layout.scss +++ b/src/components/Layout/layout.scss @@ -1,3 +1,6 @@ +// Layout overrides for React Layout stories (`layout.stories.tsx`) and consumers. +// At wide viewports, flex + the 2-1 divider below match the CFPB DS main/sidebar examples. + // Override Design System styles that cause content to overflow sidebar boundaries @media only all and (width >= 37.5625em) { .content--2-1 .content__main, diff --git a/src/components/Layout/layout.stories.tsx b/src/components/Layout/layout.stories.tsx new file mode 100644 index 0000000000..6f72aef1d2 --- /dev/null +++ b/src/components/Layout/layout.stories.tsx @@ -0,0 +1,49 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Layout } from '~/src/index'; +import { + LAYOUT_DOCS, + LayoutExampleContent, + renderLayoutTwoColumnExample, +} from './layout-stories-shared'; + +const meta: Meta = { + title: 'Components (Draft)/Layouts', + tags: ['autodocs'], + component: Layout.Main, + parameters: { + docs: { + description: { + component: LAYOUT_DOCS.component, + }, + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const BasicMain: Story = { + name: 'Basic main', + render: () => ( + + {LayoutExampleContent()} + + ), +}; + +export const Layout_2_1: Story = { + name: '2-1 layout', + args: { + layout: '2-1', + }, + render: ({ layout = '2-1' }) => renderLayoutTwoColumnExample({ layout }), +}; + +export const Layout_1_3: Story = { + name: '1-3 layout', + args: { + layout: '1-3', + }, + render: ({ layout = '1-3' }) => renderLayoutTwoColumnExample({ layout }), +}; From 862015b1fc70d326ad16d7afa84d8984252197db Mon Sep 17 00:00:00 2001 From: Richard Dinh <1038306+flacoman91@users.noreply.github.com> Date: Thu, 28 May 2026 00:12:38 -0700 Subject: [PATCH 3/6] cleanup some of the descriptions --- src/components/Layout/layout-stories-shared.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/Layout/layout-stories-shared.tsx b/src/components/Layout/layout-stories-shared.tsx index f64353ec10..f3ec88ba26 100644 --- a/src/components/Layout/layout-stories-shared.tsx +++ b/src/components/Layout/layout-stories-shared.tsx @@ -8,7 +8,13 @@ export const LAYOUT_DOCS_SOURCE = export const LAYOUT_DOCS = { component: `Use \`Layout.Main\`, \`Layout.Wrapper\`, \`Layout.Content\`, and \`Layout.Sidebar\` together to structure page content and optional sidebars. -Main is the container for all content within a layout and configures column structure (\`layout\`) and whether the sidebar bleeds to the window edge. Content is the main body of the page. Wrapper positions content and sidebar columns. Sidebar is a vertical region beside the main content. +Main is the container for all content within a layout and configures column structure and whether the sidebar bleeds to the window edge. + +Content is the main body of the page, situated between the header and the footer. + +The wrapper serves as a container for other components or elements. It wraps around child components and provides additional functionality, such as styling, context, or logic. + +A sidebar is a vertical user interface element positioned on the left or right side of the main content area. Source: ${LAYOUT_DOCS_SOURCE}`, } as const; From 14b178afa584621797314e2bd02cf1d780eead16 Mon Sep 17 00:00:00 2001 From: Richard Dinh <1038306+flacoman91@users.noreply.github.com> Date: Thu, 28 May 2026 20:04:22 -0700 Subject: [PATCH 4/6] reorg layout storybook pages. --- src/components/Layout/layout-main.tsx | 20 ++--- .../Layout/layout-stories-shared.tsx | 44 +++++++--- src/components/Layout/layout.stories.tsx | 85 ++++++++++++++++--- src/components/Layout/layout.test.tsx | 15 +++- 4 files changed, 128 insertions(+), 36 deletions(-) diff --git a/src/components/Layout/layout-main.tsx b/src/components/Layout/layout-main.tsx index 8545b2ca55..d6b148f26c 100644 --- a/src/components/Layout/layout-main.tsx +++ b/src/components/Layout/layout-main.tsx @@ -11,13 +11,13 @@ export const LayoutMain = ({ children, classes = '', id = 'main', - layout = '2-1', -}: LayoutMainProperties): JSX.Element => { - const cnames = ['content', `content--${layout}`, classes]; - - return ( -
    - {children} -
    - ); -}; + layout, +}: LayoutMainProperties): JSX.Element => ( +
    + {children} +
    +); diff --git a/src/components/Layout/layout-stories-shared.tsx b/src/components/Layout/layout-stories-shared.tsx index f3ec88ba26..89fa3ee49b 100644 --- a/src/components/Layout/layout-stories-shared.tsx +++ b/src/components/Layout/layout-stories-shared.tsx @@ -5,16 +5,26 @@ import type { LayoutMainProperties } from './layout-main'; export const LAYOUT_DOCS_SOURCE = 'https://cfpb.github.io/design-system/development/main-content-and-sidebars'; -export const LAYOUT_DOCS = { - component: `Use \`Layout.Main\`, \`Layout.Wrapper\`, \`Layout.Content\`, and \`Layout.Sidebar\` together to structure page content and optional sidebars. +/** Empty string in Storybook controls = single-column main (no `content--*` modifier). */ +export type LayoutStoryLayoutValue = LayoutMainProperties['layout'] | ''; -Main is the container for all content within a layout and configures column structure and whether the sidebar bleeds to the window edge. +export const LAYOUT_STORY_LAYOUT_OPTIONS: LayoutStoryLayoutValue[] = [ + '', + '2-1', + '1-3', +]; -Content is the main body of the page, situated between the header and the footer. +export const LAYOUT_DOCS = { + component: `Layout is a **composition API**: assemble \`Layout.Main\`, \`Layout.Wrapper\`, \`Layout.Content\`, and optionally \`Layout.Sidebar\` rather than picking a single “variant” on one component. -The wrapper serves as a container for other components or elements. It wraps around child components and provides additional functionality, such as styling, context, or logic. +| Piece | Role | +| ----- | ---- | +| **Main** | Page \`
    \` landmark. Set \`layout="2-1"\` or \`layout="1-3"\` only when using a two-column page; omit \`layout\` for a single full-width content column. | +| **Wrapper** | \`.wrapper\` around columns (and optional hero above it inside Main). | +| **Content** | Primary page body (\`.content__main\`). | +| **Sidebar** | Optional aside (\`.content__sidebar\`). Order in the wrapper must match the layout (main then sidebar for \`2-1\`; sidebar then main for \`1-3\`). | -A sidebar is a vertical user interface element positioned on the left or right side of the main content area. +Use the **Page layout** story control to preview column ratios. Individual pieces share the same DOM/classes as in production; see the building-block stories for each part in isolation. Source: ${LAYOUT_DOCS_SOURCE}`, } as const; @@ -42,18 +52,30 @@ export const LayoutExampleSidebar = (): ReactElement => ( ); -export const renderLayoutTwoColumnExample = ({ - layout = '2-1', +export const renderLayoutPageExample = ({ + layout = '', }: { - layout?: LayoutMainProperties['layout']; + layout?: LayoutStoryLayoutValue; }): ReactElement => { const contentNode = LayoutExampleContent(); + const columnLayout = layout || undefined; + + if (!columnLayout) { + return ( + + {contentNode} + + ); + } + const sidebarNode = LayoutExampleSidebar(); const columnChildren = - layout === '1-3' ? [sidebarNode, contentNode] : [contentNode, sidebarNode]; + columnLayout === '1-3' + ? [sidebarNode, contentNode] + : [contentNode, sidebarNode]; return ( - + {columnChildren} ); diff --git a/src/components/Layout/layout.stories.tsx b/src/components/Layout/layout.stories.tsx index 6f72aef1d2..95980803a9 100644 --- a/src/components/Layout/layout.stories.tsx +++ b/src/components/Layout/layout.stories.tsx @@ -2,29 +2,78 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { Layout } from '~/src/index'; import { LAYOUT_DOCS, + LAYOUT_STORY_LAYOUT_OPTIONS, LayoutExampleContent, - renderLayoutTwoColumnExample, + LayoutExampleSidebar, + type LayoutStoryLayoutValue, + renderLayoutPageExample, } from './layout-stories-shared'; +const layoutLabels: Record = { + '': 'None (single column)', + '2-1': '2-1 (main + right sidebar)', + '1-3': '1-3 (left sidebar + main)', +}; + const meta: Meta = { title: 'Components (Draft)/Layouts', tags: ['autodocs'], component: Layout.Main, parameters: { + layout: 'fullscreen', docs: { description: { component: LAYOUT_DOCS.component, }, }, }, + argTypes: { + layout: { + name: 'Column layout', + control: { type: 'select' }, + options: LAYOUT_STORY_LAYOUT_OPTIONS, + labels: layoutLabels, + description: + 'Two-column ratios on `Layout.Main`. None = no `layout` prop (main content only, no sidebar column).', + }, + id: { table: { disable: true } }, + classes: { table: { disable: true } }, + children: { table: { disable: true } }, + }, }; export default meta; type Story = StoryObj; -export const BasicMain: Story = { - name: 'Basic main', +/** Composed page shell — use Controls to switch column layout or preview main-only. */ +export const PageLayout: Story = { + name: 'Page layout', + args: { + layout: '' satisfies LayoutStoryLayoutValue, + }, + render: ({ layout = '' }) => renderLayoutPageExample({ layout }), + parameters: { + docs: { + description: { + story: + 'Typical assembly: `Layout.Main` → `Layout.Wrapper` → `Layout.Content` (and `Layout.Sidebar` when using `2-1` or `1-3`). This is the story to use when reviewing layout behavior; the stories below show each building block alone.', + }, + }, + }, +}; + +export const BuildingBlockMain: Story = { + name: 'Building block — Main', + parameters: { + controls: { disable: true }, + docs: { + description: { + story: + '`Layout.Main` only, with no `layout` prop — single-column shell (`content`, no `content--*`).', + }, + }, + }, render: () => ( {LayoutExampleContent()} @@ -32,18 +81,28 @@ export const BasicMain: Story = { ), }; -export const Layout_2_1: Story = { - name: '2-1 layout', - args: { - layout: '2-1', +export const BuildingBlockContent: Story = { + name: 'Building block — Content', + parameters: { + controls: { disable: true }, + docs: { + description: { + story: '`Layout.Content` markup and classes without Main/Wrapper.', + }, + }, }, - render: ({ layout = '2-1' }) => renderLayoutTwoColumnExample({ layout }), + render: () => LayoutExampleContent(), }; -export const Layout_1_3: Story = { - name: '1-3 layout', - args: { - layout: '1-3', +export const BuildingBlockSidebar: Story = { + name: 'Building block — Sidebar', + parameters: { + controls: { disable: true }, + docs: { + description: { + story: '`Layout.Sidebar` markup and classes without Main/Wrapper.', + }, + }, }, - render: ({ layout = '1-3' }) => renderLayoutTwoColumnExample({ layout }), + render: () => LayoutExampleSidebar(), }; diff --git a/src/components/Layout/layout.test.tsx b/src/components/Layout/layout.test.tsx index 71154cb31d..34b4828d9d 100644 --- a/src/components/Layout/layout.test.tsx +++ b/src/components/Layout/layout.test.tsx @@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react'; import Layout from './layout'; describe('Layout.Main', () => { - it('renders main landmark with default 2-1 layout classes', () => { + it('renders main landmark without a column layout class by default', () => { render( child @@ -11,11 +11,22 @@ describe('Layout.Main', () => { ); const main = screen.getByRole('main'); - expect(main).toHaveClass('content', 'content--2-1'); + expect(main).toHaveClass('content'); + expect(main).not.toHaveClass('content--2-1', 'content--1-3'); expect(main).toHaveAttribute('id', 'main'); expect(screen.getByText('child')).toBeInTheDocument(); }); + it('applies 2-1 layout class when layout is 2-1', () => { + render( + + child + , + ); + + expect(screen.getByRole('main')).toHaveClass('content--2-1'); + }); + it('applies 1-3 layout class when layout is 1-3', () => { render( From 5273453ec5b58adea36d3855cb7e0f8088d84ac8 Mon Sep 17 00:00:00 2001 From: Richard Dinh <1038306+flacoman91@users.noreply.github.com> Date: Thu, 28 May 2026 22:04:57 -0700 Subject: [PATCH 5/6] Mirror DS layout examples --- .../Layout/layout-stories-shared.tsx | 81 +++---------- src/components/Layout/layout.stories.tsx | 112 +++++++++--------- 2 files changed, 67 insertions(+), 126 deletions(-) diff --git a/src/components/Layout/layout-stories-shared.tsx b/src/components/Layout/layout-stories-shared.tsx index 89fa3ee49b..88412dfa0e 100644 --- a/src/components/Layout/layout-stories-shared.tsx +++ b/src/components/Layout/layout-stories-shared.tsx @@ -1,82 +1,29 @@ import type { ReactElement } from 'react'; -import { Layout } from '~/src/index'; -import type { LayoutMainProperties } from './layout-main'; export const LAYOUT_DOCS_SOURCE = 'https://cfpb.github.io/design-system/development/main-content-and-sidebars'; -/** Empty string in Storybook controls = single-column main (no `content--*` modifier). */ -export type LayoutStoryLayoutValue = LayoutMainProperties['layout'] | ''; - -export const LAYOUT_STORY_LAYOUT_OPTIONS: LayoutStoryLayoutValue[] = [ - '', - '2-1', - '1-3', -]; - export const LAYOUT_DOCS = { - component: `Layout is a **composition API**: assemble \`Layout.Main\`, \`Layout.Wrapper\`, \`Layout.Content\`, and optionally \`Layout.Sidebar\` rather than picking a single “variant” on one component. + component: `Layout is a **composition API** that mirrors the [CFPB main content and sidebars](${LAYOUT_DOCS_SOURCE}) pattern. Assemble \`Layout.Main\`, \`Layout.Wrapper\`, \`Layout.Content\`, and optionally \`Layout.Sidebar\`. | Piece | Role | | ----- | ---- | -| **Main** | Page \`
    \` landmark. Set \`layout="2-1"\` or \`layout="1-3"\` only when using a two-column page; omit \`layout\` for a single full-width content column. | -| **Wrapper** | \`.wrapper\` around columns (and optional hero above it inside Main). | +| **Main** | Page \`
    \` landmark (\`.content\`). Set \`layout="2-1"\` or \`layout="1-3"\` for two-column layouts; omit \`layout\` when main and sidebar stack vertically. | +| **Wrapper** | \`.wrapper\` around columns (hero or \`Divider\` may sit above it inside Main). | | **Content** | Primary page body (\`.content__main\`). | -| **Sidebar** | Optional aside (\`.content__sidebar\`). Order in the wrapper must match the layout (main then sidebar for \`2-1\`; sidebar then main for \`1-3\`). | - -Use the **Page layout** story control to preview column ratios. Individual pieces share the same DOM/classes as in production; see the building-block stories for each part in isolation. +| **Sidebar** | Optional aside (\`.content__sidebar\`). Order in the wrapper must match the layout (sidebar then main for \`1-3\`; main then sidebar for \`2-1\`). | -Source: ${LAYOUT_DOCS_SOURCE}`, +The stories below follow the live examples on the design system documentation page.`, } as const; -export const LAYOUT_EXAMPLE_LOREM = - 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat alias eum ut officiis optio similique explicabo cupiditate architecto voluptatem nostrum recusandae, eaque consectetur iure, veritatis eos, mollitia possimus error earum?'; - -export const LayoutExampleContent = (): ReactElement => ( - -

    Content

    -

    {LAYOUT_EXAMPLE_LOREM}

    -
    -); +/** Lorem copy from the CFPB left/right sidebar layout examples. */ +export const LAYOUT_EXAMPLE_LOREM = `Lorem ipsum dolor sit amet, consectetur adipisicing elit. +Cum corrupti tempora nam nihil qui mollitia consectetur +corporis nemo culpa dolorum! Laborum at eos deleniti +consequatur itaque officiis debitis quisquam! Provident!`; -export const LayoutExampleSidebar = (): ReactElement => ( - -
    -

    Sidebar

    -
      -
    • Item 1
    • -
    • Item 2
    • -
    • Item 3
    • -
    -
    -
    +export const LayoutStoryFooter = (): ReactElement => ( +
    +
    Footer
    +
    ); - -export const renderLayoutPageExample = ({ - layout = '', -}: { - layout?: LayoutStoryLayoutValue; -}): ReactElement => { - const contentNode = LayoutExampleContent(); - const columnLayout = layout || undefined; - - if (!columnLayout) { - return ( - - {contentNode} - - ); - } - - const sidebarNode = LayoutExampleSidebar(); - const columnChildren = - columnLayout === '1-3' - ? [sidebarNode, contentNode] - : [contentNode, sidebarNode]; - - return ( - - {columnChildren} - - ); -}; diff --git a/src/components/Layout/layout.stories.tsx b/src/components/Layout/layout.stories.tsx index 95980803a9..cceb12caed 100644 --- a/src/components/Layout/layout.stories.tsx +++ b/src/components/Layout/layout.stories.tsx @@ -1,20 +1,11 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Layout } from '~/src/index'; +import { Divider, Layout } from '~/src/index'; import { LAYOUT_DOCS, - LAYOUT_STORY_LAYOUT_OPTIONS, - LayoutExampleContent, - LayoutExampleSidebar, - type LayoutStoryLayoutValue, - renderLayoutPageExample, + LAYOUT_EXAMPLE_LOREM, + LayoutStoryFooter, } from './layout-stories-shared'; -const layoutLabels: Record = { - '': 'None (single column)', - '2-1': '2-1 (main + right sidebar)', - '1-3': '1-3 (left sidebar + main)', -}; - const meta: Meta = { title: 'Components (Draft)/Layouts', tags: ['autodocs'], @@ -27,82 +18,85 @@ const meta: Meta = { }, }, }, - argTypes: { - layout: { - name: 'Column layout', - control: { type: 'select' }, - options: LAYOUT_STORY_LAYOUT_OPTIONS, - labels: layoutLabels, - description: - 'Two-column ratios on `Layout.Main`. None = no `layout` prop (main content only, no sidebar column).', - }, - id: { table: { disable: true } }, - classes: { table: { disable: true } }, - children: { table: { disable: true } }, - }, }; export default meta; type Story = StoryObj; -/** Composed page shell — use Controls to switch column layout or preview main-only. */ -export const PageLayout: Story = { - name: 'Page layout', - args: { - layout: '' satisfies LayoutStoryLayoutValue, - }, - render: ({ layout = '' }) => renderLayoutPageExample({ layout }), +export const MainContentAndSidebar: Story = { + name: 'Main content and sidebar', + render: () => ( + +
    + Content hero +
    + + Main content area + Sidebar + +
    + ), parameters: { docs: { description: { story: - 'Typical assembly: `Layout.Main` → `Layout.Wrapper` → `Layout.Content` (and `Layout.Sidebar` when using `2-1` or `1-3`). This is the story to use when reviewing layout behavior; the stories below show each building block alone.', + 'Standard layout for the main content area and sidebar. By default `.content__main` and `.content__sidebar` stack vertically. Column modifiers (`2-1`, `1-3`) convert to side-by-side columns at 801px. Inline styling is for demonstration only.', }, }, }, }; -export const BuildingBlockMain: Story = { - name: 'Building block — Main', - parameters: { - controls: { disable: true }, - docs: { - description: { - story: - '`Layout.Main` only, with no `layout` prop — single-column shell (`content`, no `content--*`).', - }, - }, - }, +export const LeftHandSidebarLayout: Story = { + name: 'Left-hand sidebar layout', render: () => ( - - {LayoutExampleContent()} - + <> + + + + Section navigation + +

    Main content area

    +

    {LAYOUT_EXAMPLE_LOREM}

    +
    +
    +
    + + ), -}; - -export const BuildingBlockContent: Story = { - name: 'Building block — Content', parameters: { - controls: { disable: true }, docs: { description: { - story: '`Layout.Content` markup and classes without Main/Wrapper.', + story: + 'Add `layout="1-3"` to `Layout.Main` for a 1:3 ratio with the sidebar on the left and main content on the right. Place `Layout.Sidebar` before `Layout.Content` in the wrapper.', }, }, }, - render: () => LayoutExampleContent(), }; -export const BuildingBlockSidebar: Story = { - name: 'Building block — Sidebar', +export const RightHandSidebarLayout: Story = { + name: 'Right-hand sidebar layout', + render: () => ( + <> + + + + +

    Main content area

    +

    {LAYOUT_EXAMPLE_LOREM}

    +
    + Sidebar +
    +
    + + + ), parameters: { - controls: { disable: true }, docs: { description: { - story: '`Layout.Sidebar` markup and classes without Main/Wrapper.', + story: + 'Add `layout="2-1"` to `Layout.Main` for a 2:1 ratio with main content on the left and the sidebar on the right. Place `Layout.Content` before `Layout.Sidebar` in the wrapper.', }, }, }, - render: () => LayoutExampleSidebar(), }; From d4c2d3c8796806ae599c92afde0ec43c444c0864 Mon Sep 17 00:00:00 2001 From: Richard Dinh <1038306+flacoman91@users.noreply.github.com> Date: Thu, 28 May 2026 23:03:13 -0700 Subject: [PATCH 6/6] adding flush modifiers --- .../Layout/layout-stories-shared.tsx | 1 + src/components/Layout/layout.stories.tsx | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/src/components/Layout/layout-stories-shared.tsx b/src/components/Layout/layout-stories-shared.tsx index 88412dfa0e..e6535a710e 100644 --- a/src/components/Layout/layout-stories-shared.tsx +++ b/src/components/Layout/layout-stories-shared.tsx @@ -12,6 +12,7 @@ export const LAYOUT_DOCS = { | **Wrapper** | \`.wrapper\` around columns (hero or \`Divider\` may sit above it inside Main). | | **Content** | Primary page body (\`.content__main\`). | | **Sidebar** | Optional aside (\`.content__sidebar\`). Order in the wrapper must match the layout (sidebar then main for \`1-3\`; main then sidebar for \`2-1\`). | +| **Content / Sidebar modifiers** | \`flushBottom\`, \`flushTopOnSmall\`, and \`flushAllOnSmall\` map to \`.content--flush-bottom\`, \`.content--flush-top-on-small\`, and \`.content--flush-all-on-small\`. | The stories below follow the live examples on the design system documentation page.`, } as const; diff --git a/src/components/Layout/layout.stories.tsx b/src/components/Layout/layout.stories.tsx index cceb12caed..57e526a2b4 100644 --- a/src/components/Layout/layout.stories.tsx +++ b/src/components/Layout/layout.stories.tsx @@ -100,3 +100,87 @@ export const RightHandSidebarLayout: Story = { }, }, }; + +export const FlushBottomModifier: Story = { + name: 'Flush bottom modifier', + render: () => ( + <> + + + + + Side with no bottom padding... + + + Main content with no bottom padding... +
    + .content--flush-bottom is very useful when you have a content block + inside of .content__main with a background and flush sides. +
    +
    +
    +
    + + + ), + parameters: { + docs: { + description: { + story: + 'Set `flushBottom` on `Layout.Content` or `Layout.Sidebar` to apply `.content--flush-bottom` and remove bottom padding.', + }, + }, + }, +}; + +export const FlushTopOnSmallModifier: Story = { + name: 'Flush top modifier (only on small screens)', + render: () => ( + <> + + + + + Side with no top padding on small screens... + + Main content + + + + + ), + parameters: { + docs: { + description: { + story: + 'Set `flushTopOnSmall` on `Layout.Content` or `Layout.Sidebar` to apply `.content--flush-top-on-small`. Top padding is removed only on small screens, where main and sidebar stack in a single column.', + }, + }, + }, +}; + +export const FlushAllOnSmallModifier: Story = { + name: 'Flush all modifier (only on small screens)', + render: () => ( + <> + + + + + Side with no padding or border-based gutters on small screens... + + Main content + + + + + ), + parameters: { + docs: { + description: { + story: + 'Set `flushAllOnSmall` on `Layout.Content` or `Layout.Sidebar` to apply `.content--flush-all-on-small`. All padding and border-based gutters are removed on small screens only.', + }, + }, + }, +};