@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.
Files changed (217) hide show
  1. package/.storybook/custom.css +69 -0
  2. package/.storybook/main.ts +46 -0
  3. package/.storybook/manager.ts +26 -0
  4. package/.storybook/package.json +6 -0
  5. package/.storybook/preview.tsx +31 -0
  6. package/.storybook/public/logo.png +0 -0
  7. package/.storybook/vite.config.ts +24 -0
  8. package/.storybook/welcome.mdx +97 -0
  9. package/DEPLOYMENT.md +154 -0
  10. package/README.md +227 -0
  11. package/apps/web/app/(docs)/audit/audit.css +269 -0
  12. package/apps/web/app/(docs)/audit/page.tsx +271 -0
  13. package/apps/web/app/(docs)/components/button/page.tsx +49 -0
  14. package/apps/web/app/(docs)/components/form/page.tsx +92 -0
  15. package/apps/web/app/(docs)/components/link/page.tsx +31 -0
  16. package/apps/web/app/(docs)/components/modal/page.tsx +41 -0
  17. package/apps/web/app/(docs)/components/page.tsx +37 -0
  18. package/apps/web/app/(docs)/components/table/page.tsx +54 -0
  19. package/apps/web/app/(docs)/components/tabs/page.tsx +61 -0
  20. package/apps/web/app/(docs)/components/toast/page.tsx +51 -0
  21. package/apps/web/app/api/audit/route.ts +128 -0
  22. package/apps/web/app/favicon.ico +0 -0
  23. package/apps/web/app/layout.tsx +20 -0
  24. package/apps/web/app/page.tsx +17 -0
  25. package/apps/web/app/styles/globals.css +5 -0
  26. package/apps/web/next-env.d.ts +5 -0
  27. package/apps/web/next.config.js +21 -0
  28. package/apps/web/package.json +28 -0
  29. package/apps/web/public/_headers +17 -0
  30. package/apps/web/public/_redirects +31 -0
  31. package/apps/web/public/logo.png +0 -0
  32. package/apps/web/tsconfig.json +29 -0
  33. package/netlify/functions/audit.ts +163 -0
  34. package/netlify.toml +37 -0
  35. package/package.json +30 -58
  36. package/packages/design-system/README.md +252 -0
  37. package/packages/design-system/package.json +68 -0
  38. package/packages/design-system/scripts/copy-css.js +63 -0
  39. package/packages/design-system/src/components/Button/Button.stories.tsx +228 -0
  40. package/packages/design-system/src/components/Button/Button.tsx +137 -0
  41. package/packages/design-system/src/components/Button/index.ts +3 -0
  42. package/packages/design-system/src/components/DataTable/DataTable.stories.tsx +211 -0
  43. package/packages/design-system/src/components/DataTable/DataTable.tsx +293 -0
  44. package/packages/design-system/src/components/DataTable/index.ts +3 -0
  45. package/packages/design-system/src/components/Form/Checkbox.stories.tsx +252 -0
  46. package/packages/design-system/src/components/Form/Checkbox.tsx +114 -0
  47. package/packages/design-system/src/components/Form/Fieldset.stories.tsx +210 -0
  48. package/packages/design-system/src/components/Form/Fieldset.tsx +71 -0
  49. package/packages/design-system/src/components/Form/Input.stories.tsx +164 -0
  50. package/packages/design-system/src/components/Form/Input.tsx +113 -0
  51. package/packages/design-system/src/components/Form/Label.tsx +56 -0
  52. package/packages/design-system/src/components/Form/Radio.stories.tsx +265 -0
  53. package/packages/design-system/src/components/Form/Radio.tsx +147 -0
  54. package/packages/design-system/src/components/Form/Select.stories.tsx +295 -0
  55. package/packages/design-system/src/components/Form/Select.tsx +160 -0
  56. package/packages/design-system/src/components/Form/Textarea.stories.tsx +253 -0
  57. package/packages/design-system/src/components/Form/Textarea.tsx +145 -0
  58. package/packages/design-system/src/components/Form/index.ts +8 -0
  59. package/packages/design-system/src/components/Link/Link.stories.tsx +128 -0
  60. package/packages/design-system/src/components/Link/Link.tsx +117 -0
  61. package/packages/design-system/src/components/Link/index.ts +3 -0
  62. package/packages/design-system/src/components/Modal/Modal.stories.tsx +165 -0
  63. package/packages/design-system/src/components/Modal/Modal.tsx +202 -0
  64. package/packages/design-system/src/components/Modal/index.ts +3 -0
  65. package/packages/design-system/src/components/Tabs/Tabs.stories.tsx +213 -0
  66. package/packages/design-system/src/components/Tabs/Tabs.tsx +248 -0
  67. package/packages/design-system/src/components/Tabs/index.ts +3 -0
  68. package/packages/design-system/src/components/Toast/Toast.stories.tsx +153 -0
  69. package/packages/design-system/src/components/Toast/Toast.tsx +175 -0
  70. package/packages/design-system/src/components/Toast/ToastProvider.tsx +73 -0
  71. package/packages/design-system/src/components/Toast/index.ts +5 -0
  72. package/packages/design-system/src/hooks/useAriaLive.ts +51 -0
  73. package/packages/design-system/src/hooks/useFocusReturn.ts +40 -0
  74. package/packages/design-system/src/hooks/useFocusTrap.ts +82 -0
  75. package/{dist/index.js → packages/design-system/src/index.ts} +4 -0
  76. package/packages/design-system/src/styles/index.ts +3 -0
  77. package/packages/design-system/src/tokens/breakpoints.ts +28 -0
  78. package/packages/design-system/src/tokens/colors.ts +98 -0
  79. package/packages/design-system/src/tokens/index.ts +6 -0
  80. package/packages/design-system/src/tokens/motion.ts +41 -0
  81. package/packages/design-system/src/tokens/spacing.ts +24 -0
  82. package/packages/design-system/src/tokens/theme.ts +19 -0
  83. package/packages/design-system/src/tokens/typography.ts +64 -0
  84. package/packages/design-system/src/utils/aria.ts +108 -0
  85. package/packages/design-system/src/utils/focus.ts +87 -0
  86. package/packages/design-system/src/utils/index.ts +4 -0
  87. package/packages/design-system/src/utils/keyboard.ts +77 -0
  88. package/packages/design-system/tsconfig.json +17 -0
  89. package/public/logo.png +0 -0
  90. package/scripts/fix-storybook-paths.js +53 -0
  91. package/tsconfig.json +20 -0
  92. package/dist/components/Button/Button.d.ts +0 -37
  93. package/dist/components/Button/Button.d.ts.map +0 -1
  94. package/dist/components/Button/Button.js +0 -52
  95. package/dist/components/Button/index.d.ts +0 -3
  96. package/dist/components/Button/index.d.ts.map +0 -1
  97. package/dist/components/Button/index.js +0 -1
  98. package/dist/components/DataTable/DataTable.d.ts +0 -71
  99. package/dist/components/DataTable/DataTable.d.ts.map +0 -1
  100. package/dist/components/DataTable/DataTable.js +0 -122
  101. package/dist/components/DataTable/index.d.ts +0 -3
  102. package/dist/components/DataTable/index.d.ts.map +0 -1
  103. package/dist/components/DataTable/index.js +0 -1
  104. package/dist/components/Form/Checkbox.d.ts +0 -36
  105. package/dist/components/Form/Checkbox.d.ts.map +0 -1
  106. package/dist/components/Form/Checkbox.js +0 -39
  107. package/dist/components/Form/Fieldset.d.ts +0 -33
  108. package/dist/components/Form/Fieldset.d.ts.map +0 -1
  109. package/dist/components/Form/Fieldset.js +0 -34
  110. package/dist/components/Form/Input.d.ts +0 -37
  111. package/dist/components/Form/Input.d.ts.map +0 -1
  112. package/dist/components/Form/Input.js +0 -41
  113. package/dist/components/Form/Label.d.ts +0 -30
  114. package/dist/components/Form/Label.d.ts.map +0 -1
  115. package/dist/components/Form/Label.js +0 -30
  116. package/dist/components/Form/Radio.d.ts +0 -53
  117. package/dist/components/Form/Radio.d.ts.map +0 -1
  118. package/dist/components/Form/Radio.js +0 -39
  119. package/dist/components/Form/Select.d.ts +0 -51
  120. package/dist/components/Form/Select.d.ts.map +0 -1
  121. package/dist/components/Form/Select.js +0 -49
  122. package/dist/components/Form/Textarea.d.ts +0 -44
  123. package/dist/components/Form/Textarea.d.ts.map +0 -1
  124. package/dist/components/Form/Textarea.js +0 -43
  125. package/dist/components/Form/index.d.ts +0 -8
  126. package/dist/components/Form/index.d.ts.map +0 -1
  127. package/dist/components/Form/index.js +0 -7
  128. package/dist/components/Link/Link.d.ts +0 -34
  129. package/dist/components/Link/Link.d.ts.map +0 -1
  130. package/dist/components/Link/Link.js +0 -48
  131. package/dist/components/Link/index.d.ts +0 -3
  132. package/dist/components/Link/index.d.ts.map +0 -1
  133. package/dist/components/Link/index.js +0 -1
  134. package/dist/components/Modal/Modal.d.ts +0 -64
  135. package/dist/components/Modal/Modal.d.ts.map +0 -1
  136. package/dist/components/Modal/Modal.js +0 -108
  137. package/dist/components/Modal/index.d.ts +0 -3
  138. package/dist/components/Modal/index.d.ts.map +0 -1
  139. package/dist/components/Modal/index.js +0 -1
  140. package/dist/components/Tabs/Tabs.d.ts +0 -63
  141. package/dist/components/Tabs/Tabs.d.ts.map +0 -1
  142. package/dist/components/Tabs/Tabs.js +0 -134
  143. package/dist/components/Tabs/index.d.ts +0 -3
  144. package/dist/components/Tabs/index.d.ts.map +0 -1
  145. package/dist/components/Tabs/index.js +0 -1
  146. package/dist/components/Toast/Toast.d.ts +0 -59
  147. package/dist/components/Toast/Toast.d.ts.map +0 -1
  148. package/dist/components/Toast/Toast.js +0 -91
  149. package/dist/components/Toast/ToastProvider.d.ts +0 -22
  150. package/dist/components/Toast/ToastProvider.d.ts.map +0 -1
  151. package/dist/components/Toast/ToastProvider.js +0 -33
  152. package/dist/components/Toast/index.d.ts +0 -5
  153. package/dist/components/Toast/index.d.ts.map +0 -1
  154. package/dist/components/Toast/index.js +0 -2
  155. package/dist/hooks/useAriaLive.d.ts +0 -9
  156. package/dist/hooks/useAriaLive.d.ts.map +0 -1
  157. package/dist/hooks/useAriaLive.js +0 -39
  158. package/dist/hooks/useFocusReturn.d.ts +0 -9
  159. package/dist/hooks/useFocusReturn.d.ts.map +0 -1
  160. package/dist/hooks/useFocusReturn.js +0 -33
  161. package/dist/hooks/useFocusTrap.d.ts +0 -9
  162. package/dist/hooks/useFocusTrap.d.ts.map +0 -1
  163. package/dist/hooks/useFocusTrap.js +0 -68
  164. package/dist/index.d.ts +0 -22
  165. package/dist/index.d.ts.map +0 -1
  166. package/dist/styles/index.d.ts +0 -3
  167. package/dist/styles/index.d.ts.map +0 -1
  168. package/dist/styles/index.js +0 -1
  169. package/dist/tokens/breakpoints.d.ts +0 -25
  170. package/dist/tokens/breakpoints.d.ts.map +0 -1
  171. package/dist/tokens/breakpoints.js +0 -23
  172. package/dist/tokens/colors.d.ts +0 -81
  173. package/dist/tokens/colors.d.ts.map +0 -1
  174. package/dist/tokens/colors.js +0 -86
  175. package/dist/tokens/index.d.ts +0 -6
  176. package/dist/tokens/index.d.ts.map +0 -1
  177. package/dist/tokens/index.js +0 -5
  178. package/dist/tokens/motion.d.ts +0 -30
  179. package/dist/tokens/motion.d.ts.map +0 -1
  180. package/dist/tokens/motion.js +0 -34
  181. package/dist/tokens/spacing.d.ts +0 -22
  182. package/dist/tokens/spacing.d.ts.map +0 -1
  183. package/dist/tokens/spacing.js +0 -20
  184. package/dist/tokens/theme.d.ts +0 -159
  185. package/dist/tokens/theme.d.ts.map +0 -1
  186. package/dist/tokens/theme.js +0 -15
  187. package/dist/tokens/typography.d.ts +0 -45
  188. package/dist/tokens/typography.d.ts.map +0 -1
  189. package/dist/tokens/typography.js +0 -56
  190. package/dist/utils/aria.d.ts +0 -60
  191. package/dist/utils/aria.d.ts.map +0 -1
  192. package/dist/utils/aria.js +0 -86
  193. package/dist/utils/focus.d.ts +0 -30
  194. package/dist/utils/focus.d.ts.map +0 -1
  195. package/dist/utils/focus.js +0 -80
  196. package/dist/utils/index.d.ts +0 -4
  197. package/dist/utils/index.d.ts.map +0 -1
  198. package/dist/utils/index.js +0 -3
  199. package/dist/utils/keyboard.d.ts +0 -38
  200. package/dist/utils/keyboard.d.ts.map +0 -1
  201. package/dist/utils/keyboard.js +0 -59
  202. /package/{dist → packages/design-system/src}/components/Button/Button.css +0 -0
  203. /package/{dist → packages/design-system/src}/components/DataTable/DataTable.css +0 -0
  204. /package/{dist → packages/design-system/src}/components/Form/Checkbox.css +0 -0
  205. /package/{dist → packages/design-system/src}/components/Form/Fieldset.css +0 -0
  206. /package/{dist → packages/design-system/src}/components/Form/Input.css +0 -0
  207. /package/{dist → packages/design-system/src}/components/Form/Label.css +0 -0
  208. /package/{dist → packages/design-system/src}/components/Form/Radio.css +0 -0
  209. /package/{dist → packages/design-system/src}/components/Form/Select.css +0 -0
  210. /package/{dist → packages/design-system/src}/components/Form/Textarea.css +0 -0
  211. /package/{dist → packages/design-system/src}/components/Link/Link.css +0 -0
  212. /package/{dist → packages/design-system/src}/components/Modal/Modal.css +0 -0
  213. /package/{dist → packages/design-system/src}/components/Tabs/Tabs.css +0 -0
  214. /package/{dist → packages/design-system/src}/components/Toast/Toast.css +0 -0
  215. /package/{dist → packages/design-system/src}/components/Toast/ToastProvider.css +0 -0
  216. /package/{dist → packages/design-system/src}/styles/components.css +0 -0
  217. /package/{dist → packages/design-system/src}/styles/global.css +0 -0
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Keyboard event utilities
3
+ * Helpers for handling keyboard interactions
4
+ */
5
+
6
+ export type KeyboardHandler = (event: React.KeyboardEvent) => void
7
+
8
+ /**
9
+ * Check if a key is an activation key (Enter or Space)
10
+ */
11
+ export function isActivationKey(key: string): boolean {
12
+ return key === 'Enter' || key === ' '
13
+ }
14
+
15
+ /**
16
+ * Check if a key is an arrow key
17
+ */
18
+ export function isArrowKey(key: string): boolean {
19
+ return ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key)
20
+ }
21
+
22
+ /**
23
+ * Check if a key is a navigation key (Home, End, PageUp, PageDown)
24
+ */
25
+ export function isNavigationKey(key: string): boolean {
26
+ return ['Home', 'End', 'PageUp', 'PageDown'].includes(key)
27
+ }
28
+
29
+ /**
30
+ * Check if a key is an escape key
31
+ */
32
+ export function isEscapeKey(key: string): boolean {
33
+ return key === 'Escape'
34
+ }
35
+
36
+ /**
37
+ * Check if modifier keys are pressed
38
+ */
39
+ export function hasModifierKey(event: React.KeyboardEvent): boolean {
40
+ return event.ctrlKey || event.metaKey || event.altKey || event.shiftKey
41
+ }
42
+
43
+ /**
44
+ * Create a keyboard handler that only fires on specific keys
45
+ */
46
+ export function createKeyHandler(
47
+ keys: string[],
48
+ handler: KeyboardHandler
49
+ ): KeyboardHandler {
50
+ return (event: React.KeyboardEvent) => {
51
+ if (keys.includes(event.key)) {
52
+ handler(event)
53
+ }
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Create a keyboard handler for activation keys (Enter/Space)
59
+ */
60
+ export function createActivationHandler(
61
+ handler: KeyboardHandler
62
+ ): KeyboardHandler {
63
+ return createKeyHandler(['Enter', ' '], (event) => {
64
+ event.preventDefault()
65
+ handler(event)
66
+ })
67
+ }
68
+
69
+ /**
70
+ * Create a keyboard handler for arrow keys
71
+ */
72
+ export function createArrowKeyHandler(
73
+ handler: KeyboardHandler
74
+ ): KeyboardHandler {
75
+ return createKeyHandler(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'], handler)
76
+ }
77
+
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "module": "ESNext",
7
+ "target": "ES2020",
8
+ "moduleResolution": "bundler",
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "jsx": "react-jsx",
12
+ "noEmit": false
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist", "**/*.stories.tsx", "**/*.test.ts", "**/*.test.tsx"]
16
+ }
17
+
Binary file
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+
6
+ const storybookDir = path.join(__dirname, '../apps/web/public/storybook-static')
7
+ const indexHtmlPath = path.join(storybookDir, 'index.html')
8
+ const iframeHtmlPath = path.join(storybookDir, 'iframe.html')
9
+
10
+ function fixHtmlPaths(html) {
11
+ // Replace relative paths with absolute paths
12
+ // ./sb-manager/runtime.js -> /storybook-static/sb-manager/runtime.js
13
+ html = html.replace(/href="\.\//g, 'href="/storybook-static/')
14
+ html = html.replace(/src="\.\//g, 'src="/storybook-static/')
15
+ html = html.replace(/import '\.\//g, "import '/storybook-static/")
16
+ html = html.replace(/import "\.\//g, 'import "/storybook-static/')
17
+ html = html.replace(/url\('\.\//g, "url('/storybook-static/")
18
+ html = html.replace(/url\("\.\//g, 'url("/storybook-static/')
19
+
20
+ // Fix JSON file references (Storybook loads index.json, project.json, etc.)
21
+ html = html.replace(/(["'])(\.\/)?([^"']+\.json)(["'])/g, '$1/storybook-static/$3$4')
22
+
23
+ // Fix logo path if it's using /logo.png
24
+ html = html.replace(/\/logo\.png/g, '/storybook-static/logo.png')
25
+
26
+ // Remove ALL CSP meta tags - we'll handle CSP via HTTP headers only
27
+ // This prevents any conflicts between meta tags and HTTP headers
28
+ html = html.replace(/<meta[^>]*http-equiv=["']Content-Security-Policy["'][^>]*>/gi, '')
29
+
30
+ return html
31
+ }
32
+
33
+ // Fix index.html
34
+ if (!fs.existsSync(indexHtmlPath)) {
35
+ console.error('Storybook index.html not found at:', indexHtmlPath)
36
+ process.exit(1)
37
+ }
38
+
39
+ let indexHtml = fs.readFileSync(indexHtmlPath, 'utf8')
40
+ indexHtml = fixHtmlPaths(indexHtml)
41
+ fs.writeFileSync(indexHtmlPath, indexHtml, 'utf8')
42
+ console.log('✅ Fixed Storybook paths in index.html')
43
+
44
+ // Fix iframe.html (where components actually render)
45
+ if (fs.existsSync(iframeHtmlPath)) {
46
+ let iframeHtml = fs.readFileSync(iframeHtmlPath, 'utf8')
47
+ iframeHtml = fixHtmlPaths(iframeHtml)
48
+ fs.writeFileSync(iframeHtmlPath, iframeHtml, 'utf8')
49
+ console.log('✅ Fixed Storybook paths in iframe.html')
50
+ } else {
51
+ console.warn('⚠️ iframe.html not found - components may not load correctly')
52
+ }
53
+
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
5
+ "jsx": "react-jsx",
6
+ "module": "ESNext",
7
+ "moduleResolution": "bundler",
8
+ "resolveJsonModule": true,
9
+ "allowJs": true,
10
+ "strict": true,
11
+ "noEmit": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "isolatedModules": true,
16
+ "incremental": true
17
+ },
18
+ "exclude": ["node_modules", "dist", ".next", "storybook-static"]
19
+ }
20
+
@@ -1,37 +0,0 @@
1
- import React from 'react';
2
- import './Button.css';
3
- export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
4
- /**
5
- * Visual variant of the button
6
- */
7
- variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
8
- /**
9
- * Size of the button
10
- */
11
- size?: 'sm' | 'md' | 'lg';
12
- /**
13
- * Whether the button is in a loading state
14
- */
15
- loading?: boolean;
16
- /**
17
- * ARIA label for the button (required if no visible text)
18
- */
19
- 'aria-label'?: string;
20
- }
21
- /**
22
- * Accessible Button component
23
- *
24
- * WCAG Compliance:
25
- * - 2.1.1 Keyboard: Full keyboard support (Enter/Space)
26
- * - 2.4.7 Focus Visible: Clear focus indicators
27
- * - 4.1.2 Name, Role, Value: Proper ARIA attributes
28
- *
29
- * @example
30
- * ```tsx
31
- * <Button variant="primary" onClick={handleClick}>
32
- * Click me
33
- * </Button>
34
- * ```
35
- */
36
- export declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
37
- //# sourceMappingURL=Button.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../../src/components/Button/Button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,cAAc,CAAA;AAErB,MAAM,WAAW,WAAY,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IAChF;;OAEG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAA;IAEtD;;OAEG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IAEzB;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,MAAM,uFA2FlB,CAAA"}
@@ -1,52 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from 'react';
3
- import { createActivationHandler } from '../../utils/keyboard';
4
- import { getAriaLabel, getBusyAttributes } from '../../utils/aria';
5
- import './Button.css';
6
- /**
7
- * Accessible Button component
8
- *
9
- * WCAG Compliance:
10
- * - 2.1.1 Keyboard: Full keyboard support (Enter/Space)
11
- * - 2.4.7 Focus Visible: Clear focus indicators
12
- * - 4.1.2 Name, Role, Value: Proper ARIA attributes
13
- *
14
- * @example
15
- * ```tsx
16
- * <Button variant="primary" onClick={handleClick}>
17
- * Click me
18
- * </Button>
19
- * ```
20
- */
21
- export const Button = React.forwardRef(({ variant = 'primary', size = 'md', loading = false, disabled, children, className = '', onClick, onKeyDown, 'aria-label': ariaLabel, ...props }, ref) => {
22
- const isDisabled = disabled || loading;
23
- const handleKeyDown = React.useCallback((event) => {
24
- // Handle activation keys
25
- const activationHandler = createActivationHandler((e) => {
26
- if (!isDisabled && onClick) {
27
- onClick(e);
28
- }
29
- });
30
- activationHandler(event);
31
- // Call user's onKeyDown if provided
32
- if (onKeyDown) {
33
- onKeyDown(event);
34
- }
35
- }, [isDisabled, onClick, onKeyDown]);
36
- const ariaProps = {
37
- ...getAriaLabel(ariaLabel),
38
- ...getBusyAttributes(loading),
39
- ...props,
40
- };
41
- const classes = [
42
- 'btn',
43
- `btn--${variant}`,
44
- `btn--${size}`,
45
- loading && 'btn--loading',
46
- className,
47
- ]
48
- .filter(Boolean)
49
- .join(' ');
50
- return (_jsxs("button", { ref: ref, type: "button", className: classes, disabled: isDisabled, onClick: onClick, onKeyDown: handleKeyDown, "aria-disabled": isDisabled, ...ariaProps, children: [loading && (_jsx("span", { className: "btn__spinner", "aria-hidden": "true", children: _jsx("svg", { className: "btn__spinner-icon", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("circle", { className: "btn__spinner-circle", cx: "8", cy: "8", r: "6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeDasharray: "31.416", strokeDashoffset: "31.416" }) }) })), _jsx("span", { className: "btn__content", children: children })] }));
51
- });
52
- Button.displayName = 'Button';
@@ -1,3 +0,0 @@
1
- export { Button } from './Button';
2
- export type { ButtonProps } from './Button';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Button/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA"}
@@ -1 +0,0 @@
1
- export { Button } from './Button';
@@ -1,71 +0,0 @@
1
- import React from 'react';
2
- import './DataTable.css';
3
- export interface DataTableColumn<T> {
4
- key: string;
5
- header: string;
6
- render?: (row: T, index: number) => React.ReactNode;
7
- sortable?: boolean;
8
- width?: string;
9
- }
10
- export interface DataTableProps<T> {
11
- /**
12
- * Data rows
13
- */
14
- data: T[];
15
- /**
16
- * Column definitions
17
- */
18
- columns: DataTableColumn<T>[];
19
- /**
20
- * Key function to get unique ID for each row
21
- */
22
- getRowId: (row: T) => string;
23
- /**
24
- * Whether rows are selectable
25
- */
26
- selectable?: boolean;
27
- /**
28
- * Selected row IDs
29
- */
30
- selectedRows?: string[];
31
- /**
32
- * Callback when selection changes
33
- */
34
- onSelectionChange?: (selectedIds: string[]) => void;
35
- /**
36
- * Sort configuration
37
- */
38
- sortConfig?: {
39
- column: string;
40
- direction: 'asc' | 'desc';
41
- };
42
- /**
43
- * Callback when sort changes
44
- */
45
- onSortChange?: (column: string, direction: 'asc' | 'desc') => void;
46
- /**
47
- * Caption for the table (required for accessibility)
48
- */
49
- caption?: string;
50
- }
51
- /**
52
- * Accessible DataTable component
53
- *
54
- * WCAG Compliance:
55
- * - 1.3.1 Info and Relationships: Semantic table structure
56
- * - 2.1.1 Keyboard: Arrow keys, Home/End navigation
57
- * - 4.1.2 Name, Role, Value: Proper ARIA attributes
58
- * - 4.1.3 Status Messages: Sort announcements
59
- *
60
- * @example
61
- * ```tsx
62
- * <DataTable
63
- * data={users}
64
- * columns={columns}
65
- * getRowId={(user) => user.id}
66
- * caption="User list"
67
- * />
68
- * ```
69
- */
70
- export declare function DataTable<T extends Record<string, any>>({ data, columns, getRowId, selectable, selectedRows, onSelectionChange, sortConfig, onSortChange, caption, }: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
71
- //# sourceMappingURL=DataTable.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/DataTable.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAwC,MAAM,OAAO,CAAA;AAI5D,OAAO,iBAAiB,CAAA;AAExB,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B;;OAEG;IACH,IAAI,EAAE,CAAC,EAAE,CAAA;IAET;;OAEG;IACH,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,CAAA;IAE7B;;OAEG;IACH,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAA;IAE5B;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IAEpB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IAEvB;;OAEG;IACH,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IAEnD;;OAEG;IACH,UAAU,CAAC,EAAE;QACX,MAAM,EAAE,MAAM,CAAA;QACd,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;KAC1B,CAAA;IAED;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,GAAG,MAAM,KAAK,IAAI,CAAA;IAElE;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACvD,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,UAAkB,EAClB,YAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,OAAO,GACR,EAAE,cAAc,CAAC,CAAC,CAAC,2CAoMnB"}
@@ -1,122 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useRef, useCallback } from 'react';
4
- import { useAriaLive } from '../../hooks/useAriaLive';
5
- import { isNavigationKey } from '../../utils/keyboard';
6
- import { Checkbox } from '../Form/Checkbox';
7
- import './DataTable.css';
8
- /**
9
- * Accessible DataTable component
10
- *
11
- * WCAG Compliance:
12
- * - 1.3.1 Info and Relationships: Semantic table structure
13
- * - 2.1.1 Keyboard: Arrow keys, Home/End navigation
14
- * - 4.1.2 Name, Role, Value: Proper ARIA attributes
15
- * - 4.1.3 Status Messages: Sort announcements
16
- *
17
- * @example
18
- * ```tsx
19
- * <DataTable
20
- * data={users}
21
- * columns={columns}
22
- * getRowId={(user) => user.id}
23
- * caption="User list"
24
- * />
25
- * ```
26
- */
27
- export function DataTable({ data, columns, getRowId, selectable = false, selectedRows = [], onSelectionChange, sortConfig, onSortChange, caption, }) {
28
- const [focusedRow, setFocusedRow] = useState(null);
29
- const tableRef = useRef(null);
30
- const rowRefs = useRef(new Map());
31
- // Announce sort changes
32
- const sortAnnouncement = sortConfig
33
- ? `Sorted by ${columns.find((c) => c.key === sortConfig.column)?.header || sortConfig.column}, ${sortConfig.direction === 'asc' ? 'ascending' : 'descending'}`
34
- : undefined;
35
- useAriaLive(sortAnnouncement, 'polite');
36
- const handleSelectAll = useCallback(() => {
37
- if (!onSelectionChange)
38
- return;
39
- const allSelected = selectedRows.length === data.length;
40
- if (allSelected) {
41
- onSelectionChange([]);
42
- }
43
- else {
44
- onSelectionChange(data.map(getRowId));
45
- }
46
- }, [data, selectedRows, onSelectionChange, getRowId]);
47
- const handleSelectRow = useCallback((rowId) => {
48
- if (!onSelectionChange)
49
- return;
50
- const isSelected = selectedRows.includes(rowId);
51
- if (isSelected) {
52
- onSelectionChange(selectedRows.filter((id) => id !== rowId));
53
- }
54
- else {
55
- onSelectionChange([...selectedRows, rowId]);
56
- }
57
- }, [selectedRows, onSelectionChange]);
58
- const handleSort = useCallback((columnKey) => {
59
- if (!onSortChange || !columns.find((c) => c.key === columnKey)?.sortable) {
60
- return;
61
- }
62
- const newDirection = sortConfig?.column === columnKey && sortConfig.direction === 'asc'
63
- ? 'desc'
64
- : 'asc';
65
- onSortChange(columnKey, newDirection);
66
- }, [onSortChange, sortConfig, columns]);
67
- const handleKeyDown = useCallback((event, rowId, index) => {
68
- const row = rowRefs.current.get(rowId);
69
- if (!row)
70
- return;
71
- if (isNavigationKey(event.key)) {
72
- event.preventDefault();
73
- let newIndex = index;
74
- switch (event.key) {
75
- case 'ArrowDown':
76
- newIndex = Math.min(index + 1, data.length - 1);
77
- break;
78
- case 'ArrowUp':
79
- newIndex = Math.max(index - 1, 0);
80
- break;
81
- case 'Home':
82
- newIndex = 0;
83
- break;
84
- case 'End':
85
- newIndex = data.length - 1;
86
- break;
87
- }
88
- const newRowId = getRowId(data[newIndex]);
89
- setFocusedRow(newRowId);
90
- rowRefs.current.get(newRowId)?.focus();
91
- }
92
- else if (event.key === ' ' && selectable) {
93
- event.preventDefault();
94
- handleSelectRow(rowId);
95
- }
96
- }, [data, selectable, handleSelectRow, getRowId]);
97
- const allSelected = data.length > 0 && selectedRows.length === data.length;
98
- const someSelected = selectedRows.length > 0 && selectedRows.length < data.length;
99
- return (_jsx("div", { className: "data-table-wrapper", children: _jsxs("table", { ref: tableRef, className: "data-table", "aria-label": caption, children: [caption && _jsx("caption", { className: "data-table-caption", children: caption }), _jsx("thead", { children: _jsxs("tr", { children: [selectable && (_jsx("th", { scope: "col", className: "data-table-header data-table-header--checkbox", children: _jsx(Checkbox, { checked: allSelected, "aria-label": "Select all rows", onChange: handleSelectAll, "aria-checked": allSelected ? 'true' : someSelected ? 'mixed' : 'false' }) })), columns.map((column) => (_jsx("th", { scope: "col", className: `data-table-header ${column.sortable ? 'data-table-header--sortable' : ''}`, style: column.width ? { width: column.width } : undefined, "aria-sort": sortConfig?.column === column.key
100
- ? sortConfig.direction === 'asc'
101
- ? 'ascending'
102
- : 'descending'
103
- : 'none', children: column.sortable ? (_jsxs("button", { type: "button", className: "data-table-sort-button", "aria-label": sortConfig?.column === column.key
104
- ? `${column.header}, sorted ${sortConfig.direction === 'asc' ? 'ascending' : 'descending'}, activate to sort ${sortConfig.direction === 'asc' ? 'descending' : 'ascending'}`
105
- : `Sort by ${column.header}`, onClick: () => handleSort(column.key), children: [column.header, sortConfig?.column === column.key && (_jsx("span", { className: "data-table-sort-indicator", "aria-hidden": "true", children: sortConfig.direction === 'asc' ? ' ↑' : ' ↓' }))] })) : (column.header) }, column.key)))] }) }), _jsx("tbody", { children: data.map((row, index) => {
106
- const rowId = getRowId(row);
107
- const isSelected = selectedRows.includes(rowId);
108
- const isFocused = focusedRow === rowId;
109
- return (_jsxs("tr", { ref: (el) => {
110
- if (el) {
111
- rowRefs.current.set(rowId, el);
112
- }
113
- else {
114
- rowRefs.current.delete(rowId);
115
- }
116
- }, className: `data-table-row ${isSelected ? 'data-table-row--selected' : ''} ${isFocused ? 'data-table-row--focused' : ''}`, tabIndex: 0, "aria-selected": selectable ? isSelected : undefined, onKeyDown: (e) => handleKeyDown(e, rowId, index), onClick: () => {
117
- if (selectable) {
118
- handleSelectRow(rowId);
119
- }
120
- }, children: [selectable && (_jsx("td", { className: "data-table-cell data-table-cell--checkbox", children: _jsx(Checkbox, { checked: isSelected, "aria-label": `Select row ${index + 1}`, onChange: () => handleSelectRow(rowId), onClick: (e) => e.stopPropagation() }) })), columns.map((column) => (_jsx("td", { className: "data-table-cell", children: column.render ? column.render(row, index) : row[column.key] }, column.key)))] }, rowId));
121
- }) })] }) }));
122
- }
@@ -1,3 +0,0 @@
1
- export { DataTable } from './DataTable';
2
- export type { DataTableProps, DataTableColumn } from './DataTable';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA"}
@@ -1 +0,0 @@
1
- export { DataTable } from './DataTable';
@@ -1,36 +0,0 @@
1
- import React from 'react';
2
- import './Checkbox.css';
3
- export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
4
- /**
5
- * Label for the checkbox
6
- */
7
- label?: string;
8
- /**
9
- * Error message to display
10
- */
11
- error?: string;
12
- /**
13
- * Helper text to display
14
- */
15
- helperText?: string;
16
- }
17
- /**
18
- * Accessible Checkbox component
19
- *
20
- * WCAG Compliance:
21
- * - 1.3.1 Info and Relationships: Proper label-input association
22
- * - 2.5.3 Label in Name: Label text matches accessible name
23
- * - 4.1.2 Name, Role, Value: Proper ARIA attributes
24
- *
25
- * @example
26
- * ```tsx
27
- * <Checkbox
28
- * id="agree"
29
- * label="I agree to the terms"
30
- * checked={checked}
31
- * onChange={handleChange}
32
- * />
33
- * ```
34
- */
35
- export declare const Checkbox: React.ForwardRefExoticComponent<CheckboxProps & React.RefAttributes<HTMLInputElement>>;
36
- //# sourceMappingURL=Checkbox.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/Form/Checkbox.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,gBAAgB,CAAA;AAEvB,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC9F;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,QAAQ,wFAqEpB,CAAA"}
@@ -1,39 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import React from 'react';
4
- import { combineAriaDescribedBy } from '../../utils/aria';
5
- import './Checkbox.css';
6
- /**
7
- * Accessible Checkbox component
8
- *
9
- * WCAG Compliance:
10
- * - 1.3.1 Info and Relationships: Proper label-input association
11
- * - 2.5.3 Label in Name: Label text matches accessible name
12
- * - 4.1.2 Name, Role, Value: Proper ARIA attributes
13
- *
14
- * @example
15
- * ```tsx
16
- * <Checkbox
17
- * id="agree"
18
- * label="I agree to the terms"
19
- * checked={checked}
20
- * onChange={handleChange}
21
- * />
22
- * ```
23
- */
24
- export const Checkbox = React.forwardRef(({ id, label, error, helperText, className = '', 'aria-describedby': ariaDescribedBy, ...props }, ref) => {
25
- const checkboxId = React.useId();
26
- const finalId = id || `checkbox-${checkboxId}`;
27
- const errorId = error ? `${finalId}-error` : undefined;
28
- const helperId = helperText ? `${finalId}-helper` : undefined;
29
- const describedBy = combineAriaDescribedBy(ariaDescribedBy, errorId, helperId);
30
- const classes = [
31
- 'form-checkbox',
32
- error && 'form-checkbox--error',
33
- className,
34
- ]
35
- .filter(Boolean)
36
- .join(' ');
37
- return (_jsxs("div", { className: "form-checkbox-wrapper", children: [_jsxs("div", { className: "form-checkbox-input-wrapper", children: [_jsx("input", { ref: ref, id: finalId, type: "checkbox", className: classes, "aria-invalid": error ? true : undefined, "aria-describedby": describedBy, required: props.required ? true : undefined, ...props }), label && (_jsxs("label", { htmlFor: finalId, className: "form-checkbox-label", children: [label, props.required && (_jsxs("span", { className: "form-label__required", "aria-hidden": "true", children: [' ', "*"] }))] }))] }), helperText && !error && (_jsx("span", { id: helperId, className: "form-helper-text", children: helperText })), error && (_jsx("span", { id: errorId, className: "form-error-text", role: "alert", children: error }))] }));
38
- });
39
- Checkbox.displayName = 'Checkbox';
@@ -1,33 +0,0 @@
1
- import React from 'react';
2
- import './Fieldset.css';
3
- export interface FieldsetProps extends React.FieldsetHTMLAttributes<HTMLFieldSetElement> {
4
- /**
5
- * Legend text for the fieldset
6
- */
7
- legend?: string;
8
- /**
9
- * Whether the legend is visually hidden (but still accessible)
10
- */
11
- legendHidden?: boolean;
12
- /**
13
- * Whether to show a required field explanation. Use this if the fieldset contains required fields.
14
- */
15
- required?: boolean;
16
- }
17
- /**
18
- * Accessible Fieldset component
19
- *
20
- * WCAG Compliance:
21
- * - 1.3.1 Info and Relationships: Proper fieldset/legend structure
22
- * - 4.1.2 Name, Role, Value: Proper semantic HTML
23
- *
24
- * @example
25
- * ```tsx
26
- * <Fieldset legend="Shipping Address">
27
- * <Input label="Street" />
28
- * <Input label="City" />
29
- * </Fieldset>
30
- * ```
31
- */
32
- export declare const Fieldset: React.ForwardRefExoticComponent<FieldsetProps & React.RefAttributes<HTMLFieldSetElement>>;
33
- //# sourceMappingURL=Fieldset.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Fieldset.d.ts","sourceRoot":"","sources":["../../../src/components/Form/Fieldset.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,gBAAgB,CAAA;AAEvB,MAAM,WAAW,aAAc,SAAQ,KAAK,CAAC,sBAAsB,CAAC,mBAAmB,CAAC;IACtF;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IAEtB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,QAAQ,2FAgCpB,CAAA"}
@@ -1,34 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from 'react';
3
- import './Fieldset.css';
4
- /**
5
- * Accessible Fieldset component
6
- *
7
- * WCAG Compliance:
8
- * - 1.3.1 Info and Relationships: Proper fieldset/legend structure
9
- * - 4.1.2 Name, Role, Value: Proper semantic HTML
10
- *
11
- * @example
12
- * ```tsx
13
- * <Fieldset legend="Shipping Address">
14
- * <Input label="Street" />
15
- * <Input label="City" />
16
- * </Fieldset>
17
- * ```
18
- */
19
- export const Fieldset = React.forwardRef(({ legend, legendHidden = false, required = false, className = '', children, ...props }, ref) => {
20
- const classes = [
21
- 'form-fieldset',
22
- className,
23
- ]
24
- .filter(Boolean)
25
- .join(' ');
26
- const legendClasses = [
27
- 'form-legend',
28
- legendHidden && 'form-legend--hidden',
29
- ]
30
- .filter(Boolean)
31
- .join(' ');
32
- return (_jsxs("fieldset", { ref: ref, className: classes, ...props, children: [legend && (_jsx("legend", { className: legendClasses, children: legend })), required && (_jsxs("p", { className: "fieldset__required", children: [' ', _jsx("span", { className: 'fieldset__required-indicator', children: "*" }), " indicates a required field."] })), children] }));
33
- });
34
- Fieldset.displayName = 'Fieldset';