@a11ypros/a11y-ui-components 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/custom.css +69 -0
- package/.storybook/main.ts +46 -0
- package/.storybook/manager.ts +26 -0
- package/.storybook/package.json +6 -0
- package/.storybook/preview.tsx +31 -0
- package/.storybook/public/logo.png +0 -0
- package/.storybook/vite.config.ts +24 -0
- package/.storybook/welcome.mdx +97 -0
- package/DEPLOYMENT.md +154 -0
- package/README.md +227 -0
- package/apps/web/app/(docs)/audit/audit.css +269 -0
- package/apps/web/app/(docs)/audit/page.tsx +271 -0
- package/apps/web/app/(docs)/components/button/page.tsx +49 -0
- package/apps/web/app/(docs)/components/form/page.tsx +92 -0
- package/apps/web/app/(docs)/components/link/page.tsx +31 -0
- package/apps/web/app/(docs)/components/modal/page.tsx +41 -0
- package/apps/web/app/(docs)/components/page.tsx +37 -0
- package/apps/web/app/(docs)/components/table/page.tsx +54 -0
- package/apps/web/app/(docs)/components/tabs/page.tsx +61 -0
- package/apps/web/app/(docs)/components/toast/page.tsx +51 -0
- package/apps/web/app/api/audit/route.ts +128 -0
- package/apps/web/app/favicon.ico +0 -0
- package/apps/web/app/layout.tsx +20 -0
- package/apps/web/app/page.tsx +17 -0
- package/apps/web/app/styles/globals.css +5 -0
- package/apps/web/next-env.d.ts +5 -0
- package/apps/web/next.config.js +21 -0
- package/apps/web/package.json +28 -0
- package/apps/web/public/_headers +17 -0
- package/apps/web/public/_redirects +31 -0
- package/apps/web/public/logo.png +0 -0
- package/apps/web/tsconfig.json +29 -0
- package/netlify/functions/audit.ts +163 -0
- package/netlify.toml +37 -0
- package/package.json +30 -58
- package/packages/design-system/README.md +252 -0
- package/packages/design-system/package.json +68 -0
- package/packages/design-system/scripts/copy-css.js +63 -0
- package/packages/design-system/src/components/Button/Button.stories.tsx +228 -0
- package/packages/design-system/src/components/Button/Button.tsx +137 -0
- package/packages/design-system/src/components/Button/index.ts +3 -0
- package/packages/design-system/src/components/DataTable/DataTable.stories.tsx +211 -0
- package/packages/design-system/src/components/DataTable/DataTable.tsx +293 -0
- package/packages/design-system/src/components/DataTable/index.ts +3 -0
- package/packages/design-system/src/components/Form/Checkbox.stories.tsx +252 -0
- package/packages/design-system/src/components/Form/Checkbox.tsx +114 -0
- package/packages/design-system/src/components/Form/Fieldset.stories.tsx +210 -0
- package/packages/design-system/src/components/Form/Fieldset.tsx +71 -0
- package/packages/design-system/src/components/Form/Input.stories.tsx +164 -0
- package/packages/design-system/src/components/Form/Input.tsx +113 -0
- package/packages/design-system/src/components/Form/Label.tsx +56 -0
- package/packages/design-system/src/components/Form/Radio.stories.tsx +265 -0
- package/packages/design-system/src/components/Form/Radio.tsx +147 -0
- package/packages/design-system/src/components/Form/Select.stories.tsx +295 -0
- package/packages/design-system/src/components/Form/Select.tsx +160 -0
- package/packages/design-system/src/components/Form/Textarea.stories.tsx +253 -0
- package/packages/design-system/src/components/Form/Textarea.tsx +145 -0
- package/packages/design-system/src/components/Form/index.ts +8 -0
- package/packages/design-system/src/components/Link/Link.stories.tsx +128 -0
- package/packages/design-system/src/components/Link/Link.tsx +117 -0
- package/packages/design-system/src/components/Link/index.ts +3 -0
- package/packages/design-system/src/components/Modal/Modal.stories.tsx +165 -0
- package/packages/design-system/src/components/Modal/Modal.tsx +202 -0
- package/packages/design-system/src/components/Modal/index.ts +3 -0
- package/packages/design-system/src/components/Tabs/Tabs.stories.tsx +213 -0
- package/packages/design-system/src/components/Tabs/Tabs.tsx +248 -0
- package/packages/design-system/src/components/Tabs/index.ts +3 -0
- package/packages/design-system/src/components/Toast/Toast.stories.tsx +153 -0
- package/packages/design-system/src/components/Toast/Toast.tsx +175 -0
- package/packages/design-system/src/components/Toast/ToastProvider.tsx +73 -0
- package/packages/design-system/src/components/Toast/index.ts +5 -0
- package/packages/design-system/src/hooks/useAriaLive.ts +51 -0
- package/packages/design-system/src/hooks/useFocusReturn.ts +40 -0
- package/packages/design-system/src/hooks/useFocusTrap.ts +82 -0
- package/{dist/index.js → packages/design-system/src/index.ts} +4 -0
- package/packages/design-system/src/styles/index.ts +3 -0
- package/packages/design-system/src/tokens/breakpoints.ts +28 -0
- package/packages/design-system/src/tokens/colors.ts +98 -0
- package/packages/design-system/src/tokens/index.ts +6 -0
- package/packages/design-system/src/tokens/motion.ts +41 -0
- package/packages/design-system/src/tokens/spacing.ts +24 -0
- package/packages/design-system/src/tokens/theme.ts +19 -0
- package/packages/design-system/src/tokens/typography.ts +64 -0
- package/packages/design-system/src/utils/aria.ts +108 -0
- package/packages/design-system/src/utils/focus.ts +87 -0
- package/packages/design-system/src/utils/index.ts +4 -0
- package/packages/design-system/src/utils/keyboard.ts +77 -0
- package/packages/design-system/tsconfig.json +17 -0
- package/public/logo.png +0 -0
- package/scripts/fix-storybook-paths.js +53 -0
- package/tsconfig.json +20 -0
- package/dist/components/Button/Button.d.ts +0 -37
- package/dist/components/Button/Button.d.ts.map +0 -1
- package/dist/components/Button/Button.js +0 -52
- package/dist/components/Button/index.d.ts +0 -3
- package/dist/components/Button/index.d.ts.map +0 -1
- package/dist/components/Button/index.js +0 -1
- package/dist/components/DataTable/DataTable.d.ts +0 -71
- package/dist/components/DataTable/DataTable.d.ts.map +0 -1
- package/dist/components/DataTable/DataTable.js +0 -122
- package/dist/components/DataTable/index.d.ts +0 -3
- package/dist/components/DataTable/index.d.ts.map +0 -1
- package/dist/components/DataTable/index.js +0 -1
- package/dist/components/Form/Checkbox.d.ts +0 -36
- package/dist/components/Form/Checkbox.d.ts.map +0 -1
- package/dist/components/Form/Checkbox.js +0 -39
- package/dist/components/Form/Fieldset.d.ts +0 -33
- package/dist/components/Form/Fieldset.d.ts.map +0 -1
- package/dist/components/Form/Fieldset.js +0 -34
- package/dist/components/Form/Input.d.ts +0 -37
- package/dist/components/Form/Input.d.ts.map +0 -1
- package/dist/components/Form/Input.js +0 -41
- package/dist/components/Form/Label.d.ts +0 -30
- package/dist/components/Form/Label.d.ts.map +0 -1
- package/dist/components/Form/Label.js +0 -30
- package/dist/components/Form/Radio.d.ts +0 -53
- package/dist/components/Form/Radio.d.ts.map +0 -1
- package/dist/components/Form/Radio.js +0 -39
- package/dist/components/Form/Select.d.ts +0 -51
- package/dist/components/Form/Select.d.ts.map +0 -1
- package/dist/components/Form/Select.js +0 -49
- package/dist/components/Form/Textarea.d.ts +0 -44
- package/dist/components/Form/Textarea.d.ts.map +0 -1
- package/dist/components/Form/Textarea.js +0 -43
- package/dist/components/Form/index.d.ts +0 -8
- package/dist/components/Form/index.d.ts.map +0 -1
- package/dist/components/Form/index.js +0 -7
- package/dist/components/Link/Link.d.ts +0 -34
- package/dist/components/Link/Link.d.ts.map +0 -1
- package/dist/components/Link/Link.js +0 -48
- package/dist/components/Link/index.d.ts +0 -3
- package/dist/components/Link/index.d.ts.map +0 -1
- package/dist/components/Link/index.js +0 -1
- package/dist/components/Modal/Modal.d.ts +0 -64
- package/dist/components/Modal/Modal.d.ts.map +0 -1
- package/dist/components/Modal/Modal.js +0 -108
- package/dist/components/Modal/index.d.ts +0 -3
- package/dist/components/Modal/index.d.ts.map +0 -1
- package/dist/components/Modal/index.js +0 -1
- package/dist/components/Tabs/Tabs.d.ts +0 -63
- package/dist/components/Tabs/Tabs.d.ts.map +0 -1
- package/dist/components/Tabs/Tabs.js +0 -134
- package/dist/components/Tabs/index.d.ts +0 -3
- package/dist/components/Tabs/index.d.ts.map +0 -1
- package/dist/components/Tabs/index.js +0 -1
- package/dist/components/Toast/Toast.d.ts +0 -59
- package/dist/components/Toast/Toast.d.ts.map +0 -1
- package/dist/components/Toast/Toast.js +0 -91
- package/dist/components/Toast/ToastProvider.d.ts +0 -22
- package/dist/components/Toast/ToastProvider.d.ts.map +0 -1
- package/dist/components/Toast/ToastProvider.js +0 -33
- package/dist/components/Toast/index.d.ts +0 -5
- package/dist/components/Toast/index.d.ts.map +0 -1
- package/dist/components/Toast/index.js +0 -2
- package/dist/hooks/useAriaLive.d.ts +0 -9
- package/dist/hooks/useAriaLive.d.ts.map +0 -1
- package/dist/hooks/useAriaLive.js +0 -39
- package/dist/hooks/useFocusReturn.d.ts +0 -9
- package/dist/hooks/useFocusReturn.d.ts.map +0 -1
- package/dist/hooks/useFocusReturn.js +0 -33
- package/dist/hooks/useFocusTrap.d.ts +0 -9
- package/dist/hooks/useFocusTrap.d.ts.map +0 -1
- package/dist/hooks/useFocusTrap.js +0 -68
- package/dist/index.d.ts +0 -22
- package/dist/index.d.ts.map +0 -1
- package/dist/styles/index.d.ts +0 -3
- package/dist/styles/index.d.ts.map +0 -1
- package/dist/styles/index.js +0 -1
- package/dist/tokens/breakpoints.d.ts +0 -25
- package/dist/tokens/breakpoints.d.ts.map +0 -1
- package/dist/tokens/breakpoints.js +0 -23
- package/dist/tokens/colors.d.ts +0 -81
- package/dist/tokens/colors.d.ts.map +0 -1
- package/dist/tokens/colors.js +0 -86
- package/dist/tokens/index.d.ts +0 -6
- package/dist/tokens/index.d.ts.map +0 -1
- package/dist/tokens/index.js +0 -5
- package/dist/tokens/motion.d.ts +0 -30
- package/dist/tokens/motion.d.ts.map +0 -1
- package/dist/tokens/motion.js +0 -34
- package/dist/tokens/spacing.d.ts +0 -22
- package/dist/tokens/spacing.d.ts.map +0 -1
- package/dist/tokens/spacing.js +0 -20
- package/dist/tokens/theme.d.ts +0 -159
- package/dist/tokens/theme.d.ts.map +0 -1
- package/dist/tokens/theme.js +0 -15
- package/dist/tokens/typography.d.ts +0 -45
- package/dist/tokens/typography.d.ts.map +0 -1
- package/dist/tokens/typography.js +0 -56
- package/dist/utils/aria.d.ts +0 -60
- package/dist/utils/aria.d.ts.map +0 -1
- package/dist/utils/aria.js +0 -86
- package/dist/utils/focus.d.ts +0 -30
- package/dist/utils/focus.d.ts.map +0 -1
- package/dist/utils/focus.js +0 -80
- package/dist/utils/index.d.ts +0 -4
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -3
- package/dist/utils/keyboard.d.ts +0 -38
- package/dist/utils/keyboard.d.ts.map +0 -1
- package/dist/utils/keyboard.js +0 -59
- /package/{dist → packages/design-system/src}/components/Button/Button.css +0 -0
- /package/{dist → packages/design-system/src}/components/DataTable/DataTable.css +0 -0
- /package/{dist → packages/design-system/src}/components/Form/Checkbox.css +0 -0
- /package/{dist → packages/design-system/src}/components/Form/Fieldset.css +0 -0
- /package/{dist → packages/design-system/src}/components/Form/Input.css +0 -0
- /package/{dist → packages/design-system/src}/components/Form/Label.css +0 -0
- /package/{dist → packages/design-system/src}/components/Form/Radio.css +0 -0
- /package/{dist → packages/design-system/src}/components/Form/Select.css +0 -0
- /package/{dist → packages/design-system/src}/components/Form/Textarea.css +0 -0
- /package/{dist → packages/design-system/src}/components/Link/Link.css +0 -0
- /package/{dist → packages/design-system/src}/components/Modal/Modal.css +0 -0
- /package/{dist → packages/design-system/src}/components/Tabs/Tabs.css +0 -0
- /package/{dist → packages/design-system/src}/components/Toast/Toast.css +0 -0
- /package/{dist → packages/design-system/src}/components/Toast/ToastProvider.css +0 -0
- /package/{dist → packages/design-system/src}/styles/components.css +0 -0
- /package/{dist → packages/design-system/src}/styles/global.css +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Link } from '@a11ypros/a11y-ui-components'
|
|
4
|
+
|
|
5
|
+
export default function LinkPage() {
|
|
6
|
+
return (
|
|
7
|
+
<main style={{ padding: '2rem' }}>
|
|
8
|
+
<h1>Link Component</h1>
|
|
9
|
+
<p>Accessible link component with external link detection.</p>
|
|
10
|
+
|
|
11
|
+
<section style={{ marginTop: '2rem' }}>
|
|
12
|
+
<h2>Examples</h2>
|
|
13
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
|
14
|
+
<Link href="/components">Internal Link</Link>
|
|
15
|
+
<Link href="https://example.com" external>External Link</Link>
|
|
16
|
+
<Link href="#main-content" skip>Skip Link</Link>
|
|
17
|
+
</div>
|
|
18
|
+
</section>
|
|
19
|
+
|
|
20
|
+
<section style={{ marginTop: '2rem' }}>
|
|
21
|
+
<h2>Accessibility</h2>
|
|
22
|
+
<ul>
|
|
23
|
+
<li>WCAG 2.4.4 Link Purpose: Clear link text or aria-label</li>
|
|
24
|
+
<li>WCAG 2.4.7 Focus Visible: Clear focus indicators</li>
|
|
25
|
+
<li>WCAG 4.1.2 Name, Role, Value: Proper semantic HTML</li>
|
|
26
|
+
</ul>
|
|
27
|
+
</section>
|
|
28
|
+
</main>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { Modal, Button } from '@a11ypros/a11y-ui-components'
|
|
5
|
+
|
|
6
|
+
export default function ModalPage() {
|
|
7
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<main style={{ padding: '2rem' }}>
|
|
11
|
+
<h1>Modal Component</h1>
|
|
12
|
+
<p>Accessible modal dialog with focus trap and keyboard support.</p>
|
|
13
|
+
|
|
14
|
+
<section style={{ marginTop: '2rem' }}>
|
|
15
|
+
<h2>Example</h2>
|
|
16
|
+
<Button onClick={() => setIsOpen(true)}>Open Modal</Button>
|
|
17
|
+
<Modal
|
|
18
|
+
isOpen={isOpen}
|
|
19
|
+
onClose={() => setIsOpen(false)}
|
|
20
|
+
title="Example Modal"
|
|
21
|
+
>
|
|
22
|
+
<p>This is a modal dialog. Press ESC to close or click outside.</p>
|
|
23
|
+
<div style={{ marginTop: '1rem' }}>
|
|
24
|
+
<Button onClick={() => setIsOpen(false)}>Close</Button>
|
|
25
|
+
</div>
|
|
26
|
+
</Modal>
|
|
27
|
+
</section>
|
|
28
|
+
|
|
29
|
+
<section style={{ marginTop: '2rem' }}>
|
|
30
|
+
<h2>Accessibility</h2>
|
|
31
|
+
<ul>
|
|
32
|
+
<li>WCAG 2.1.1 Keyboard: ESC key support, focus trap</li>
|
|
33
|
+
<li>WCAG 2.1.2 No Keyboard Trap: Focus returns to trigger</li>
|
|
34
|
+
<li>WCAG 2.4.3 Focus Order: Focus trapped within modal</li>
|
|
35
|
+
<li>WCAG 4.1.2 Name, Role, Value: ARIA modal pattern</li>
|
|
36
|
+
</ul>
|
|
37
|
+
</section>
|
|
38
|
+
</main>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link'
|
|
4
|
+
import { Button, Link as DesignSystemLink } from '@a11ypros/a11y-ui-components'
|
|
5
|
+
|
|
6
|
+
export default function ComponentsPage() {
|
|
7
|
+
return (
|
|
8
|
+
<main>
|
|
9
|
+
<h1>Design System Components</h1>
|
|
10
|
+
<p>Browse and test all accessible components in the design system.</p>
|
|
11
|
+
|
|
12
|
+
<nav>
|
|
13
|
+
<h2>Components</h2>
|
|
14
|
+
<ul>
|
|
15
|
+
<li><Link href="/components/button">Button</Link></li>
|
|
16
|
+
<li><Link href="/components/link">Link</Link></li>
|
|
17
|
+
<li><Link href="/components/modal">Modal</Link></li>
|
|
18
|
+
<li><Link href="/components/form">Form Components</Link></li>
|
|
19
|
+
<li><Link href="/components/table">Data Table</Link></li>
|
|
20
|
+
<li><Link href="/components/toast">Toast</Link></li>
|
|
21
|
+
<li><Link href="/components/tabs">Tabs</Link></li>
|
|
22
|
+
</ul>
|
|
23
|
+
</nav>
|
|
24
|
+
|
|
25
|
+
<section>
|
|
26
|
+
<h2>Quick Examples</h2>
|
|
27
|
+
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap', marginTop: '1rem' }}>
|
|
28
|
+
<Button variant="primary">Primary Button</Button>
|
|
29
|
+
<Button variant="secondary">Secondary Button</Button>
|
|
30
|
+
<Button variant="ghost">Ghost Button</Button>
|
|
31
|
+
<DesignSystemLink href="/audit">Go to Audit Tool</DesignSystemLink>
|
|
32
|
+
</div>
|
|
33
|
+
</section>
|
|
34
|
+
</main>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { DataTable } from '@a11ypros/a11y-ui-components'
|
|
5
|
+
|
|
6
|
+
const sampleData = [
|
|
7
|
+
{ id: '1', name: 'John Doe', email: 'john@example.com', role: 'Admin' },
|
|
8
|
+
{ id: '2', name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
|
|
9
|
+
{ id: '3', name: 'Bob Johnson', email: 'bob@example.com', role: 'User' },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
const columns = [
|
|
13
|
+
{ key: 'name', header: 'Name', sortable: true },
|
|
14
|
+
{ key: 'email', header: 'Email', sortable: true },
|
|
15
|
+
{ key: 'role', header: 'Role', sortable: true },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
export default function TablePage() {
|
|
19
|
+
const [selectedRows, setSelectedRows] = useState<string[]>([])
|
|
20
|
+
const [sortConfig, setSortConfig] = useState<{ column: string; direction: 'asc' | 'desc' } | undefined>()
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<main style={{ padding: '2rem' }}>
|
|
24
|
+
<h1>DataTable Component</h1>
|
|
25
|
+
<p>Accessible data table with keyboard navigation and sorting.</p>
|
|
26
|
+
|
|
27
|
+
<section style={{ marginTop: '2rem' }}>
|
|
28
|
+
<h2>Example</h2>
|
|
29
|
+
<DataTable
|
|
30
|
+
data={sampleData}
|
|
31
|
+
columns={columns}
|
|
32
|
+
getRowId={(row) => row.id}
|
|
33
|
+
selectable
|
|
34
|
+
selectedRows={selectedRows}
|
|
35
|
+
onSelectionChange={setSelectedRows}
|
|
36
|
+
sortConfig={sortConfig}
|
|
37
|
+
onSortChange={(column, direction) => setSortConfig({ column, direction })}
|
|
38
|
+
caption="User list"
|
|
39
|
+
/>
|
|
40
|
+
</section>
|
|
41
|
+
|
|
42
|
+
<section style={{ marginTop: '2rem' }}>
|
|
43
|
+
<h2>Accessibility</h2>
|
|
44
|
+
<ul>
|
|
45
|
+
<li>WCAG 1.3.1 Info and Relationships: Semantic table structure</li>
|
|
46
|
+
<li>WCAG 2.1.1 Keyboard: Arrow keys, Home/End navigation</li>
|
|
47
|
+
<li>WCAG 4.1.2 Name, Role, Value: Proper ARIA attributes</li>
|
|
48
|
+
<li>WCAG 4.1.3 Status Messages: Sort announcements</li>
|
|
49
|
+
</ul>
|
|
50
|
+
</section>
|
|
51
|
+
</main>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Tabs } from '@a11ypros/a11y-ui-components'
|
|
4
|
+
|
|
5
|
+
export default function TabsPage() {
|
|
6
|
+
return (
|
|
7
|
+
<main style={{ padding: '2rem' }}>
|
|
8
|
+
<h1>Tabs Component</h1>
|
|
9
|
+
<p>Accessible tabs component with arrow key navigation.</p>
|
|
10
|
+
|
|
11
|
+
<section style={{ marginTop: '2rem' }}>
|
|
12
|
+
<h2>Example</h2>
|
|
13
|
+
<Tabs
|
|
14
|
+
aria-label="Settings tabs"
|
|
15
|
+
items={[
|
|
16
|
+
{
|
|
17
|
+
id: 'general',
|
|
18
|
+
label: 'General',
|
|
19
|
+
content: (
|
|
20
|
+
<div>
|
|
21
|
+
<h3>General Settings</h3>
|
|
22
|
+
<p>Configure general application settings here.</p>
|
|
23
|
+
</div>
|
|
24
|
+
),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'account',
|
|
28
|
+
label: 'Account',
|
|
29
|
+
content: (
|
|
30
|
+
<div>
|
|
31
|
+
<h3>Account Settings</h3>
|
|
32
|
+
<p>Manage your account preferences.</p>
|
|
33
|
+
</div>
|
|
34
|
+
),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'privacy',
|
|
38
|
+
label: 'Privacy',
|
|
39
|
+
content: (
|
|
40
|
+
<div>
|
|
41
|
+
<h3>Privacy Settings</h3>
|
|
42
|
+
<p>Control your privacy and data settings.</p>
|
|
43
|
+
</div>
|
|
44
|
+
),
|
|
45
|
+
},
|
|
46
|
+
]}
|
|
47
|
+
/>
|
|
48
|
+
</section>
|
|
49
|
+
|
|
50
|
+
<section style={{ marginTop: '2rem' }}>
|
|
51
|
+
<h2>Accessibility</h2>
|
|
52
|
+
<ul>
|
|
53
|
+
<li>WCAG 2.1.1 Keyboard: Arrow key navigation, Home/End support</li>
|
|
54
|
+
<li>WCAG 4.1.2 Name, Role, Value: ARIA tabs pattern</li>
|
|
55
|
+
<li>WCAG 2.4.3 Focus Order: Proper focus management</li>
|
|
56
|
+
</ul>
|
|
57
|
+
</section>
|
|
58
|
+
</main>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ToastProvider, useToast } from '@a11ypros/a11y-ui-components'
|
|
4
|
+
import { Button } from '@a11ypros/a11y-ui-components'
|
|
5
|
+
|
|
6
|
+
function ToastDemo() {
|
|
7
|
+
const { addToast } = useToast()
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
|
|
11
|
+
<Button onClick={() => addToast({ message: 'Info message', type: 'info' })}>
|
|
12
|
+
Show Info Toast
|
|
13
|
+
</Button>
|
|
14
|
+
<Button onClick={() => addToast({ message: 'Success!', type: 'success' })}>
|
|
15
|
+
Show Success Toast
|
|
16
|
+
</Button>
|
|
17
|
+
<Button onClick={() => addToast({ message: 'Warning message', type: 'warning' })}>
|
|
18
|
+
Show Warning Toast
|
|
19
|
+
</Button>
|
|
20
|
+
<Button onClick={() => addToast({ message: 'Error occurred', type: 'error' })}>
|
|
21
|
+
Show Error Toast
|
|
22
|
+
</Button>
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function ToastPage() {
|
|
28
|
+
return (
|
|
29
|
+
<main style={{ padding: '2rem' }}>
|
|
30
|
+
<h1>Toast Component</h1>
|
|
31
|
+
<p>Accessible toast notifications with ARIA live regions.</p>
|
|
32
|
+
|
|
33
|
+
<section style={{ marginTop: '2rem' }}>
|
|
34
|
+
<h2>Examples</h2>
|
|
35
|
+
<ToastProvider>
|
|
36
|
+
<ToastDemo />
|
|
37
|
+
</ToastProvider>
|
|
38
|
+
</section>
|
|
39
|
+
|
|
40
|
+
<section style={{ marginTop: '2rem' }}>
|
|
41
|
+
<h2>Accessibility</h2>
|
|
42
|
+
<ul>
|
|
43
|
+
<li>WCAG 4.1.3 Status Messages: ARIA live region announcements</li>
|
|
44
|
+
<li>WCAG 2.1.1 Keyboard: ESC key support</li>
|
|
45
|
+
<li>WCAG 4.1.2 Name, Role, Value: Proper ARIA attributes</li>
|
|
46
|
+
</ul>
|
|
47
|
+
</section>
|
|
48
|
+
</main>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import Anthropic from '@anthropic-ai/sdk'
|
|
3
|
+
|
|
4
|
+
const anthropic = new Anthropic({
|
|
5
|
+
apiKey: process.env.ANTHROPIC_API_KEY || '',
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
export interface AuditIssue {
|
|
9
|
+
wcagSC: string // WCAG Success Criterion (e.g., "1.3.1", "2.4.7")
|
|
10
|
+
severity: 'error' | 'warning' | 'info'
|
|
11
|
+
message: string
|
|
12
|
+
suggestion: string
|
|
13
|
+
codeSuggestion?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AuditResponse {
|
|
17
|
+
issues: AuditIssue[]
|
|
18
|
+
summary: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const WCAG_PROMPT = `You are an accessibility expert reviewing JSX/React code for WCAG 2.1 and 2.2 compliance.
|
|
22
|
+
|
|
23
|
+
Analyze the provided JSX code snippet and identify accessibility issues. For each issue:
|
|
24
|
+
1. Map it to the specific WCAG Success Criterion (SC) number (e.g., "1.3.1", "2.4.7", "4.1.2")
|
|
25
|
+
2. Determine severity: "error" (violates WCAG), "warning" (may cause issues), or "info" (best practice)
|
|
26
|
+
3. Provide a clear message explaining the issue
|
|
27
|
+
4. Suggest how to fix it
|
|
28
|
+
5. If applicable, provide corrected code
|
|
29
|
+
|
|
30
|
+
Focus on:
|
|
31
|
+
- Semantic HTML (1.3.1)
|
|
32
|
+
- Keyboard accessibility (2.1.1, 2.1.2)
|
|
33
|
+
- Focus management (2.4.7)
|
|
34
|
+
- ARIA usage (4.1.2, 4.1.3)
|
|
35
|
+
- Color contrast (1.4.3)
|
|
36
|
+
- Form labels (2.5.3)
|
|
37
|
+
- Status messages (4.1.3)
|
|
38
|
+
|
|
39
|
+
Return your response as a JSON object with this structure:
|
|
40
|
+
{
|
|
41
|
+
"issues": [
|
|
42
|
+
{
|
|
43
|
+
"wcagSC": "2.4.7",
|
|
44
|
+
"severity": "error",
|
|
45
|
+
"message": "Button lacks visible focus indicator",
|
|
46
|
+
"suggestion": "Add focus-visible styles with 2px outline",
|
|
47
|
+
"codeSuggestion": "button:focus-visible { outline: 2px solid #0ea5e9; }"
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"summary": "Found 3 issues: 2 errors, 1 warning"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Only return valid JSON, no markdown formatting.`
|
|
54
|
+
|
|
55
|
+
export async function POST(request: NextRequest) {
|
|
56
|
+
try {
|
|
57
|
+
const { code } = await request.json()
|
|
58
|
+
|
|
59
|
+
if (!code || typeof code !== 'string') {
|
|
60
|
+
return NextResponse.json(
|
|
61
|
+
{ error: 'Code snippet is required' },
|
|
62
|
+
{ status: 400 }
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
67
|
+
return NextResponse.json(
|
|
68
|
+
{ error: 'ANTHROPIC_API_KEY environment variable is not set' },
|
|
69
|
+
{ status: 500 }
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const message = await anthropic.messages.create({
|
|
74
|
+
model: 'claude-sonnet-4-20250514',
|
|
75
|
+
max_tokens: 4096,
|
|
76
|
+
messages: [
|
|
77
|
+
{
|
|
78
|
+
role: 'user',
|
|
79
|
+
content: `${WCAG_PROMPT}\n\nCode to review:\n\`\`\`jsx\n${code}\n\`\`\``,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const content = message.content[0]
|
|
85
|
+
if (content.type !== 'text') {
|
|
86
|
+
throw new Error('Unexpected response type from Anthropic')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Parse the JSON response
|
|
90
|
+
let auditResult: AuditResponse
|
|
91
|
+
try {
|
|
92
|
+
// Extract JSON from the response (handle markdown code blocks if present)
|
|
93
|
+
let jsonText = content.text.trim()
|
|
94
|
+
if (jsonText.startsWith('```')) {
|
|
95
|
+
jsonText = jsonText.replace(/^```(?:json)?\n/, '').replace(/\n```$/, '')
|
|
96
|
+
}
|
|
97
|
+
auditResult = JSON.parse(jsonText)
|
|
98
|
+
} catch (parseError) {
|
|
99
|
+
// If parsing fails, try to extract JSON from the text
|
|
100
|
+
const jsonMatch = content.text.match(/\{[\s\S]*\}/)
|
|
101
|
+
if (jsonMatch) {
|
|
102
|
+
auditResult = JSON.parse(jsonMatch[0])
|
|
103
|
+
} else {
|
|
104
|
+
throw new Error('Failed to parse audit response as JSON')
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Validate response structure
|
|
109
|
+
if (!auditResult.issues || !Array.isArray(auditResult.issues)) {
|
|
110
|
+
auditResult = {
|
|
111
|
+
issues: [],
|
|
112
|
+
summary: 'No issues found or invalid response format',
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return NextResponse.json(auditResult)
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error('Audit API error:', error)
|
|
119
|
+
return NextResponse.json(
|
|
120
|
+
{
|
|
121
|
+
error: 'Failed to perform accessibility audit',
|
|
122
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
123
|
+
},
|
|
124
|
+
{ status: 500 }
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import './styles/globals.css'
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = {
|
|
5
|
+
title: 'Accessible Design System',
|
|
6
|
+
description: 'An accessible design system with AI-powered accessibility auditing',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function RootLayout({
|
|
10
|
+
children,
|
|
11
|
+
}: {
|
|
12
|
+
children: React.ReactNode
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<body>{children}</body>
|
|
17
|
+
</html>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Link from 'next/link'
|
|
2
|
+
|
|
3
|
+
export default function Home() {
|
|
4
|
+
return (
|
|
5
|
+
<main>
|
|
6
|
+
<nav>
|
|
7
|
+
<ul>
|
|
8
|
+
<li><Link href="/components">Components</Link></li>
|
|
9
|
+
<li><Link href="/audit">AI Audit Assistant</Link></li>
|
|
10
|
+
</ul>
|
|
11
|
+
</nav>
|
|
12
|
+
<h1>Accessible Design System</h1>
|
|
13
|
+
<p>Welcome to the accessible design system documentation.</p>
|
|
14
|
+
</main>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** @type {import('next').NextConfig} */
|
|
2
|
+
const nextConfig = {
|
|
3
|
+
transpilePackages: ['@a11ypros/a11y-ui-components'],
|
|
4
|
+
// Enable static export for Netlify
|
|
5
|
+
output: 'export',
|
|
6
|
+
// Disable image optimization for static export
|
|
7
|
+
images: {
|
|
8
|
+
unoptimized: true,
|
|
9
|
+
},
|
|
10
|
+
// Increase static page generation timeout (default is 60s)
|
|
11
|
+
staticPageGenerationTimeout: 120,
|
|
12
|
+
webpack: (config) => {
|
|
13
|
+
config.resolve.alias = {
|
|
14
|
+
...config.resolve.alias,
|
|
15
|
+
}
|
|
16
|
+
return config
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = nextConfig
|
|
21
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@apps/web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "next lint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@anthropic-ai/sdk": "^0.20.0",
|
|
13
|
+
"@a11ypros/a11y-ui-components": "*",
|
|
14
|
+
"next": "^14.1.0",
|
|
15
|
+
"react": "^18.2.0",
|
|
16
|
+
"react-dom": "^18.2.0",
|
|
17
|
+
"react-syntax-highlighter": "^15.5.0",
|
|
18
|
+
"prismjs": "^1.29.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^20.11.5",
|
|
22
|
+
"@types/react": "^18.2.48",
|
|
23
|
+
"@types/react-dom": "^18.2.18",
|
|
24
|
+
"@types/react-syntax-highlighter": "^15.5.11",
|
|
25
|
+
"typescript": "^5.3.3"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Headers for Storybook - VERY permissive CSP to allow eval
|
|
2
|
+
# IMPORTANT: Order matters - more specific paths must come first
|
|
3
|
+
# The _headers file is processed AFTER netlify.toml, so this will override any CSP
|
|
4
|
+
f# This CSP works for both local development and production
|
|
5
|
+
|
|
6
|
+
# Storybook root - must come first to match /storybook before catch-all
|
|
7
|
+
# 'unsafe-eval' must be in script-src (not default-src) for Storybook to work
|
|
8
|
+
/storybook
|
|
9
|
+
Content-Security-Policy: default-src 'self' 'unsafe-inline' data: blob: http: https: ws: wss:; script-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: http: https:; style-src 'self' 'unsafe-inline' data: http: https:; img-src 'self' data: blob: http: https:; font-src 'self' data: http: https:; connect-src 'self' ws: wss: blob: http: https:; worker-src 'self' blob: http: https:; frame-src 'self' http: https:; frame-ancestors 'self';
|
|
10
|
+
|
|
11
|
+
# Storybook sub-paths
|
|
12
|
+
/storybook/*
|
|
13
|
+
Content-Security-Policy: default-src 'self' 'unsafe-inline' data: blob: http: https: ws: wss:; script-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: http: https:; style-src 'self' 'unsafe-inline' data: http: https:; img-src 'self' data: blob: http: https:; font-src 'self' data: http: https:; connect-src 'self' ws: wss: blob: http: https:; worker-src 'self' blob: http: https:; frame-src 'self' http: https:; frame-ancestors 'self';
|
|
14
|
+
|
|
15
|
+
# Storybook static files
|
|
16
|
+
/storybook-static/*
|
|
17
|
+
Content-Security-Policy: default-src 'self' 'unsafe-inline' data: blob: http: https: ws: wss:; script-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: http: https:; style-src 'self' 'unsafe-inline' data: http: https:; img-src 'self' data: blob: http: https:; font-src 'self' data: http: https:; connect-src 'self' ws: wss: blob: http: https:; worker-src 'self' blob: http: https:; frame-src 'self' http: https:; frame-ancestors 'self';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Storybook redirects - must be in public/ to be included in build
|
|
2
|
+
# IMPORTANT: Order matters! More specific rules must come first
|
|
3
|
+
|
|
4
|
+
# Storybook JSON files (must come before catch-all)
|
|
5
|
+
# Storybook may request these from root
|
|
6
|
+
/index.json /storybook-static/index.json 200
|
|
7
|
+
/project.json /storybook-static/project.json 200
|
|
8
|
+
|
|
9
|
+
# Storybook iframe.html - must come before other Storybook rules
|
|
10
|
+
# Storybook loads components in an iframe with query parameters
|
|
11
|
+
/iframe.html /storybook-static/iframe.html 200
|
|
12
|
+
/storybook/iframe.html /storybook-static/iframe.html 200
|
|
13
|
+
|
|
14
|
+
# Storybook root path
|
|
15
|
+
/storybook /storybook-static/index.html 200
|
|
16
|
+
|
|
17
|
+
# Storybook sub-paths (JS files, assets, etc.)
|
|
18
|
+
# This catches /storybook/sb-manager/runtime.js → /storybook-static/sb-manager/runtime.js
|
|
19
|
+
/storybook/* /storybook-static/:splat 200
|
|
20
|
+
|
|
21
|
+
# Netlify Function for audit API
|
|
22
|
+
/api/audit /.netlify/functions/audit 200
|
|
23
|
+
|
|
24
|
+
# Storybook static files (ensures they're served correctly)
|
|
25
|
+
/storybook-static/* /storybook-static/:splat 200
|
|
26
|
+
|
|
27
|
+
# Next.js SPA fallback (must be last)
|
|
28
|
+
# Note: This will catch everything else, including Storybook query params
|
|
29
|
+
# But Storybook iframe.html should be handled above
|
|
30
|
+
/* /index.html 200
|
|
31
|
+
|
|
Binary file
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2017",
|
|
5
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
6
|
+
"allowJs": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"module": "esnext",
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"jsx": "preserve",
|
|
16
|
+
"incremental": true,
|
|
17
|
+
"plugins": [
|
|
18
|
+
{
|
|
19
|
+
"name": "next"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"paths": {
|
|
23
|
+
"@a11ypros/a11y-ui-components": ["../../packages/design-system/src"]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
27
|
+
"exclude": ["node_modules"]
|
|
28
|
+
}
|
|
29
|
+
|