diff --git a/code/core/src/manager/App.tsx b/code/core/src/manager/App.tsx index cecbc87f4809..fe188849001c 100644 --- a/code/core/src/manager/App.tsx +++ b/code/core/src/manager/App.tsx @@ -8,7 +8,6 @@ import { addons } from 'storybook/manager-api'; import { Global, createGlobal } from 'storybook/theming'; import { Layout } from './components/layout/Layout'; -import { useLayout } from './components/layout/LayoutProvider'; import Panel from './container/Panel'; import Preview from './container/Preview'; import Sidebar from './container/Sidebar'; @@ -21,8 +20,6 @@ type Props = { }; export const App = ({ managerLayoutState, setManagerLayoutState, pages, hasTab }: Props) => { - const { setMobileAboutOpen } = useLayout(); - /** * Lets us tell the UI whether or not keyboard shortcuts are enabled, in places where it's not * convenient to load the addons singleton to figure it out. @@ -67,7 +64,7 @@ export const App = ({ managerLayoutState, setManagerLayoutState, pages, hasTab } managerLayoutState={managerLayoutState} setManagerLayoutState={setManagerLayoutState} slotMain={} - slotSidebar={ setMobileAboutOpen((state) => !state)} />} + slotSidebar={} slotPanel={} slotPages={pages.map(({ id, render: Content }) => ( diff --git a/code/core/src/manager/components/a11y/A11yStatement.tsx b/code/core/src/manager/components/a11y/A11yStatement.tsx new file mode 100644 index 000000000000..cb2e361bd4a6 --- /dev/null +++ b/code/core/src/manager/components/a11y/A11yStatement.tsx @@ -0,0 +1,188 @@ +import type { FC } from 'react'; +import React from 'react'; + +import { H2, H3, H4, H5, Link } from 'storybook/internal/components'; + +import { shortcutToHumanString } from 'storybook/manager-api'; +import { styled } from 'storybook/theming'; + +const Container = styled.div(({ theme }) => ({ + fontSize: theme.typography.size.s2, +})); + +const Kbd = styled.kbd(({ theme }) => ({ + background: theme.base === 'light' ? 'rgba(0,0,0,0.05)' : 'rgba(255,255,255,0.05)', + borderRadius: theme.appBorderRadius, + border: `1px solid ${theme.appBorderColor}`, + display: 'inline-block', + padding: '2px 6px', + fontFamily: theme.typography.fonts.mono, +})); + +const Shortcut: FC<{ shortcut: string }> = ({ shortcut }) => { + const human = shortcutToHumanString([shortcut]); + return {human}; +}; +const Code = styled.pre(({ theme }) => ({ + background: theme.base === 'light' ? 'rgba(0, 0, 0, 0.05)' : theme.appBorderColor, + fontSize: theme.typography.size.s2 - 1, + margin: '4px 0 16px', +})); + +const A11yStatement: FC = () => ( + +

Accessibility Statement for Storybook

+

This is an accessibility statement for the Storybook application.

+ +

Conformance status

+

+ The{' '} + + Web Content Accessibility Guidelines (WCAG) + {' '} + defines requirements for designers and developers to improve accessibility for people with + disabilities. It defines three levels of conformance: Level A, Level AA, and Level AAA. +

+

+ Storybook is partially conformant with WCAG 2.2 level AA. + Partially conformant means that some parts of the content do not fully conform to the + accessibility standard. +

+ +

Measures to support accessibility

+

+ The Storybook maintainers take the following measures to ensure the accessibility of + Storybook: +

+
    +
  • Include accessibility throughout our development workflow
  • +
  • Test new features for accessibility issues
  • +
  • Monitor Storybook automatically for accessibility regressions
  • +
  • Request review from external accessibility experts on specific features
  • +
+ +

Feedback

+

+ We welcome your feedback on the accessibility of Storybook. Please let us know if you + encounter accessibility barriers on Storybook by{' '} + + filling a bug report + + . +

+

We try to respond to feedback within one to two weeks.

+ +

Compatibility

+

Storybook is designed to be compatible with the following assistive technologies:

+
    +
  • Chrome with NVDA on Windows
  • +
  • Firefox with NVDA on Windows
  • +
  • Safari with VoiceOver on Mac
  • + {/*
  • Firefox with Orca on ArchLinux
  • TODO */} +
+

+ Storybook is not tested with the following technologies and may not be compatible with them: +

+
    +
  • Browsers older than Chrome 131, Edge 134, Firefox 136, Safari 18.3, and Opera 117
  • +
  • The Narrator and JAWS screen readers
  • +
+ +

Technical specifications

+

+ Accessibility of Storybook relies on the following technologies to work with the particular + combination of web browser and any assistive technologies or plugins installed on your + computer: +

+
    +
  • HTML
  • +
  • WAI-ARIA
  • +
  • CSS
  • +
  • JavaScript
  • +
+

These technologies are relied upon for conformance with the accessibility standards used.

+ +

Limitations and alternatives

+

+ Despite our best efforts to ensure the accessibility of Storybook, there are some limitations. + Below is a description of the most impactful limitations, and potential solutions. +

+

+ Please{' '} + + fill a bug report + {' '} + if you observe an issue not listed below or on our{' '} + + accessibility bug tracker + + . +

+ +

Sidebar navigation is not accessible with the keyboard

+

+ Navigating through stories in the sidebar is difficult, and the context menus next to each + component (which let you run tests for that specific component) are not reachable by keyboard. + We are currently testing an alternative design of the sidebar that works well with keyboards + and major screen readers. +

+

+ If you use the search bar to find specific stories, press the {' '} + and keys from the search input to navigate to search + results. +

+

+ If you use the sidebar directly, press to navigate between content + sections, and press and to + enter a content section when on the title of a section. Each section is immediately followed + by a button to expand all items within the section. Make sure to expand all items before + navigating in the section, as expanding and collapsing individual items is not currently + possible with the keyboard alone. +

+ +

Storybook Vitest addon limitations

+
Test result are not announced
+

+ The test widget provided by the{' '} + + Vitest addon + {' '} + at the bottom of the sidebar allows you to run tests on all stories, but it does not report + when tests are done running. We will implement live announcements in a future update to + address this issue. If you have few tests to run, results will be available after + approximately ten seconds. If you have hundreds of tests, it can take a few minutes for tests + to complete. Please wait and recheck the test widget manually for results. +

+
Tests cannot be run per component
+

+ It is not currently possible to run tests on a specific component with the keyboard only. We + will fix this issue alongside the sidebar navigation improvements. In the meantime, you can + run tests in your terminal application via Vitest, which can{' '} + run tests for specific files: +

+ vitest --project=storybook + +

Interactive stories always auto-play

+

+ Interactive stories on Storybook play automatically as you navigate to them. This is harmful + to some users, but disabling that feature requires a few underlying changes to how Storybook + functions.. We are working on a solution to disable autoplay based on user motion preferences. + In the meantime, please ask your Storybook administrator to follow the{' '} + + workaround + {' '} + kindly provided by the GitHub Design System team. +

+ +

Assessment approach

+

Storybook assessed the accessibility of Storybook by the following approaches:

+
    +
  • Self-evaluation
  • +
+ +

Date

+

This statement was created on 2 January 2026.

+
+); + +export { A11yStatement }; diff --git a/code/core/src/manager/components/layout/LayoutProvider.tsx b/code/core/src/manager/components/layout/LayoutProvider.tsx index 78d5e5db053a..c4aae99803d9 100644 --- a/code/core/src/manager/components/layout/LayoutProvider.tsx +++ b/code/core/src/manager/components/layout/LayoutProvider.tsx @@ -7,6 +7,8 @@ import { useMediaQuery } from '../../hooks/useMedia'; type LayoutContextType = { isMobileMenuOpen: boolean; setMobileMenuOpen: React.Dispatch>; + isMobileA11yStatementOpen: boolean; + setMobileA11yStatementOpen: React.Dispatch>; isMobileAboutOpen: boolean; setMobileAboutOpen: React.Dispatch>; isMobilePanelOpen: boolean; @@ -18,6 +20,8 @@ type LayoutContextType = { const LayoutContext = createContext({ isMobileMenuOpen: false, setMobileMenuOpen: () => {}, + isMobileA11yStatementOpen: false, + setMobileA11yStatementOpen: () => {}, isMobileAboutOpen: false, setMobileAboutOpen: () => {}, isMobilePanelOpen: false, @@ -28,10 +32,12 @@ const LayoutContext = createContext({ export const LayoutProvider: FC< PropsWithChildren & { + /* Helps with testing components that depend on LayoutProvider */ forceDesktop?: boolean; } > = ({ children, forceDesktop }) => { const [isMobileMenuOpen, setMobileMenuOpen] = useState(false); + const [isMobileA11yStatementOpen, setMobileA11yStatementOpen] = useState(false); const [isMobileAboutOpen, setMobileAboutOpen] = useState(false); const [isMobilePanelOpen, setMobilePanelOpen] = useState(false); const isDesktop = forceDesktop ?? useMediaQuery(`(min-width: ${BREAKPOINT}px)`); @@ -41,6 +47,8 @@ export const LayoutProvider: FC< () => ({ isMobileMenuOpen, setMobileMenuOpen, + isMobileA11yStatementOpen, + setMobileA11yStatementOpen, isMobileAboutOpen, setMobileAboutOpen, isMobilePanelOpen, @@ -51,6 +59,8 @@ export const LayoutProvider: FC< [ isMobileMenuOpen, setMobileMenuOpen, + isMobileA11yStatementOpen, + setMobileA11yStatementOpen, isMobileAboutOpen, setMobileAboutOpen, isMobilePanelOpen, diff --git a/code/core/src/manager/components/mobile/a11y/MobileA11yStatement.stories.tsx b/code/core/src/manager/components/mobile/a11y/MobileA11yStatement.stories.tsx new file mode 100644 index 000000000000..811e78e73575 --- /dev/null +++ b/code/core/src/manager/components/mobile/a11y/MobileA11yStatement.stories.tsx @@ -0,0 +1,69 @@ +import React, { useEffect } from 'react'; + +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { ManagerContext } from 'storybook/manager-api'; +import { within } from 'storybook/test'; + +import { LayoutProvider, useLayout } from '../../layout/LayoutProvider'; +import { MobileA11yStatement } from './MobileA11yStatement'; + +/** A helper component to open the about page via the MobileLayoutContext */ +const OpenAboutHelper = ({ children }: { children: any }) => { + const { setMobileA11yStatementOpen } = useLayout(); + useEffect(() => { + setMobileA11yStatementOpen(true); + }, [setMobileA11yStatementOpen]); + return children; +}; + +const meta = { + component: MobileA11yStatement, + title: 'Mobile/AccessibilityStatement', + globals: { sb_theme: 'light' }, + parameters: { + layout: 'fullscreen', + viewport: { + defaultViewport: 'mobile1', + }, + chromatic: { viewports: [320] }, + }, + decorators: [ + (storyFn) => { + return ( + ({ + version: '7.2.0', + }), + }, + } as any + } + > + + {storyFn()} + + + ); + }, + ], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; +export const Dark: Story = { + globals: { sb_theme: 'dark' }, +}; + +export const Closed: Story = { + play: async ({ canvasElement }) => { + await new Promise((resolve) => setTimeout(resolve, 1000)); + const closeButton = within(canvasElement).getByText('Back'); + closeButton.click(); + }, +}; diff --git a/code/core/src/manager/components/mobile/a11y/MobileA11yStatement.tsx b/code/core/src/manager/components/mobile/a11y/MobileA11yStatement.tsx new file mode 100644 index 000000000000..5104dfa14856 --- /dev/null +++ b/code/core/src/manager/components/mobile/a11y/MobileA11yStatement.tsx @@ -0,0 +1,106 @@ +import type { FC } from 'react'; +import React, { useEffect, useRef } from 'react'; + +import { Button, ScrollArea } from 'storybook/internal/components'; + +import { ArrowLeftIcon } from '@storybook/icons'; + +import { useTransitionState } from 'react-transition-state'; +import { keyframes, styled } from 'storybook/theming'; + +import { MOBILE_TRANSITION_DURATION } from '../../../constants'; +import { A11yStatement } from '../../a11y/A11yStatement'; +import { useLayout } from '../../layout/LayoutProvider'; + +export const MobileA11yStatement: FC = () => { + const { isMobileA11yStatementOpen, setMobileA11yStatementOpen } = useLayout(); + const pageRef = useRef(null); + + const [state, toggle] = useTransitionState({ + timeout: MOBILE_TRANSITION_DURATION, + mountOnEnter: true, + unmountOnExit: true, + }); + + // Update transition state when isMobileA11yStatementOpen changes + useEffect(() => { + toggle(isMobileA11yStatementOpen); + }, [isMobileA11yStatementOpen, toggle]); + + if (!state.isMounted) { + return null; + } + + return ( + + + + setMobileA11yStatementOpen(false)} + ariaLabel="Close accessibility statement" + variant="ghost" + > + + Back + + + + + + ); +}; + +const slideFromRight = keyframes({ + from: { + opacity: 0, + transform: 'translate(20px, 0)', + }, + to: { + opacity: 1, + transform: 'translate(0, 0)', + }, +}); + +const slideToRight = keyframes({ + from: { + opacity: 1, + transform: 'translate(0, 0)', + }, + to: { + opacity: 0, + transform: 'translate(20px, 0)', + }, +}); + +const Container = styled.div<{ $status: string; $transitionDuration: number }>( + ({ theme, $status, $transitionDuration }) => ({ + position: 'absolute', + width: '100%', + height: '100%', + top: 0, + left: 0, + zIndex: 11, + overflow: 'auto', + color: theme.color.defaultText, + background: theme.background.content, + animation: + $status === 'exiting' + ? `${slideToRight} ${$transitionDuration}ms` + : `${slideFromRight} ${$transitionDuration}ms`, + }) +); + +const InnerArea = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: 20, + padding: '25px 12px 20px', +}); + +const CloseButton = styled(Button)({ + alignSelf: 'start', +}); diff --git a/code/core/src/manager/components/mobile/navigation/MobileMenuDrawer.tsx b/code/core/src/manager/components/mobile/navigation/MobileMenuDrawer.tsx index b6e450afc0e5..2cd7b789140d 100644 --- a/code/core/src/manager/components/mobile/navigation/MobileMenuDrawer.tsx +++ b/code/core/src/manager/components/mobile/navigation/MobileMenuDrawer.tsx @@ -6,6 +6,7 @@ import { Modal } from 'storybook/internal/components'; import { styled } from 'storybook/theming'; import { MOBILE_TRANSITION_DURATION } from '../../../constants'; +import { MobileA11yStatement } from '../a11y/MobileA11yStatement'; import { MobileAbout } from '../about/MobileAbout'; interface MobileMenuDrawerProps { @@ -38,6 +39,7 @@ export const MobileMenuDrawer: FC = ({ onOpenChange={onOpenChange} > {children} + ); diff --git a/code/core/src/manager/components/sidebar/Heading.stories.tsx b/code/core/src/manager/components/sidebar/Heading.stories.tsx index d74b0a82c09b..3a272090fe82 100644 --- a/code/core/src/manager/components/sidebar/Heading.stories.tsx +++ b/code/core/src/manager/components/sidebar/Heading.stories.tsx @@ -239,3 +239,17 @@ export const SkipToCanvasLinkFocused: StoryObj = { screen.getAllByText('Skip to canvas').forEach((x) => x.focus()); }, }; + +export const AccessibilityStatementLinkFocused: StoryObj = { + args: { + menu: menuItems, + a11yStatementHref: './?path=/settings/accessibility-statement', + isLoading: false, + }, + globals: { sb_theme: 'light' }, + parameters: { layout: 'padded', chromatic: { delay: 300 } }, + play: () => { + // focus each instance for chromatic/storybook's stacked theme + screen.getAllByText('Accessibility Statement').forEach((x) => x.focus()); + }, +}; diff --git a/code/core/src/manager/components/sidebar/Heading.tsx b/code/core/src/manager/components/sidebar/Heading.tsx index 423a2378b865..353e0628868d 100644 --- a/code/core/src/manager/components/sidebar/Heading.tsx +++ b/code/core/src/manager/components/sidebar/Heading.tsx @@ -6,15 +6,15 @@ import { Button } from 'storybook/internal/components'; import { styled } from 'storybook/theming'; import { Brand } from './Brand'; -import type { MenuList, SidebarMenuProps } from './Menu'; +import type { MenuList } from './Menu'; import { SidebarMenu } from './Menu'; export interface HeadingProps { menuHighlighted?: boolean; menu: MenuList; skipLinkHref?: string; + a11yStatementHref?: string; isLoading: boolean; - onMenuClick?: SidebarMenuProps['onClick']; } const BrandArea = styled.div(({ theme }) => ({ @@ -81,8 +81,7 @@ export const Heading: FC> = menuHighlighted = false, menu, skipLinkHref, - isLoading, - onMenuClick, + a11yStatementHref, ...props }) => { return ( @@ -95,11 +94,19 @@ export const Heading: FC> = )} + {a11yStatementHref && ( + + + Accessibility Statement + + + )} + - + ); }; diff --git a/code/core/src/manager/components/sidebar/Menu.stories.tsx b/code/core/src/manager/components/sidebar/Menu.stories.tsx index 4276e5cfdc34..d70b76836d5f 100644 --- a/code/core/src/manager/components/sidebar/Menu.stories.tsx +++ b/code/core/src/manager/components/sidebar/Menu.stories.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { type FC, useEffect } from 'react'; import { TooltipLinkList } from 'storybook/internal/components'; @@ -13,7 +13,7 @@ import { styled } from 'storybook/theming'; import { initialState } from '../../../shared/checklist-store/checklistData.state'; import { useMenu } from '../../container/Menu'; import { internal_universalChecklistStore as mockStore } from '../../manager-stores.mock'; -import { LayoutProvider } from '../layout/LayoutProvider'; +import { LayoutProvider, useLayout } from '../layout/LayoutProvider'; import { type MenuList, SidebarMenu } from './Menu'; const fakemenu: MenuList = [ @@ -222,3 +222,88 @@ export const ExpandedWithWhatsNew: Story = { await expect(releaseNotes).not.toBeInTheDocument(); }, }; + +const LayoutContainerPrinter: FC<{ children: React.ReactNode }> = ({ children }) => { + const { isMobileMenuOpen, isMobileA11yStatementOpen, isMobileAboutOpen, setMobileMenuOpen } = + useLayout(); + + useEffect(() => { + setMobileMenuOpen(true); + }, [setMobileMenuOpen]); + + return ( + <> + {children} +
+
    +
  • isMobileMenuOpen: {isMobileMenuOpen.toString()}
  • +
  • isMobileA11yStatementOpen: {isMobileA11yStatementOpen.toString()}
  • +
  • isMobileAboutOpen: {isMobileAboutOpen.toString()}
  • +
+ + ); +}; + +const MobileLayoutProvider: FC<{ children: React.ReactNode }> = ({ children }) => { + return ( + + {children} + + ); +}; + +export const Mobile: Story = { + decorators: [ + (storyFn) => ( + + {storyFn()} + + ), + ], +}; + +export const MobileA11yStatement: Story = { + name: 'MobileA11yStatement', + decorators: [ + (storyFn) => ( + + {storyFn()} + + ), + ], + play: async ({ canvas }) => { + const a11yButton = await canvas.findByRole('button', { name: 'Accessibility statement' }); + await userEvent.click(a11yButton); + await expect(canvas.getByText('isMobileA11yStatementOpen: true')).toBeInTheDocument(); + }, +}; + +export const MobileAbout: Story = { + decorators: [ + (storyFn) => ( + + {storyFn()} + + ), + ], + play: async ({ canvas }) => { + const aboutButton = await canvas.findByRole('button', { name: 'About Storybook' }); + await userEvent.click(aboutButton); + await expect(canvas.getByText('isMobileAboutOpen: true')).toBeInTheDocument(); + }, +}; + +export const MobileClose: Story = { + decorators: [ + (storyFn) => ( + + {storyFn()} + + ), + ], + play: async ({ canvas }) => { + const closeButton = await canvas.findByRole('button', { name: /Close menu/i }); + await userEvent.click(closeButton); + await expect(canvas.getByText('isMobileMenuOpen: false')).toBeInTheDocument(); + }, +}; diff --git a/code/core/src/manager/components/sidebar/Menu.tsx b/code/core/src/manager/components/sidebar/Menu.tsx index 8a6bcfb484ca..235e0d1972e4 100644 --- a/code/core/src/manager/components/sidebar/Menu.tsx +++ b/code/core/src/manager/components/sidebar/Menu.tsx @@ -3,7 +3,7 @@ import React, { useState } from 'react'; import { ActionList, Button, PopoverProvider, ToggleButton } from 'storybook/internal/components'; -import { CloseIcon, CogIcon } from '@storybook/icons'; +import { AccessibilityIcon, CloseIcon, CogIcon } from '@storybook/icons'; import { transparentize } from 'polished'; import { type Theme, css, styled } from 'storybook/theming'; @@ -133,22 +133,32 @@ const SidebarMenuList: FC<{ export interface SidebarMenuProps { menu: MenuList; isHighlighted?: boolean; - onClick?: React.MouseEventHandler; } -export const SidebarMenu: FC = ({ menu, isHighlighted, onClick }) => { +export const SidebarMenu: FC = ({ menu, isHighlighted }) => { const [isTooltipVisible, setIsTooltipVisible] = useState(false); - const { isMobile, setMobileMenuOpen } = useLayout(); + const { isMobile, setMobileAboutOpen, setMobileA11yStatementOpen, setMobileMenuOpen } = + useLayout(); if (isMobile) { return ( + setMobileA11yStatementOpen(true)} + isMobile={true} + > + + setMobileAboutOpen(true)} isMobile={true} > diff --git a/code/core/src/manager/components/sidebar/Sidebar.tsx b/code/core/src/manager/components/sidebar/Sidebar.tsx index d16b5b1a51b2..1939d30a2b63 100644 --- a/code/core/src/manager/components/sidebar/Sidebar.tsx +++ b/code/core/src/manager/components/sidebar/Sidebar.tsx @@ -15,7 +15,6 @@ import { useLayout } from '../layout/LayoutProvider'; import { ChecklistWidget } from './ChecklistWidget'; import { CreateNewStoryFileModal } from './CreateNewStoryFileModal'; import { Explorer } from './Explorer'; -import type { HeadingProps } from './Heading'; import { Heading } from './Heading'; import { Search } from './Search'; import { SearchResults } from './SearchResults'; @@ -111,7 +110,6 @@ export interface SidebarProps extends API_LoadedRefData { refId?: string; menuHighlighted?: boolean; enableShortcuts?: boolean; - onMenuClick?: HeadingProps['onMenuClick']; showCreateStoryButton?: boolean; indexJson?: StoryIndex; isDevelopment?: boolean; @@ -130,7 +128,6 @@ export const Sidebar = React.memo(function Sidebar({ enableShortcuts = true, isDevelopment = global.CONFIG_TYPE === 'DEVELOPMENT', refs = {}, - onMenuClick, showCreateStoryButton = isDevelopment && isRendererReact, }: SidebarProps) { const [isFileSearchModalOpen, setIsFileSearchModalOpen] = useState(false); @@ -163,8 +160,8 @@ export const Sidebar = React.memo(function Sidebar({ menuHighlighted={menuHighlighted} menu={menu} skipLinkHref="#storybook-preview-wrapper" + a11yStatementHref="./?path=/settings/accessibility-statement" isLoading={isLoading} - onMenuClick={onMenuClick} /> {!isLoading && global.CONFIG_TYPE === 'DEVELOPMENT' && } diff --git a/code/core/src/manager/container/Menu.tsx b/code/core/src/manager/container/Menu.tsx index 08bc657f2711..56be2255a0ca 100644 --- a/code/core/src/manager/container/Menu.tsx +++ b/code/core/src/manager/container/Menu.tsx @@ -6,6 +6,7 @@ import { STORIES_COLLAPSE_ALL } from 'storybook/internal/core-events'; import { global } from '@storybook/global'; import { + AccessibilityIcon, CheckIcon, CommandIcon, DocumentIcon, @@ -88,6 +89,17 @@ export const useMenu = ({ [api] ); + const a11yStatement = useMemo( + () => ({ + id: 'accessibility-statement', + title: 'Accessibility statement', + onClick: () => api.changeSettingsTab('accessibility-statement'), + closeOnClick: true, + icon: , + }), + [api] + ); + const guide = useMemo( () => ({ id: 'guide', @@ -249,10 +261,11 @@ export const useMenu = ({ ], [sidebarToggle, toolbarToogle, addonsToggle, up, down, prev, next, collapse], getAddonsShortcuts(), - [documentation], + [a11yStatement, documentation], ] satisfies NormalLink[][], [ about, + a11yStatement, guide, documentation, shortcuts, diff --git a/code/core/src/manager/container/Sidebar.tsx b/code/core/src/manager/container/Sidebar.tsx index 7f664c4e0855..2b198b21c758 100755 --- a/code/core/src/manager/container/Sidebar.tsx +++ b/code/core/src/manager/container/Sidebar.tsx @@ -3,17 +3,12 @@ import React from 'react'; import type { Combo, StoriesHash } from 'storybook/manager-api'; import { Consumer, experimental_useStatusStore } from 'storybook/manager-api'; -import type { SidebarProps as SidebarComponentProps } from '../components/sidebar/Sidebar'; import { Sidebar as SidebarComponent } from '../components/sidebar/Sidebar'; import { useMenu } from './Menu'; export type Item = StoriesHash[keyof StoriesHash]; -interface SidebarProps { - onMenuClick?: SidebarComponentProps['onMenuClick']; -} - -const Sidebar = React.memo(function Sideber({ onMenuClick }: SidebarProps) { +const Sidebar = React.memo(function SidebarContainer() { const mapper = ({ state, api }: Combo) => { const { ui: { name, url, enableShortcuts }, @@ -63,7 +58,6 @@ const Sidebar = React.memo(function Sideber({ onMenuClick }: SidebarProps) { diff --git a/code/core/src/manager/settings/A11yStatementPage.tsx b/code/core/src/manager/settings/A11yStatementPage.tsx new file mode 100644 index 000000000000..d3a6c5b322c7 --- /dev/null +++ b/code/core/src/manager/settings/A11yStatementPage.tsx @@ -0,0 +1,21 @@ +import type { FC } from 'react'; +import React from 'react'; + +import { styled } from 'storybook/theming'; + +import { A11yStatement } from '../components/a11y/A11yStatement'; + +const MainWrapper = styled.main(({ theme }) => ({ + fontSize: theme.typography.size.s2, + padding: `3rem 20px`, + maxWidth: 600, + margin: '0 auto', +})); + +const A11yStatementPage: FC = () => ( + + + +); + +export { A11yStatementPage }; diff --git a/code/core/src/manager/settings/index.tsx b/code/core/src/manager/settings/index.tsx index 6a75d4aa78c2..329840e56948 100644 --- a/code/core/src/manager/settings/index.tsx +++ b/code/core/src/manager/settings/index.tsx @@ -12,6 +12,7 @@ import { types, useStorybookApi, useStorybookState } from 'storybook/manager-api import { styled } from 'storybook/theming'; import { matchesKeyCode, matchesModifiers } from '../keybinding'; +import { A11yStatementPage } from './A11yStatementPage'; import { AboutPage } from './AboutPage'; import { GuidePage } from './GuidePage'; import { ShortcutsPage } from './ShortcutsPage'; @@ -86,6 +87,16 @@ const Pages: FC<{ ), }); + tabsToInclude.push({ + id: 'accessibility-statement', + title: 'Accessibility statement', + children: ( + + + + ), + }); + if (enableWhatsNew) { tabsToInclude.push({ id: 'whats-new',