@a11ypros/a11y-ui-components 1.0.1 → 1.0.2

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/README.md +182 -157
  2. package/dist/components/Button/Button.d.ts +37 -0
  3. package/dist/components/Button/Button.d.ts.map +1 -0
  4. package/dist/components/Button/Button.js +52 -0
  5. package/dist/components/Button/index.d.ts +3 -0
  6. package/dist/components/Button/index.d.ts.map +1 -0
  7. package/dist/components/Button/index.js +1 -0
  8. package/dist/components/DataTable/DataTable.d.ts +71 -0
  9. package/dist/components/DataTable/DataTable.d.ts.map +1 -0
  10. package/dist/components/DataTable/DataTable.js +122 -0
  11. package/dist/components/DataTable/index.d.ts +3 -0
  12. package/dist/components/DataTable/index.d.ts.map +1 -0
  13. package/dist/components/DataTable/index.js +1 -0
  14. package/dist/components/Form/Checkbox.d.ts +36 -0
  15. package/dist/components/Form/Checkbox.d.ts.map +1 -0
  16. package/dist/components/Form/Checkbox.js +39 -0
  17. package/dist/components/Form/Fieldset.d.ts +33 -0
  18. package/dist/components/Form/Fieldset.d.ts.map +1 -0
  19. package/dist/components/Form/Fieldset.js +34 -0
  20. package/dist/components/Form/Input.d.ts +37 -0
  21. package/dist/components/Form/Input.d.ts.map +1 -0
  22. package/dist/components/Form/Input.js +41 -0
  23. package/dist/components/Form/Label.d.ts +30 -0
  24. package/dist/components/Form/Label.d.ts.map +1 -0
  25. package/dist/components/Form/Label.js +30 -0
  26. package/dist/components/Form/Radio.d.ts +53 -0
  27. package/dist/components/Form/Radio.d.ts.map +1 -0
  28. package/dist/components/Form/Radio.js +39 -0
  29. package/dist/components/Form/Select.d.ts +51 -0
  30. package/dist/components/Form/Select.d.ts.map +1 -0
  31. package/dist/components/Form/Select.js +49 -0
  32. package/dist/components/Form/Textarea.d.ts +44 -0
  33. package/dist/components/Form/Textarea.d.ts.map +1 -0
  34. package/dist/components/Form/Textarea.js +43 -0
  35. package/dist/components/Form/index.d.ts +8 -0
  36. package/dist/components/Form/index.d.ts.map +1 -0
  37. package/dist/components/Form/index.js +7 -0
  38. package/dist/components/Link/Link.d.ts +34 -0
  39. package/dist/components/Link/Link.d.ts.map +1 -0
  40. package/dist/components/Link/Link.js +48 -0
  41. package/dist/components/Link/index.d.ts +3 -0
  42. package/dist/components/Link/index.d.ts.map +1 -0
  43. package/dist/components/Link/index.js +1 -0
  44. package/dist/components/Modal/Modal.d.ts +64 -0
  45. package/dist/components/Modal/Modal.d.ts.map +1 -0
  46. package/dist/components/Modal/Modal.js +108 -0
  47. package/dist/components/Modal/index.d.ts +3 -0
  48. package/dist/components/Modal/index.d.ts.map +1 -0
  49. package/dist/components/Modal/index.js +1 -0
  50. package/dist/components/Tabs/Tabs.d.ts +63 -0
  51. package/dist/components/Tabs/Tabs.d.ts.map +1 -0
  52. package/dist/components/Tabs/Tabs.js +134 -0
  53. package/dist/components/Tabs/index.d.ts +3 -0
  54. package/dist/components/Tabs/index.d.ts.map +1 -0
  55. package/dist/components/Tabs/index.js +1 -0
  56. package/dist/components/Toast/Toast.d.ts +59 -0
  57. package/dist/components/Toast/Toast.d.ts.map +1 -0
  58. package/dist/components/Toast/Toast.js +91 -0
  59. package/dist/components/Toast/ToastProvider.d.ts +22 -0
  60. package/dist/components/Toast/ToastProvider.d.ts.map +1 -0
  61. package/dist/components/Toast/ToastProvider.js +33 -0
  62. package/dist/components/Toast/index.d.ts +5 -0
  63. package/dist/components/Toast/index.d.ts.map +1 -0
  64. package/dist/components/Toast/index.js +2 -0
  65. package/dist/hooks/useAriaLive.d.ts +9 -0
  66. package/dist/hooks/useAriaLive.d.ts.map +1 -0
  67. package/dist/hooks/useAriaLive.js +39 -0
  68. package/dist/hooks/useFocusReturn.d.ts +9 -0
  69. package/dist/hooks/useFocusReturn.d.ts.map +1 -0
  70. package/dist/hooks/useFocusReturn.js +33 -0
  71. package/dist/hooks/useFocusTrap.d.ts +9 -0
  72. package/dist/hooks/useFocusTrap.d.ts.map +1 -0
  73. package/dist/hooks/useFocusTrap.js +68 -0
  74. package/dist/index.d.ts +22 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/{packages/design-system/src/index.ts → dist/index.js} +0 -4
  77. package/dist/styles/index.d.ts +3 -0
  78. package/dist/styles/index.d.ts.map +1 -0
  79. package/dist/styles/index.js +1 -0
  80. package/dist/tokens/breakpoints.d.ts +25 -0
  81. package/dist/tokens/breakpoints.d.ts.map +1 -0
  82. package/dist/tokens/breakpoints.js +23 -0
  83. package/dist/tokens/colors.d.ts +81 -0
  84. package/dist/tokens/colors.d.ts.map +1 -0
  85. package/dist/tokens/colors.js +86 -0
  86. package/dist/tokens/index.d.ts +6 -0
  87. package/dist/tokens/index.d.ts.map +1 -0
  88. package/dist/tokens/index.js +5 -0
  89. package/dist/tokens/motion.d.ts +30 -0
  90. package/dist/tokens/motion.d.ts.map +1 -0
  91. package/dist/tokens/motion.js +34 -0
  92. package/dist/tokens/spacing.d.ts +22 -0
  93. package/dist/tokens/spacing.d.ts.map +1 -0
  94. package/dist/tokens/spacing.js +20 -0
  95. package/dist/tokens/theme.d.ts +159 -0
  96. package/dist/tokens/theme.d.ts.map +1 -0
  97. package/dist/tokens/theme.js +15 -0
  98. package/dist/tokens/typography.d.ts +45 -0
  99. package/dist/tokens/typography.d.ts.map +1 -0
  100. package/dist/tokens/typography.js +56 -0
  101. package/dist/utils/aria.d.ts +60 -0
  102. package/dist/utils/aria.d.ts.map +1 -0
  103. package/dist/utils/aria.js +86 -0
  104. package/dist/utils/focus.d.ts +30 -0
  105. package/dist/utils/focus.d.ts.map +1 -0
  106. package/dist/utils/focus.js +80 -0
  107. package/dist/utils/index.d.ts +4 -0
  108. package/dist/utils/index.d.ts.map +1 -0
  109. package/dist/utils/index.js +3 -0
  110. package/dist/utils/keyboard.d.ts +38 -0
  111. package/dist/utils/keyboard.d.ts.map +1 -0
  112. package/dist/utils/keyboard.js +59 -0
  113. package/package.json +58 -31
  114. package/.storybook/custom.css +0 -69
  115. package/.storybook/main.ts +0 -46
  116. package/.storybook/manager.ts +0 -26
  117. package/.storybook/package.json +0 -6
  118. package/.storybook/preview.tsx +0 -31
  119. package/.storybook/public/logo.png +0 -0
  120. package/.storybook/vite.config.ts +0 -24
  121. package/.storybook/welcome.mdx +0 -97
  122. package/DEPLOYMENT.md +0 -154
  123. package/apps/web/app/(docs)/audit/audit.css +0 -269
  124. package/apps/web/app/(docs)/audit/page.tsx +0 -271
  125. package/apps/web/app/(docs)/components/button/page.tsx +0 -49
  126. package/apps/web/app/(docs)/components/form/page.tsx +0 -92
  127. package/apps/web/app/(docs)/components/link/page.tsx +0 -31
  128. package/apps/web/app/(docs)/components/modal/page.tsx +0 -41
  129. package/apps/web/app/(docs)/components/page.tsx +0 -37
  130. package/apps/web/app/(docs)/components/table/page.tsx +0 -54
  131. package/apps/web/app/(docs)/components/tabs/page.tsx +0 -61
  132. package/apps/web/app/(docs)/components/toast/page.tsx +0 -51
  133. package/apps/web/app/api/audit/route.ts +0 -128
  134. package/apps/web/app/favicon.ico +0 -0
  135. package/apps/web/app/layout.tsx +0 -20
  136. package/apps/web/app/page.tsx +0 -17
  137. package/apps/web/app/styles/globals.css +0 -5
  138. package/apps/web/next-env.d.ts +0 -5
  139. package/apps/web/next.config.js +0 -21
  140. package/apps/web/package.json +0 -28
  141. package/apps/web/public/_headers +0 -17
  142. package/apps/web/public/_redirects +0 -31
  143. package/apps/web/public/logo.png +0 -0
  144. package/apps/web/tsconfig.json +0 -29
  145. package/netlify/functions/audit.ts +0 -163
  146. package/netlify.toml +0 -37
  147. package/packages/design-system/README.md +0 -252
  148. package/packages/design-system/package.json +0 -68
  149. package/packages/design-system/scripts/copy-css.js +0 -63
  150. package/packages/design-system/src/components/Button/Button.stories.tsx +0 -228
  151. package/packages/design-system/src/components/Button/Button.tsx +0 -137
  152. package/packages/design-system/src/components/Button/index.ts +0 -3
  153. package/packages/design-system/src/components/DataTable/DataTable.stories.tsx +0 -211
  154. package/packages/design-system/src/components/DataTable/DataTable.tsx +0 -293
  155. package/packages/design-system/src/components/DataTable/index.ts +0 -3
  156. package/packages/design-system/src/components/Form/Checkbox.stories.tsx +0 -252
  157. package/packages/design-system/src/components/Form/Checkbox.tsx +0 -114
  158. package/packages/design-system/src/components/Form/Fieldset.stories.tsx +0 -210
  159. package/packages/design-system/src/components/Form/Fieldset.tsx +0 -71
  160. package/packages/design-system/src/components/Form/Input.stories.tsx +0 -164
  161. package/packages/design-system/src/components/Form/Input.tsx +0 -113
  162. package/packages/design-system/src/components/Form/Label.tsx +0 -56
  163. package/packages/design-system/src/components/Form/Radio.stories.tsx +0 -265
  164. package/packages/design-system/src/components/Form/Radio.tsx +0 -147
  165. package/packages/design-system/src/components/Form/Select.stories.tsx +0 -295
  166. package/packages/design-system/src/components/Form/Select.tsx +0 -160
  167. package/packages/design-system/src/components/Form/Textarea.stories.tsx +0 -253
  168. package/packages/design-system/src/components/Form/Textarea.tsx +0 -145
  169. package/packages/design-system/src/components/Form/index.ts +0 -8
  170. package/packages/design-system/src/components/Link/Link.stories.tsx +0 -128
  171. package/packages/design-system/src/components/Link/Link.tsx +0 -117
  172. package/packages/design-system/src/components/Link/index.ts +0 -3
  173. package/packages/design-system/src/components/Modal/Modal.stories.tsx +0 -165
  174. package/packages/design-system/src/components/Modal/Modal.tsx +0 -202
  175. package/packages/design-system/src/components/Modal/index.ts +0 -3
  176. package/packages/design-system/src/components/Tabs/Tabs.stories.tsx +0 -213
  177. package/packages/design-system/src/components/Tabs/Tabs.tsx +0 -248
  178. package/packages/design-system/src/components/Tabs/index.ts +0 -3
  179. package/packages/design-system/src/components/Toast/Toast.stories.tsx +0 -153
  180. package/packages/design-system/src/components/Toast/Toast.tsx +0 -175
  181. package/packages/design-system/src/components/Toast/ToastProvider.tsx +0 -73
  182. package/packages/design-system/src/components/Toast/index.ts +0 -5
  183. package/packages/design-system/src/hooks/useAriaLive.ts +0 -51
  184. package/packages/design-system/src/hooks/useFocusReturn.ts +0 -40
  185. package/packages/design-system/src/hooks/useFocusTrap.ts +0 -82
  186. package/packages/design-system/src/styles/index.ts +0 -3
  187. package/packages/design-system/src/tokens/breakpoints.ts +0 -28
  188. package/packages/design-system/src/tokens/colors.ts +0 -98
  189. package/packages/design-system/src/tokens/index.ts +0 -6
  190. package/packages/design-system/src/tokens/motion.ts +0 -41
  191. package/packages/design-system/src/tokens/spacing.ts +0 -24
  192. package/packages/design-system/src/tokens/theme.ts +0 -19
  193. package/packages/design-system/src/tokens/typography.ts +0 -64
  194. package/packages/design-system/src/utils/aria.ts +0 -108
  195. package/packages/design-system/src/utils/focus.ts +0 -87
  196. package/packages/design-system/src/utils/index.ts +0 -4
  197. package/packages/design-system/src/utils/keyboard.ts +0 -77
  198. package/packages/design-system/tsconfig.json +0 -17
  199. package/public/logo.png +0 -0
  200. package/scripts/fix-storybook-paths.js +0 -53
  201. package/tsconfig.json +0 -20
  202. /package/{packages/design-system/src → dist}/components/Button/Button.css +0 -0
  203. /package/{packages/design-system/src → dist}/components/DataTable/DataTable.css +0 -0
  204. /package/{packages/design-system/src → dist}/components/Form/Checkbox.css +0 -0
  205. /package/{packages/design-system/src → dist}/components/Form/Fieldset.css +0 -0
  206. /package/{packages/design-system/src → dist}/components/Form/Input.css +0 -0
  207. /package/{packages/design-system/src → dist}/components/Form/Label.css +0 -0
  208. /package/{packages/design-system/src → dist}/components/Form/Radio.css +0 -0
  209. /package/{packages/design-system/src → dist}/components/Form/Select.css +0 -0
  210. /package/{packages/design-system/src → dist}/components/Form/Textarea.css +0 -0
  211. /package/{packages/design-system/src → dist}/components/Link/Link.css +0 -0
  212. /package/{packages/design-system/src → dist}/components/Modal/Modal.css +0 -0
  213. /package/{packages/design-system/src → dist}/components/Tabs/Tabs.css +0 -0
  214. /package/{packages/design-system/src → dist}/components/Toast/Toast.css +0 -0
  215. /package/{packages/design-system/src → dist}/components/Toast/ToastProvider.css +0 -0
  216. /package/{packages/design-system/src → dist}/styles/components.css +0 -0
  217. /package/{packages/design-system/src → dist}/styles/global.css +0 -0
@@ -1,40 +0,0 @@
1
- 'use client'
2
-
3
- import { useEffect, useRef } from 'react'
4
-
5
- /**
6
- * Hook to return focus to a previously focused element when component unmounts
7
- * Useful for modals, dropdowns, and other temporary UI elements
8
- *
9
- * @param returnOnUnmount - Whether to return focus on unmount
10
- * @param returnElement - Optional specific element to return focus to
11
- */
12
- export function useFocusReturn(
13
- returnOnUnmount: boolean = true,
14
- returnElement?: HTMLElement | null
15
- ): void {
16
- const savedElementRef = useRef<HTMLElement | null>(null)
17
-
18
- useEffect(() => {
19
- if (!returnOnUnmount) return
20
-
21
- // Save the currently focused element
22
- if (document.activeElement instanceof HTMLElement) {
23
- savedElementRef.current = document.activeElement
24
- }
25
-
26
- return () => {
27
- // Restore focus on unmount
28
- const elementToFocus = returnElement || savedElementRef.current
29
- if (elementToFocus && typeof elementToFocus.focus === 'function') {
30
- try {
31
- elementToFocus.focus()
32
- } catch (error) {
33
- // Silently fail if focus fails
34
- console.warn('Failed to return focus:', error)
35
- }
36
- }
37
- }
38
- }, [returnOnUnmount, returnElement])
39
- }
40
-
@@ -1,82 +0,0 @@
1
- 'use client'
2
-
3
- import { useEffect, useRef } from 'react'
4
- import { getFirstFocusable, getLastFocusable } from '../utils/focus'
5
-
6
- /**
7
- * Hook to trap focus within a container element
8
- * Implements WCAG 2.1.2 Keyboard (No Keyboard Trap)
9
- *
10
- * @param enabled - Whether the focus trap is active
11
- * @param containerRef - Ref to the container element
12
- */
13
- export function useFocusTrap(
14
- enabled: boolean,
15
- containerRef: React.RefObject<HTMLElement>
16
- ): void {
17
- const previousActiveElement = useRef<HTMLElement | null>(null)
18
-
19
- useEffect(() => {
20
- if (!enabled || !containerRef.current) return
21
-
22
- const container = containerRef.current
23
-
24
- // Save the previously focused element
25
- if (document.activeElement instanceof HTMLElement) {
26
- previousActiveElement.current = document.activeElement
27
- }
28
-
29
- // Focus the first focusable element in the container
30
- const firstFocusable = getFirstFocusable(container)
31
- if (firstFocusable) {
32
- firstFocusable.focus()
33
- } else {
34
- // If no focusable elements, focus the container itself
35
- container.focus()
36
- }
37
-
38
- // Handle Tab key to trap focus
39
- const handleKeyDown = (event: KeyboardEvent) => {
40
- if (event.key !== 'Tab') return
41
-
42
- const focusableElements = [
43
- getFirstFocusable(container),
44
- getLastFocusable(container),
45
- ].filter(Boolean) as HTMLElement[]
46
-
47
- if (focusableElements.length === 0) {
48
- event.preventDefault()
49
- return
50
- }
51
-
52
- const firstElement = focusableElements[0]
53
- const lastElement = focusableElements[focusableElements.length - 1]
54
-
55
- if (event.shiftKey) {
56
- // Shift + Tab
57
- if (document.activeElement === firstElement) {
58
- event.preventDefault()
59
- lastElement.focus()
60
- }
61
- } else {
62
- // Tab
63
- if (document.activeElement === lastElement) {
64
- event.preventDefault()
65
- firstElement.focus()
66
- }
67
- }
68
- }
69
-
70
- container.addEventListener('keydown', handleKeyDown)
71
-
72
- return () => {
73
- container.removeEventListener('keydown', handleKeyDown)
74
-
75
- // Restore focus to the previously focused element
76
- if (previousActiveElement.current) {
77
- previousActiveElement.current.focus()
78
- }
79
- }
80
- }, [enabled, containerRef])
81
- }
82
-
@@ -1,3 +0,0 @@
1
- export { theme } from '../tokens/theme'
2
- export type { Theme } from '../tokens/theme'
3
-
@@ -1,28 +0,0 @@
1
- /**
2
- * Responsive breakpoints
3
- * Mobile-first approach
4
- */
5
-
6
- export const breakpoints = {
7
- sm: '640px', // Small devices (landscape phones)
8
- md: '768px', // Medium devices (tablets)
9
- lg: '1024px', // Large devices (desktops)
10
- xl: '1280px', // Extra large devices
11
- '2xl': '1536px', // 2X Extra large devices
12
- } as const
13
-
14
- export type BreakpointToken = typeof breakpoints
15
-
16
- /**
17
- * Media query helpers
18
- */
19
- export const mediaQuery = {
20
- sm: `@media (min-width: ${breakpoints.sm})`,
21
- md: `@media (min-width: ${breakpoints.md})`,
22
- lg: `@media (min-width: ${breakpoints.lg})`,
23
- xl: `@media (min-width: ${breakpoints.xl})`,
24
- '2xl': `@media (min-width: ${breakpoints['2xl']})`,
25
- reducedMotion: '@media (prefers-reduced-motion: reduce)',
26
- highContrast: '@media (prefers-contrast: high)',
27
- } as const
28
-
@@ -1,98 +0,0 @@
1
- /**
2
- * Color tokens with WCAG AA contrast ratios (4.5:1 for normal text, 3:1 for large text)
3
- * All colors meet WCAG 2.1 Level AA standards
4
- */
5
-
6
- export const colors = {
7
- // Primary palette
8
- primary: {
9
- 50: '#f0f9ff',
10
- 100: '#e0f2fe',
11
- 200: '#bae6fd',
12
- 300: '#7dd3fc',
13
- 400: '#38bdf8',
14
- 500: '#0ea5e9', // Main primary - meets 4.5:1 on white
15
- 600: '#0284c7',
16
- 700: '#0369a1',
17
- 800: '#075985',
18
- 900: '#0c4a6e',
19
- },
20
-
21
- // Neutral grays
22
- neutral: {
23
- 50: '#fafafa',
24
- 100: '#f5f5f5',
25
- 200: '#bbbbbb',
26
- 300: '#d4d4d4',
27
- 400: '#a3a3a3',
28
- 500: '#737373',
29
- 600: '#525252', // Meets 4.5:1 on white
30
- 700: '#404040', // Meets 4.5:1 on white
31
- 800: '#262626', // Meets 4.5:1 on white
32
- 900: '#171717', // Meets 4.5:1 on white
33
- },
34
-
35
- // Semantic colors
36
- success: {
37
- 50: '#f0fdf4',
38
- 100: '#dcfce7',
39
- 200: '#bbf7d0',
40
- 300: '#86efac',
41
- 400: '#4ade80',
42
- 500: '#22c55e', // Meets 4.5:1 on white
43
- 600: '#16a34a',
44
- 700: '#15803d',
45
- },
46
-
47
- warning: {
48
- 50: '#fffbeb',
49
- 100: '#fef3c7',
50
- 200: '#fde68a',
51
- 300: '#fcd34d',
52
- 400: '#fbbf24',
53
- 500: '#f59e0b', // Meets 4.5:1 on white
54
- 600: '#d97706',
55
- 700: '#b45309',
56
- },
57
-
58
- error: {
59
- 50: '#fef2f2',
60
- 100: '#fee2e2',
61
- 200: '#fecaca',
62
- 300: '#fca5a5',
63
- 400: '#f87171',
64
- 500: '#ef4444', // Meets 4.5:1 on white
65
- 600: '#dc2626', // Meets 4.5:1 on white
66
- 700: '#b91c1c',
67
- },
68
-
69
- // Base colors
70
- white: '#ffffff',
71
- black: '#000000',
72
-
73
- // Background colors
74
- background: {
75
- default: '#ffffff',
76
- secondary: '#fafafa',
77
- tertiary: '#f5f5f5',
78
- },
79
-
80
- // Text colors (meet contrast requirements)
81
- text: {
82
- primary: '#171717', // neutral.900 - 4.5:1 on white
83
- secondary: '#525252', // neutral.600 - 4.5:1 on white
84
- tertiary: '#737373', // neutral.500 - 4.5:1 on white (large text)
85
- inverse: '#ffffff', // white - 4.5:1 on dark backgrounds
86
- disabled: '#a3a3a3', // neutral.400
87
- },
88
-
89
- // Border colors
90
- border: {
91
- default: '#bbbbbb', // neutral.200
92
- focus: '#0ea5e9', // primary.500
93
- error: '#ef4444', // error.500
94
- },
95
- } as const
96
-
97
- export type ColorToken = typeof colors
98
-
@@ -1,6 +0,0 @@
1
- export * from './colors'
2
- export * from './spacing'
3
- export * from './typography'
4
- export * from './motion'
5
- export * from './breakpoints'
6
-
@@ -1,41 +0,0 @@
1
- /**
2
- * Motion tokens that respect prefers-reduced-motion
3
- * All animations should check for reduced motion preference
4
- */
5
-
6
- export const motion = {
7
- duration: {
8
- fast: '150ms',
9
- normal: '200ms',
10
- slow: '300ms',
11
- slower: '400ms',
12
- },
13
-
14
- easing: {
15
- easeIn: 'cubic-bezier(0.4, 0, 1, 1)',
16
- easeOut: 'cubic-bezier(0, 0, 0.2, 1)',
17
- easeInOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
18
- linear: 'linear',
19
- },
20
-
21
- // Reduced motion overrides (set to 0 or instant)
22
- reduced: {
23
- duration: '0ms',
24
- easing: 'linear',
25
- },
26
- } as const
27
-
28
- export type MotionToken = typeof motion
29
-
30
- /**
31
- * Helper to get motion duration respecting prefers-reduced-motion
32
- * Usage: const duration = getMotionDuration('normal')
33
- * In CSS: use @media (prefers-reduced-motion: reduce) { animation-duration: 0ms; }
34
- */
35
- export function getMotionDuration(key: keyof typeof motion.duration): string {
36
- if (typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
37
- return motion.reduced.duration
38
- }
39
- return motion.duration[key]
40
- }
41
-
@@ -1,24 +0,0 @@
1
- /**
2
- * Spacing scale using 4px base unit
3
- * Follows consistent 8px grid system
4
- */
5
-
6
- export const spacing = {
7
- 0: '0',
8
- 1: '0.25rem', // 4px
9
- 2: '0.5rem', // 8px
10
- 3: '0.75rem', // 12px
11
- 4: '1rem', // 16px
12
- 5: '1.25rem', // 20px
13
- 6: '1.5rem', // 24px
14
- 8: '2rem', // 32px
15
- 10: '2.5rem', // 40px
16
- 12: '3rem', // 48px
17
- 16: '4rem', // 64px
18
- 20: '5rem', // 80px
19
- 24: '6rem', // 96px
20
- 32: '8rem', // 128px
21
- } as const
22
-
23
- export type SpacingToken = typeof spacing
24
-
@@ -1,19 +0,0 @@
1
- /**
2
- * Central theme export combining all design tokens
3
- */
4
- import { colors } from './colors'
5
- import { spacing } from './spacing'
6
- import { typography } from './typography'
7
- import { motion } from './motion'
8
- import { breakpoints } from './breakpoints'
9
-
10
- export const theme = {
11
- colors,
12
- spacing,
13
- typography,
14
- motion,
15
- breakpoints,
16
- } as const
17
-
18
- export type Theme = typeof theme
19
-
@@ -1,64 +0,0 @@
1
- /**
2
- * Typography scale with accessible line heights and font sizes
3
- * Line heights ensure readability (minimum 1.5 for body text)
4
- */
5
-
6
- export const typography = {
7
- fontFamily: {
8
- sans: [
9
- '-apple-system',
10
- 'BlinkMacSystemFont',
11
- '"Segoe UI"',
12
- 'Roboto',
13
- '"Helvetica Neue"',
14
- 'Arial',
15
- 'sans-serif',
16
- ].join(', '),
17
- mono: [
18
- 'Menlo',
19
- 'Monaco',
20
- '"Courier New"',
21
- 'monospace',
22
- ].join(', '),
23
- },
24
-
25
- fontSize: {
26
- xs: '0.75rem', // 12px
27
- sm: '0.875rem', // 14px
28
- base: '1rem', // 16px
29
- lg: '1.125rem', // 18px
30
- xl: '1.25rem', // 20px
31
- '2xl': '1.5rem', // 24px
32
- '3xl': '1.875rem', // 30px
33
- '4xl': '2.25rem', // 36px
34
- '5xl': '3rem', // 48px
35
- },
36
-
37
- lineHeight: {
38
- none: '1',
39
- tight: '1.25',
40
- snug: '1.375',
41
- normal: '1.5', // Minimum for accessibility
42
- relaxed: '1.625',
43
- loose: '2',
44
- },
45
-
46
- fontWeight: {
47
- normal: '400',
48
- medium: '500',
49
- semibold: '600',
50
- bold: '700',
51
- },
52
-
53
- letterSpacing: {
54
- tighter: '-0.05em',
55
- tight: '-0.025em',
56
- normal: '0',
57
- wide: '0.025em',
58
- wider: '0.05em',
59
- widest: '0.1em',
60
- },
61
- } as const
62
-
63
- export type TypographyToken = typeof typography
64
-
@@ -1,108 +0,0 @@
1
- /**
2
- * ARIA utility functions
3
- * Helpers for managing ARIA attributes and roles
4
- */
5
-
6
- /**
7
- * Generate a unique ID for ARIA attributes
8
- */
9
- let idCounter = 0
10
- export function generateId(prefix = 'id'): string {
11
- return `${prefix}-${++idCounter}`
12
- }
13
-
14
- /**
15
- * Get ARIA label from props, preferring aria-label over aria-labelledby
16
- */
17
- export function getAriaLabel(
18
- ariaLabel?: string,
19
- ariaLabelledBy?: string
20
- ): { 'aria-label'?: string; 'aria-labelledby'?: string } {
21
- if (ariaLabel) {
22
- return { 'aria-label': ariaLabel }
23
- }
24
- if (ariaLabelledBy) {
25
- return { 'aria-labelledby': ariaLabelledBy }
26
- }
27
- return {}
28
- }
29
-
30
- /**
31
- * Get ARIA describedby attributes
32
- */
33
- export function getAriaDescribedBy(
34
- describedBy?: string
35
- ): { 'aria-describedby'?: string } {
36
- if (describedBy) {
37
- return { 'aria-describedby': describedBy }
38
- }
39
- return {}
40
- }
41
-
42
- /**
43
- * Combine multiple ARIA describedby IDs
44
- */
45
- export function combineAriaDescribedBy(...ids: (string | undefined)[]): string | undefined {
46
- const validIds = ids.filter((id): id is string => Boolean(id))
47
- return validIds.length > 0 ? validIds.join(' ') : undefined
48
- }
49
-
50
- /**
51
- * Get live region attributes
52
- */
53
- export function getLiveRegionAttributes(
54
- live: 'polite' | 'assertive' | 'off' = 'polite'
55
- ): { 'aria-live': 'polite' | 'assertive' | 'off'; 'aria-atomic'?: boolean } {
56
- return {
57
- 'aria-live': live,
58
- 'aria-atomic': live !== 'off',
59
- }
60
- }
61
-
62
- /**
63
- * Get busy state attributes
64
- */
65
- export function getBusyAttributes(busy: boolean): { 'aria-busy': boolean } {
66
- return { 'aria-busy': busy }
67
- }
68
-
69
- /**
70
- * Get expanded state attributes
71
- */
72
- export function getExpandedAttributes(
73
- expanded: boolean | undefined
74
- ): { 'aria-expanded'?: boolean } {
75
- if (expanded === undefined) return {}
76
- return { 'aria-expanded': expanded }
77
- }
78
-
79
- /**
80
- * Get pressed state attributes (for toggle buttons)
81
- */
82
- export function getPressedAttributes(
83
- pressed: boolean | undefined
84
- ): { 'aria-pressed'?: boolean } {
85
- if (pressed === undefined) return {}
86
- return { 'aria-pressed': pressed }
87
- }
88
-
89
- /**
90
- * Get selected state attributes
91
- */
92
- export function getSelectedAttributes(
93
- selected: boolean | undefined
94
- ): { 'aria-selected'?: boolean } {
95
- if (selected === undefined) return {}
96
- return { 'aria-selected': selected }
97
- }
98
-
99
- /**
100
- * Get current state attributes (for navigation)
101
- */
102
- export function getCurrentAttributes(
103
- current: boolean | 'page' | 'step' | 'location' | 'date' | 'time' | undefined
104
- ): { 'aria-current'?: boolean | 'page' | 'step' | 'location' | 'date' | 'time' } {
105
- if (current === undefined) return {}
106
- return { 'aria-current': current }
107
- }
108
-
@@ -1,87 +0,0 @@
1
- /**
2
- * Focus management utilities
3
- * Helpers for programmatic focus control
4
- */
5
-
6
- /**
7
- * Get all focusable elements within a container
8
- */
9
- export function getFocusableElements(
10
- container: HTMLElement
11
- ): HTMLElement[] {
12
- const focusableSelectors = [
13
- 'a[href]',
14
- 'button:not([disabled])',
15
- 'textarea:not([disabled])',
16
- 'input:not([disabled])',
17
- 'select:not([disabled])',
18
- '[tabindex]:not([tabindex="-1"])',
19
- ].join(', ')
20
-
21
- return Array.from(container.querySelectorAll<HTMLElement>(focusableSelectors))
22
- }
23
-
24
- /**
25
- * Get the first focusable element in a container
26
- */
27
- export function getFirstFocusable(container: HTMLElement): HTMLElement | null {
28
- const focusable = getFocusableElements(container)
29
- return focusable[0] || null
30
- }
31
-
32
- /**
33
- * Get the last focusable element in a container
34
- */
35
- export function getLastFocusable(container: HTMLElement): HTMLElement | null {
36
- const focusable = getFocusableElements(container)
37
- return focusable[focusable.length - 1] || null
38
- }
39
-
40
- /**
41
- * Check if an element is focusable
42
- */
43
- export function isFocusable(element: HTMLElement): boolean {
44
- if (element.tabIndex < 0) return false
45
- if (element.hasAttribute('disabled')) return false
46
- if (element.hasAttribute('hidden')) return false
47
- if (element.style.display === 'none') return false
48
- if (element.style.visibility === 'hidden') return false
49
-
50
- return true
51
- }
52
-
53
- /**
54
- * Save the currently focused element
55
- */
56
- let savedFocusElement: HTMLElement | null = null
57
-
58
- export function saveFocus(): void {
59
- if (document.activeElement instanceof HTMLElement) {
60
- savedFocusElement = document.activeElement
61
- }
62
- }
63
-
64
- /**
65
- * Restore focus to the previously saved element
66
- */
67
- export function restoreFocus(): void {
68
- if (savedFocusElement) {
69
- savedFocusElement.focus()
70
- savedFocusElement = null
71
- }
72
- }
73
-
74
- /**
75
- * Focus an element safely (with error handling)
76
- */
77
- export function safeFocus(element: HTMLElement | null): void {
78
- if (element && typeof element.focus === 'function') {
79
- try {
80
- element.focus()
81
- } catch (error) {
82
- // Silently fail if focus fails
83
- console.warn('Failed to focus element:', error)
84
- }
85
- }
86
- }
87
-
@@ -1,4 +0,0 @@
1
- export * from './aria'
2
- export * from './keyboard'
3
- export * from './focus'
4
-
@@ -1,77 +0,0 @@
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
-