@refraction-ui/astro 0.3.1 → 0.4.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 (246) hide show
  1. package/dist/astro-accordion/Button.astro +55 -0
  2. package/dist/astro-accordion/index.ts +1 -0
  3. package/dist/astro-ai/AIScript.astro +69 -0
  4. package/dist/astro-ai/TTSScript.astro +60 -0
  5. package/dist/astro-ai/index.ts +16 -0
  6. package/dist/astro-animated-text/AnimatedText.astro +81 -0
  7. package/dist/astro-animated-text/TypewriterText.astro +90 -0
  8. package/dist/astro-animated-text/index.ts +1 -0
  9. package/dist/astro-app-shell/AppShell.astro +195 -0
  10. package/dist/astro-app-shell/AppShellContent.astro +29 -0
  11. package/dist/astro-app-shell/AppShellHeader.astro +54 -0
  12. package/dist/astro-app-shell/AppShellMain.astro +18 -0
  13. package/dist/astro-app-shell/AppShellMobileNav.astro +27 -0
  14. package/dist/astro-app-shell/AppShellOverlay.astro +21 -0
  15. package/dist/astro-app-shell/AppShellSidebar.astro +26 -0
  16. package/dist/astro-app-shell/AuthShell.astro +31 -0
  17. package/dist/astro-app-shell/AuthShellCard.astro +24 -0
  18. package/dist/astro-app-shell/PageShell.astro +36 -0
  19. package/dist/astro-app-shell/PageShellFooter.astro +30 -0
  20. package/dist/astro-app-shell/PageShellNav.astro +33 -0
  21. package/dist/astro-app-shell/PageShellSection.astro +32 -0
  22. package/dist/astro-app-shell/index.ts +21 -0
  23. package/dist/astro-auth/AuthGuard.astro +133 -0
  24. package/dist/astro-auth/index.ts +5 -0
  25. package/dist/astro-avatar/Avatar.astro +22 -0
  26. package/dist/astro-avatar/AvatarFallback.astro +18 -0
  27. package/dist/astro-avatar/AvatarImage.astro +48 -0
  28. package/dist/astro-avatar/index.ts +1 -0
  29. package/dist/astro-avatar-group/AvatarGroup.astro +42 -0
  30. package/dist/astro-avatar-group/index.ts +1 -0
  31. package/dist/astro-badge/Badge.astro +27 -0
  32. package/dist/astro-badge/index.ts +1 -0
  33. package/dist/astro-bottom-nav/BottomNav.astro +40 -0
  34. package/dist/astro-bottom-nav/index.ts +1 -0
  35. package/dist/astro-breadcrumbs/Breadcrumbs.astro +56 -0
  36. package/dist/astro-breadcrumbs/index.ts +1 -0
  37. package/dist/astro-button/Button.astro +55 -0
  38. package/dist/astro-button/index.ts +1 -0
  39. package/dist/astro-calendar/Calendar.astro +259 -0
  40. package/dist/astro-calendar/index.ts +1 -0
  41. package/dist/astro-callout/Callout.astro +3 -0
  42. package/dist/astro-callout/index.ts +1 -0
  43. package/dist/astro-card/Card.astro +20 -0
  44. package/dist/astro-card/CardContent.astro +13 -0
  45. package/dist/astro-card/CardDescription.astro +13 -0
  46. package/dist/astro-card/CardFooter.astro +13 -0
  47. package/dist/astro-card/CardHeader.astro +13 -0
  48. package/dist/astro-card/CardTitle.astro +15 -0
  49. package/dist/astro-card/index.ts +1 -0
  50. package/dist/astro-card-grid/CardGrid.astro +17 -0
  51. package/dist/astro-card-grid/index.ts +1 -0
  52. package/dist/astro-carousel/Button.astro +55 -0
  53. package/dist/astro-carousel/index.ts +1 -0
  54. package/dist/astro-charts/Bars.astro +53 -0
  55. package/dist/astro-charts/Chart.astro +31 -0
  56. package/dist/astro-charts/Circles.astro +48 -0
  57. package/dist/astro-charts/Gradient.astro +34 -0
  58. package/dist/astro-charts/Histogram.astro +107 -0
  59. package/dist/astro-charts/Line.astro +55 -0
  60. package/dist/astro-charts/PieChart.astro +63 -0
  61. package/dist/astro-charts/ScatterPlot.astro +95 -0
  62. package/dist/astro-charts/XAxis.astro +46 -0
  63. package/dist/astro-charts/YAxis.astro +44 -0
  64. package/dist/astro-charts/index.ts +26 -0
  65. package/dist/astro-checkbox/Checkbox.astro +129 -0
  66. package/dist/astro-checkbox/index.ts +1 -0
  67. package/dist/astro-code-block/CodeBlock.astro +3 -0
  68. package/dist/astro-code-block/index.ts +1 -0
  69. package/dist/astro-code-editor/CodeEditor.astro +123 -0
  70. package/dist/astro-code-editor/index.ts +1 -0
  71. package/dist/astro-collapsible/Collapsible.astro +58 -0
  72. package/dist/astro-collapsible/CollapsibleContent.astro +21 -0
  73. package/dist/astro-collapsible/CollapsibleTrigger.astro +17 -0
  74. package/dist/astro-collapsible/index.ts +1 -0
  75. package/dist/astro-command/Command.astro +170 -0
  76. package/dist/astro-command/CommandEmpty.astro +17 -0
  77. package/dist/astro-command/CommandGroup.astro +25 -0
  78. package/dist/astro-command/CommandInput.astro +21 -0
  79. package/dist/astro-command/CommandItem.astro +26 -0
  80. package/dist/astro-command/CommandList.astro +17 -0
  81. package/dist/astro-command/CommandSeparator.astro +14 -0
  82. package/dist/astro-command/index.ts +1 -0
  83. package/dist/astro-command-input/CommandInput.astro +6 -0
  84. package/dist/astro-command-input/index.ts +1 -0
  85. package/dist/astro-content-protection/ContentProtection.astro +104 -0
  86. package/dist/astro-content-protection/index.ts +1 -0
  87. package/dist/astro-data-table/DataTable.astro +263 -0
  88. package/dist/astro-data-table/index.ts +1 -0
  89. package/dist/astro-date-picker/DatePicker.astro +398 -0
  90. package/dist/astro-date-picker/index.ts +1 -0
  91. package/dist/astro-device-frame/DeviceFrame.astro +44 -0
  92. package/dist/astro-device-frame/index.ts +1 -0
  93. package/dist/astro-dialog/Dialog.astro +138 -0
  94. package/dist/astro-dialog/DialogClose.astro +16 -0
  95. package/dist/astro-dialog/DialogContent.astro +24 -0
  96. package/dist/astro-dialog/DialogDescription.astro +15 -0
  97. package/dist/astro-dialog/DialogFooter.astro +14 -0
  98. package/dist/astro-dialog/DialogHeader.astro +14 -0
  99. package/dist/astro-dialog/DialogOverlay.astro +17 -0
  100. package/dist/astro-dialog/DialogTitle.astro +15 -0
  101. package/dist/astro-dialog/DialogTrigger.astro +18 -0
  102. package/dist/astro-dialog/index.ts +1 -0
  103. package/dist/astro-diff-viewer/DiffViewer.astro +371 -0
  104. package/dist/astro-diff-viewer/index.ts +1 -0
  105. package/dist/astro-dropdown-menu/DropdownMenu.astro +138 -0
  106. package/dist/astro-dropdown-menu/DropdownMenuContent.astro +20 -0
  107. package/dist/astro-dropdown-menu/DropdownMenuItem.astro +21 -0
  108. package/dist/astro-dropdown-menu/DropdownMenuLabel.astro +14 -0
  109. package/dist/astro-dropdown-menu/DropdownMenuSeparator.astro +14 -0
  110. package/dist/astro-dropdown-menu/DropdownMenuTrigger.astro +18 -0
  111. package/dist/astro-dropdown-menu/index.ts +1 -0
  112. package/dist/astro-emoji-picker/EmojiPicker.astro +255 -0
  113. package/dist/astro-emoji-picker/index.ts +1 -0
  114. package/dist/astro-feedback-dialog/FeedbackDialog.astro +152 -0
  115. package/dist/astro-feedback-dialog/index.ts +1 -0
  116. package/dist/astro-file-tree/astro-file-tree.astro +4 -0
  117. package/dist/astro-file-tree/index.ts +1 -0
  118. package/dist/astro-file-upload/FileUpload.astro +306 -0
  119. package/dist/astro-file-upload/index.ts +1 -0
  120. package/dist/astro-footer/Footer.astro +61 -0
  121. package/dist/astro-footer/index.ts +1 -0
  122. package/dist/astro-icon-system/astro-icon-system.astro +4 -0
  123. package/dist/astro-icon-system/index.ts +1 -0
  124. package/dist/astro-inline-editor/InlineEditor.astro +169 -0
  125. package/dist/astro-inline-editor/index.ts +1 -0
  126. package/dist/astro-input/Input.astro +49 -0
  127. package/dist/astro-input/index.ts +1 -0
  128. package/dist/astro-input-group/InputGroup.astro +29 -0
  129. package/dist/astro-input-group/InputGroupAddon.astro +14 -0
  130. package/dist/astro-input-group/InputGroupButton.astro +19 -0
  131. package/dist/astro-input-group/InputGroupText.astro +11 -0
  132. package/dist/astro-input-group/index.ts +1 -0
  133. package/dist/astro-install-prompt/InstallPrompt.astro +115 -0
  134. package/dist/astro-install-prompt/index.ts +1 -0
  135. package/dist/astro-keyboard-shortcut/KeyboardShortcut.astro +83 -0
  136. package/dist/astro-keyboard-shortcut/ShortcutBadge.astro +41 -0
  137. package/dist/astro-keyboard-shortcut/index.ts +1 -0
  138. package/dist/astro-language-selector/LanguageSelector.astro +247 -0
  139. package/dist/astro-language-selector/index.ts +1 -0
  140. package/dist/astro-link-card/LinkCard.astro +17 -0
  141. package/dist/astro-link-card/index.ts +1 -0
  142. package/dist/astro-markdown-renderer/MarkdownRenderer.astro +62 -0
  143. package/dist/astro-markdown-renderer/index.ts +1 -0
  144. package/dist/astro-mobile-nav/MobileNav.astro +84 -0
  145. package/dist/astro-mobile-nav/MobileNavContent.astro +29 -0
  146. package/dist/astro-mobile-nav/MobileNavLink.astro +16 -0
  147. package/dist/astro-mobile-nav/MobileNavTrigger.astro +57 -0
  148. package/dist/astro-mobile-nav/index.ts +1 -0
  149. package/dist/astro-navbar/Navbar.astro +49 -0
  150. package/dist/astro-navbar/index.ts +1 -0
  151. package/dist/astro-otp-input/OtpInput.astro +253 -0
  152. package/dist/astro-otp-input/index.ts +1 -0
  153. package/dist/astro-pagination/Pagination.astro +3 -0
  154. package/dist/astro-pagination/index.ts +1 -0
  155. package/dist/astro-payment/Payment.astro +19 -0
  156. package/dist/astro-payment/index.ts +1 -0
  157. package/dist/astro-popover/Popover.astro +87 -0
  158. package/dist/astro-popover/PopoverClose.astro +16 -0
  159. package/dist/astro-popover/PopoverContent.astro +23 -0
  160. package/dist/astro-popover/PopoverTrigger.astro +18 -0
  161. package/dist/astro-popover/index.ts +1 -0
  162. package/dist/astro-presence-indicator/PresenceIndicator.astro +26 -0
  163. package/dist/astro-presence-indicator/index.ts +1 -0
  164. package/dist/astro-progress-display/BadgeDisplay.astro +33 -0
  165. package/dist/astro-progress-display/ProgressBar.astro +27 -0
  166. package/dist/astro-progress-display/StatsGrid.astro +32 -0
  167. package/dist/astro-progress-display/index.ts +1 -0
  168. package/dist/astro-radio/RadioGroup.astro +111 -0
  169. package/dist/astro-radio/RadioItem.astro +36 -0
  170. package/dist/astro-radio/index.ts +1 -0
  171. package/dist/astro-reaction-bar/ReactionBar.astro +117 -0
  172. package/dist/astro-reaction-bar/index.ts +1 -0
  173. package/dist/astro-resizable-layout/ResizableDivider.astro +27 -0
  174. package/dist/astro-resizable-layout/ResizableLayout.astro +162 -0
  175. package/dist/astro-resizable-layout/ResizablePane.astro +28 -0
  176. package/dist/astro-resizable-layout/index.ts +11 -0
  177. package/dist/astro-rich-editor/RichEditor.astro +260 -0
  178. package/dist/astro-rich-editor/index.ts +25 -0
  179. package/dist/astro-search-bar/SearchBar.astro +140 -0
  180. package/dist/astro-search-bar/SearchResultItem.astro +16 -0
  181. package/dist/astro-search-bar/SearchResults.astro +17 -0
  182. package/dist/astro-search-bar/index.ts +1 -0
  183. package/dist/astro-select/Select.astro +165 -0
  184. package/dist/astro-select/SelectContent.astro +19 -0
  185. package/dist/astro-select/SelectItem.astro +42 -0
  186. package/dist/astro-select/SelectTrigger.astro +36 -0
  187. package/dist/astro-select/index.ts +1 -0
  188. package/dist/astro-sidebar/Sidebar.astro +64 -0
  189. package/dist/astro-sidebar/index.ts +1 -0
  190. package/dist/astro-skeleton/Skeleton.astro +39 -0
  191. package/dist/astro-skeleton/SkeletonText.astro +22 -0
  192. package/dist/astro-skeleton/index.ts +1 -0
  193. package/dist/astro-skip-to-content/SkipToContent.astro +3 -0
  194. package/dist/astro-skip-to-content/index.ts +1 -0
  195. package/dist/astro-slide-viewer/SlideViewer.astro +231 -0
  196. package/dist/astro-slide-viewer/index.ts +1 -0
  197. package/dist/astro-slider/index.ts +1 -0
  198. package/dist/astro-status-indicator/StatusIndicator.astro +31 -0
  199. package/dist/astro-status-indicator/index.ts +1 -0
  200. package/dist/astro-steps/Steps.astro +3 -0
  201. package/dist/astro-steps/index.ts +1 -0
  202. package/dist/astro-switch/Switch.astro +89 -0
  203. package/dist/astro-switch/index.ts +1 -0
  204. package/dist/astro-table-of-contents/TableOfContents.astro +53 -0
  205. package/dist/astro-table-of-contents/index.ts +1 -0
  206. package/dist/astro-tabs/Tabs.astro +96 -0
  207. package/dist/astro-tabs/TabsContent.astro +20 -0
  208. package/dist/astro-tabs/TabsList.astro +16 -0
  209. package/dist/astro-tabs/TabsTrigger.astro +24 -0
  210. package/dist/astro-tabs/index.ts +1 -0
  211. package/dist/astro-textarea/Textarea.astro +51 -0
  212. package/dist/astro-textarea/index.ts +1 -0
  213. package/dist/astro-theme/ThemeScript.astro +13 -0
  214. package/dist/astro-theme/ThemeToggle.astro +279 -0
  215. package/dist/astro-theme/index.ts +1 -0
  216. package/dist/astro-thread-view/ThreadView.astro +159 -0
  217. package/dist/astro-thread-view/index.ts +1 -0
  218. package/dist/astro-toast/Toast.astro +49 -0
  219. package/dist/astro-toast/Toaster.astro +142 -0
  220. package/dist/astro-toast/index.ts +1 -0
  221. package/dist/astro-tooltip/Tooltip.astro +95 -0
  222. package/dist/astro-tooltip/TooltipContent.astro +22 -0
  223. package/dist/astro-tooltip/TooltipTrigger.astro +16 -0
  224. package/dist/astro-tooltip/index.ts +1 -0
  225. package/dist/astro-version-selector/VersionSelector.astro +174 -0
  226. package/dist/astro-version-selector/index.ts +1 -0
  227. package/dist/astro-video-player/VideoPlayer.astro +153 -0
  228. package/dist/astro-video-player/index.ts +1 -0
  229. package/dist/index.ts +77 -0
  230. package/dist/shared/a11y.ts +33 -0
  231. package/dist/shared/aria.ts +30 -0
  232. package/dist/shared/cn.ts +35 -0
  233. package/dist/shared/cva.ts +79 -0
  234. package/dist/shared/engine-adapter.ts +10 -0
  235. package/dist/shared/focus-trap.ts +123 -0
  236. package/dist/shared/index.ts +60 -0
  237. package/dist/shared/keyboard.ts +36 -0
  238. package/dist/shared/live-region.ts +90 -0
  239. package/dist/shared/motion.ts +15 -0
  240. package/dist/shared/skip-link.ts +35 -0
  241. package/dist/shared/state-machine.ts +57 -0
  242. package/dist/shared/tokens.ts +16 -0
  243. package/dist/shared/types.ts +45 -0
  244. package/dist/shared/variants.ts +17 -0
  245. package/package.json +22 -20
  246. package/src/index.ts +0 -92
package/dist/index.ts ADDED
@@ -0,0 +1,77 @@
1
+ // Auto-generated meta-package index
2
+ export * from './astro-accordion/index.ts';
3
+ export * from './astro-ai/index.ts';
4
+ export * from './astro-animated-text/index.ts';
5
+ export * from './astro-app-shell/index.ts';
6
+ export * from './astro-auth/index.ts';
7
+ export * from './astro-avatar/index.ts';
8
+ export * from './astro-avatar-group/index.ts';
9
+ export * from './astro-badge/index.ts';
10
+ export * from './astro-bottom-nav/index.ts';
11
+ export * from './astro-breadcrumbs/index.ts';
12
+ export * from './astro-button/index.ts';
13
+ export * from './astro-calendar/index.ts';
14
+ export * from './astro-card/index.ts';
15
+ export * from './astro-charts/index.ts';
16
+ export * from './astro-checkbox/index.ts';
17
+ export * from './astro-code-editor/index.ts';
18
+ export * from './astro-collapsible/index.ts';
19
+ export * from './astro-command/index.ts';
20
+ export * from './astro-content-protection/index.ts';
21
+ export * from './astro-data-table/index.ts';
22
+ export * from './astro-date-picker/index.ts';
23
+ export * from './astro-device-frame/index.ts';
24
+ export * from './astro-dialog/index.ts';
25
+ export * from './astro-diff-viewer/index.ts';
26
+ export * from './astro-dropdown-menu/index.ts';
27
+ export * from './astro-emoji-picker/index.ts';
28
+ export * from './astro-feedback-dialog/index.ts';
29
+ export * from './astro-file-upload/index.ts';
30
+ export * from './astro-footer/index.ts';
31
+ export * from './astro-inline-editor/index.ts';
32
+ export * from './astro-input/index.ts';
33
+ export * from './astro-input-group/index.ts';
34
+ export * from './astro-install-prompt/index.ts';
35
+ export * from './astro-keyboard-shortcut/index.ts';
36
+ export * from './astro-language-selector/index.ts';
37
+ export * from './astro-markdown-renderer/index.ts';
38
+ export * from './astro-mobile-nav/index.ts';
39
+ export * from './astro-navbar/index.ts';
40
+ export * from './astro-otp-input/index.ts';
41
+ export * from './astro-popover/index.ts';
42
+ export * from './astro-presence-indicator/index.ts';
43
+ export * from './astro-progress-display/index.ts';
44
+ export * from './astro-radio/index.ts';
45
+ export * from './astro-reaction-bar/index.ts';
46
+ export * from './astro-resizable-layout/index.ts';
47
+ export * from './astro-rich-editor/index.ts';
48
+ export * from './astro-search-bar/index.ts';
49
+ export * from './astro-select/index.ts';
50
+ export * from './astro-sidebar/index.ts';
51
+ export * from './astro-skeleton/index.ts';
52
+ export * from './astro-slide-viewer/index.ts';
53
+ export * from './astro-status-indicator/index.ts';
54
+ export * from './astro-switch/index.ts';
55
+ export * from './astro-tabs/index.ts';
56
+ export * from './astro-textarea/index.ts';
57
+ export * from './astro-theme/index.ts';
58
+ export * from './astro-thread-view/index.ts';
59
+ export * from './astro-toast/index.ts';
60
+ export * from './astro-tooltip/index.ts';
61
+ export * from './astro-version-selector/index.ts';
62
+ export * from './astro-video-player/index.ts';
63
+ export * from './shared/index.ts';
64
+ export * from './astro-table-of-contents/index.ts';
65
+ export * from './astro-carousel/index.ts';
66
+ export * from './astro-slider/index.ts';
67
+ export * from './astro-pagination/index.ts';
68
+ export * from './astro-callout/index.ts';
69
+ export * from './astro-steps/index.ts';
70
+ export * from './astro-file-tree/index.ts';
71
+ export * from './astro-icon-system/index.ts';
72
+ export * from './astro-skip-to-content/index.ts';
73
+ export * from './astro-code-block/index.ts';
74
+ export * from './astro-link-card/index.ts';
75
+ export * from './astro-card-grid/index.ts';
76
+ export * from './astro-payment/index.ts';
77
+ export * from './astro-command-input/index.ts';
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Accessibility constants and utilities used across all components.
3
+ *
4
+ * Refraction UI Accessibility Principles:
5
+ * 1. NEVER convey meaning through color alone — always provide icon, text, or pattern
6
+ * 2. All interactive elements are keyboard navigable
7
+ * 3. All dynamic content has aria-live announcements
8
+ * 4. All colors meet WCAG AA contrast ratios (4.5:1 normal, 3:1 large)
9
+ * 5. Respect prefers-reduced-motion
10
+ * 6. Respect prefers-contrast
11
+ * 7. Focus indicators are always visible on keyboard navigation
12
+ */
13
+
14
+ /** Standard icon names used across components for status indication */
15
+ export const STATUS_ICONS = {
16
+ success: 'check-circle',
17
+ error: 'x-circle',
18
+ warning: 'alert-triangle',
19
+ info: 'info-circle',
20
+ } as const
21
+
22
+ export type StatusIconName = typeof STATUS_ICONS[keyof typeof STATUS_ICONS]
23
+
24
+ /** Check if a component state relies on color alone (for testing) */
25
+ export function hasNonColorIndicator(config: {
26
+ hasIcon?: boolean
27
+ hasText?: boolean
28
+ hasPattern?: boolean
29
+ hasBorder?: boolean
30
+ hasShape?: boolean
31
+ }): boolean {
32
+ return Object.values(config).some(Boolean)
33
+ }
@@ -0,0 +1,30 @@
1
+ /** Merge multiple ARIA prop objects, later values override earlier ones */
2
+ export function mergeAriaProps(
3
+ ...propSets: Array<Record<string, unknown>>
4
+ ): Record<string, unknown> {
5
+ const result: Record<string, unknown> = {}
6
+ for (const props of propSets) {
7
+ for (const [key, value] of Object.entries(props)) {
8
+ if (value !== undefined) {
9
+ result[key] = value
10
+ }
11
+ }
12
+ }
13
+ return result
14
+ }
15
+
16
+ let idCounter = 0
17
+
18
+ /**
19
+ * Generate a unique ID, safe for SSR (deterministic within a render pass).
20
+ * In browsers, uses crypto.randomUUID when available.
21
+ */
22
+ export function generateId(prefix = 'rfr'): string {
23
+ idCounter++
24
+ return `${prefix}-${idCounter}`
25
+ }
26
+
27
+ /** Reset the ID counter (useful for tests) */
28
+ export function resetIdCounter(): void {
29
+ idCounter = 0
30
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Lightweight class name utility — our own implementation.
3
+ * Handles conditional classes, arrays, and falsy values.
4
+ * No external dependencies (no clsx, no tailwind-merge).
5
+ *
6
+ * For Tailwind class conflict resolution (e.g., 'p-2 p-4' → 'p-4'),
7
+ * consumers can use @refraction-ui/tailwind-config which provides
8
+ * a tw-merge-aware variant of this function.
9
+ */
10
+
11
+ type ClassValue = string | number | boolean | undefined | null | ClassValue[]
12
+ type ClassRecord = Record<string, boolean | undefined | null>
13
+
14
+ export function cn(...inputs: Array<ClassValue | ClassRecord>): string {
15
+ const classes: string[] = []
16
+
17
+ for (const input of inputs) {
18
+ if (!input) continue
19
+
20
+ if (typeof input === 'string') {
21
+ classes.push(input)
22
+ } else if (typeof input === 'number') {
23
+ classes.push(String(input))
24
+ } else if (Array.isArray(input)) {
25
+ const nested = cn(...input)
26
+ if (nested) classes.push(nested)
27
+ } else if (typeof input === 'object') {
28
+ for (const [key, value] of Object.entries(input)) {
29
+ if (value) classes.push(key)
30
+ }
31
+ }
32
+ }
33
+
34
+ return classes.join(' ')
35
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Lightweight class-variance-authority alternative — zero dependencies.
3
+ * Creates variant-driven class name functions for components.
4
+ */
5
+
6
+ interface VariantConfig {
7
+ [variant: string]: Record<string, string>
8
+ }
9
+
10
+ interface CVAConfig<V extends VariantConfig> {
11
+ base?: string
12
+ variants?: V
13
+ defaultVariants?: {
14
+ [K in keyof V]?: keyof V[K]
15
+ }
16
+ compoundVariants?: Array<
17
+ {
18
+ [K in keyof V]?: keyof V[K]
19
+ } & { class: string }
20
+ >
21
+ }
22
+
23
+ type VariantProps<V extends VariantConfig> = {
24
+ [K in keyof V]?: keyof V[K]
25
+ }
26
+
27
+ export function cva<V extends VariantConfig>(config: CVAConfig<V>) {
28
+ return (props?: VariantProps<V> & { className?: string }): string => {
29
+ const classes: string[] = []
30
+
31
+ if (config.base) {
32
+ classes.push(config.base)
33
+ }
34
+
35
+ if (config.variants) {
36
+ for (const [variantKey, variantOptions] of Object.entries(config.variants)) {
37
+ const selectedValue =
38
+ (props as Record<string, unknown> | undefined)?.[variantKey] ??
39
+ config.defaultVariants?.[variantKey]
40
+
41
+ if (selectedValue != null) {
42
+ const variantClass = (variantOptions as Record<string, string>)[
43
+ selectedValue as string
44
+ ]
45
+ if (variantClass) {
46
+ classes.push(variantClass)
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ if (config.compoundVariants) {
53
+ for (const compound of config.compoundVariants) {
54
+ const { class: compoundClass, ...conditions } = compound
55
+ let matches = true
56
+
57
+ for (const [key, value] of Object.entries(conditions)) {
58
+ const propValue =
59
+ (props as Record<string, unknown>)?.[key] ??
60
+ config.defaultVariants?.[key]
61
+ if (propValue !== value) {
62
+ matches = false
63
+ break
64
+ }
65
+ }
66
+
67
+ if (matches) {
68
+ classes.push(compoundClass as string)
69
+ }
70
+ }
71
+ }
72
+
73
+ if (props?.className) {
74
+ classes.push(props.className)
75
+ }
76
+
77
+ return classes.filter(Boolean).join(' ')
78
+ }
79
+ }
@@ -0,0 +1,10 @@
1
+ export interface EngineAdapter<
2
+ TState = Record<string, unknown>,
3
+ TMethods = Record<string, unknown>,
4
+ TReturnProps = Record<string, unknown>
5
+ > {
6
+ name: string;
7
+ state: TState;
8
+ methods: TMethods;
9
+ getProps: (elementName: string, props?: Record<string, unknown>) => TReturnProps;
10
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Focus trap utilities — keeps keyboard focus within a container.
3
+ * Used by Dialog, DropdownMenu, and other overlay components.
4
+ * Pure TypeScript, no DOM dependency (accepts element via parameter).
5
+ */
6
+
7
+ /** Selector for all natively focusable elements */
8
+ export const FOCUSABLE_SELECTOR =
9
+ 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
10
+
11
+ /** Get all focusable elements within a container */
12
+ export function getFocusableElements(container: Element): Element[] {
13
+ return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR))
14
+ }
15
+
16
+ /** Configuration for creating a focus trap */
17
+ export interface FocusTrapConfig {
18
+ /** The container element */
19
+ container: Element
20
+ /** Called to get the initial focus target */
21
+ initialFocus?: () => Element | null
22
+ /** Called when Escape is pressed */
23
+ onEscape?: () => void
24
+ /** Whether to return focus to trigger on deactivate */
25
+ returnFocusOnDeactivate?: boolean
26
+ }
27
+
28
+ /** A focus trap instance */
29
+ export interface FocusTrap {
30
+ activate(): void
31
+ deactivate(): void
32
+ isActive(): boolean
33
+ }
34
+
35
+ /** Create a focus trap that keeps Tab/Shift+Tab within a container */
36
+ export function createFocusTrap(config: FocusTrapConfig): FocusTrap {
37
+ let active = false
38
+ let previouslyFocused: Element | null = null
39
+
40
+ function handleKeyDown(event: KeyboardEvent): void {
41
+ if (event.key === 'Escape' && config.onEscape) {
42
+ event.preventDefault()
43
+ config.onEscape()
44
+ return
45
+ }
46
+
47
+ if (event.key !== 'Tab') return
48
+
49
+ const focusable = getFocusableElements(config.container)
50
+ if (focusable.length === 0) return
51
+
52
+ const first = focusable[0] as HTMLElement
53
+ const last = focusable[focusable.length - 1] as HTMLElement
54
+
55
+ if (event.shiftKey) {
56
+ // Shift+Tab: if on first element, wrap to last
57
+ if (document.activeElement === first) {
58
+ event.preventDefault()
59
+ last.focus()
60
+ }
61
+ } else {
62
+ // Tab: if on last element, wrap to first
63
+ if (document.activeElement === last) {
64
+ event.preventDefault()
65
+ first.focus()
66
+ }
67
+ }
68
+ }
69
+
70
+ return {
71
+ activate() {
72
+ if (active) return
73
+ active = true
74
+
75
+ // Remember the currently focused element so we can restore it later
76
+ if (typeof document !== 'undefined') {
77
+ previouslyFocused = document.activeElement
78
+ }
79
+
80
+ // Set initial focus
81
+ if (config.initialFocus) {
82
+ const target = config.initialFocus()
83
+ if (target && 'focus' in target) {
84
+ (target as HTMLElement).focus()
85
+ }
86
+ } else {
87
+ const focusable = getFocusableElements(config.container)
88
+ if (focusable.length > 0) {
89
+ (focusable[0] as HTMLElement).focus()
90
+ }
91
+ }
92
+
93
+ // Attach keydown listener to the container
94
+ config.container.addEventListener(
95
+ 'keydown',
96
+ handleKeyDown as EventListener,
97
+ )
98
+ },
99
+
100
+ deactivate() {
101
+ if (!active) return
102
+ active = false
103
+
104
+ config.container.removeEventListener(
105
+ 'keydown',
106
+ handleKeyDown as EventListener,
107
+ )
108
+
109
+ // Restore focus to the previously focused element
110
+ if (
111
+ config.returnFocusOnDeactivate !== false &&
112
+ previouslyFocused &&
113
+ 'focus' in previouslyFocused
114
+ ) {
115
+ (previouslyFocused as HTMLElement).focus()
116
+ }
117
+ },
118
+
119
+ isActive() {
120
+ return active
121
+ },
122
+ }
123
+ }
@@ -0,0 +1,60 @@
1
+ // Types
2
+ export type {
3
+ BaseProps,
4
+ AccessibilityProps,
5
+ ThemeProps,
6
+ CompositionProps,
7
+ } from './types.js'
8
+
9
+ export type {
10
+ Size,
11
+ Variant,
12
+ Orientation,
13
+ Side,
14
+ Align,
15
+ DataState,
16
+ } from './variants.js'
17
+
18
+ export type {
19
+ TokenContract,
20
+ TokenDefinition,
21
+ } from './tokens.js'
22
+
23
+ export type {
24
+ KeyboardKey,
25
+ KeyboardHandlerMap,
26
+ } from './keyboard.js'
27
+
28
+ export type {
29
+ MachineConfig,
30
+ Machine,
31
+ } from './state-machine.js'
32
+
33
+ export type {
34
+ FocusTrapConfig,
35
+ FocusTrap,
36
+ } from './focus-trap.js'
37
+
38
+ export type {
39
+ LiveRegionConfig,
40
+ LiveRegion,
41
+ } from './live-region.js'
42
+
43
+ export type {
44
+ EngineAdapter,
45
+ } from './engine-adapter.js'
46
+
47
+ export type {
48
+ SkipLinkProps,
49
+ } from './skip-link.js'
50
+
51
+ // Functions
52
+ export { mergeAriaProps, generateId, resetIdCounter } from './aria.js'
53
+ export { Keys, createKeyboardHandler } from './keyboard.js'
54
+ export { createMachine } from './state-machine.js'
55
+ export { cn } from './cn.js'
56
+ export { cva } from './cva.js'
57
+ export { FOCUSABLE_SELECTOR, getFocusableElements, createFocusTrap } from './focus-trap.js'
58
+ export { createLiveRegion } from './live-region.js'
59
+ export { prefersReducedMotion, getAnimationDuration } from './motion.js'
60
+ export { createSkipLink } from './skip-link.js'
@@ -0,0 +1,36 @@
1
+ /** Standard keyboard key constants */
2
+ export const Keys = {
3
+ Enter: 'Enter',
4
+ Space: ' ',
5
+ Escape: 'Escape',
6
+ Tab: 'Tab',
7
+ ArrowUp: 'ArrowUp',
8
+ ArrowDown: 'ArrowDown',
9
+ ArrowLeft: 'ArrowLeft',
10
+ ArrowRight: 'ArrowRight',
11
+ Home: 'Home',
12
+ End: 'End',
13
+ PageUp: 'PageUp',
14
+ PageDown: 'PageDown',
15
+ Backspace: 'Backspace',
16
+ Delete: 'Delete',
17
+ } as const
18
+
19
+ export type KeyboardKey = (typeof Keys)[keyof typeof Keys]
20
+
21
+ /** Map of key → handler function */
22
+ export type KeyboardHandlerMap = Partial<
23
+ Record<string, (event: KeyboardEvent) => void>
24
+ >
25
+
26
+ /** Create a keyboard event handler from a handler map */
27
+ export function createKeyboardHandler(
28
+ handlers: KeyboardHandlerMap,
29
+ ): (event: KeyboardEvent) => void {
30
+ return (event: KeyboardEvent) => {
31
+ const handler = handlers[event.key]
32
+ if (handler) {
33
+ handler(event)
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Screen reader announcement utility.
3
+ * Creates and manages an aria-live region for dynamic content updates.
4
+ */
5
+
6
+ export interface LiveRegionConfig {
7
+ /** 'polite' (default) or 'assertive' */
8
+ politeness?: 'polite' | 'assertive'
9
+ /** Clear the announcement after this many ms. Default: 5000 */
10
+ clearAfterMs?: number
11
+ }
12
+
13
+ export interface LiveRegion {
14
+ announce(message: string): void
15
+ clear(): void
16
+ destroy(): void
17
+ }
18
+
19
+ /** Create a live region that announces messages to screen readers */
20
+ export function createLiveRegion(config: LiveRegionConfig = {}): LiveRegion {
21
+ const { politeness = 'polite', clearAfterMs = 5000 } = config
22
+ let element: HTMLElement | null = null
23
+ let clearTimeout: ReturnType<typeof setTimeout> | null = null
24
+
25
+ function getElement(): HTMLElement {
26
+ if (!element && typeof document !== 'undefined') {
27
+ element = document.createElement('div')
28
+ element.setAttribute('role', 'status')
29
+ element.setAttribute('aria-live', politeness)
30
+ element.setAttribute('aria-atomic', 'true')
31
+ // Visually hidden but accessible to screen readers
32
+ Object.assign(element.style, {
33
+ position: 'absolute',
34
+ width: '1px',
35
+ height: '1px',
36
+ padding: '0',
37
+ margin: '-1px',
38
+ overflow: 'hidden',
39
+ clip: 'rect(0, 0, 0, 0)',
40
+ whiteSpace: 'nowrap',
41
+ border: '0',
42
+ })
43
+ document.body.appendChild(element)
44
+ }
45
+ return element!
46
+ }
47
+
48
+ return {
49
+ announce(message: string): void {
50
+ const el = getElement()
51
+ if (!el) return
52
+
53
+ // Clear any pending timeout
54
+ if (clearTimeout !== null) {
55
+ globalThis.clearTimeout(clearTimeout)
56
+ }
57
+
58
+ // Set text content — screen readers will detect the change via aria-live
59
+ el.textContent = message
60
+
61
+ if (clearAfterMs > 0) {
62
+ clearTimeout = globalThis.setTimeout(() => {
63
+ el.textContent = ''
64
+ clearTimeout = null
65
+ }, clearAfterMs)
66
+ }
67
+ },
68
+
69
+ clear(): void {
70
+ if (clearTimeout !== null) {
71
+ globalThis.clearTimeout(clearTimeout)
72
+ clearTimeout = null
73
+ }
74
+ if (element) {
75
+ element.textContent = ''
76
+ }
77
+ },
78
+
79
+ destroy(): void {
80
+ if (clearTimeout !== null) {
81
+ globalThis.clearTimeout(clearTimeout)
82
+ clearTimeout = null
83
+ }
84
+ if (element && element.parentNode) {
85
+ element.parentNode.removeChild(element)
86
+ element = null
87
+ }
88
+ },
89
+ }
90
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Reduced motion utilities.
3
+ * Respects user preferences for reduced motion.
4
+ */
5
+
6
+ /** Check if user prefers reduced motion */
7
+ export function prefersReducedMotion(): boolean {
8
+ if (typeof window === 'undefined') return false
9
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches
10
+ }
11
+
12
+ /** Get animation duration — returns '0ms' if reduced motion preferred */
13
+ export function getAnimationDuration(normalDuration: string): string {
14
+ return prefersReducedMotion() ? '0ms' : normalDuration
15
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Skip-to-content link utility.
3
+ * Provides a keyboard-accessible link that is visually hidden until focused.
4
+ */
5
+
6
+ /** Props for a skip-to-content link */
7
+ export interface SkipLinkProps {
8
+ /** ID of the target element. Default: 'main-content' */
9
+ targetId?: string
10
+ /** Label text for the link. Default: 'Skip to main content' */
11
+ label?: string
12
+ }
13
+
14
+ /** Create props for a skip-to-content link */
15
+ export function createSkipLink(props: SkipLinkProps = {}): {
16
+ ariaProps: Record<string, string>
17
+ href: string
18
+ label: string
19
+ /** CSS classes — visually hidden until focused */
20
+ className: string
21
+ } {
22
+ const targetId = props.targetId ?? 'main-content'
23
+ const label = props.label ?? 'Skip to main content'
24
+
25
+ return {
26
+ ariaProps: {
27
+ role: 'link',
28
+ 'aria-label': label,
29
+ },
30
+ href: `#${targetId}`,
31
+ label,
32
+ className:
33
+ 'sr-only focus:not-sr-only focus:absolute focus:z-50 focus:p-4 focus:bg-white focus:text-black focus:underline',
34
+ }
35
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Minimal state machine — zero dependencies, < 1KB.
3
+ * Inspired by XState concepts but dramatically simpler.
4
+ */
5
+
6
+ export interface MachineConfig<TState extends string, TEvent extends string> {
7
+ initial: TState
8
+ states: Record<TState, {
9
+ on?: Partial<Record<TEvent, TState>>
10
+ }>
11
+ }
12
+
13
+ export interface Machine<TState extends string, TEvent extends string> {
14
+ /** Current state */
15
+ state: TState
16
+ /** Send an event to transition */
17
+ send(event: TEvent): void
18
+ /** Subscribe to state changes. Returns unsubscribe function. */
19
+ subscribe(fn: (state: TState) => void): () => void
20
+ /** Check if machine is in a given state */
21
+ matches(state: TState): boolean
22
+ }
23
+
24
+ export function createMachine<TState extends string, TEvent extends string>(
25
+ config: MachineConfig<TState, TEvent>,
26
+ ): Machine<TState, TEvent> {
27
+ let current = config.initial
28
+ const listeners = new Set<(state: TState) => void>()
29
+
30
+ return {
31
+ get state() {
32
+ return current
33
+ },
34
+
35
+ send(event: TEvent) {
36
+ const stateConfig = config.states[current]
37
+ const next = stateConfig?.on?.[event]
38
+ if (next && next !== current) {
39
+ current = next
40
+ for (const fn of listeners) {
41
+ fn(current)
42
+ }
43
+ }
44
+ },
45
+
46
+ subscribe(fn: (state: TState) => void) {
47
+ listeners.add(fn)
48
+ return () => {
49
+ listeners.delete(fn)
50
+ }
51
+ },
52
+
53
+ matches(state: TState) {
54
+ return current === state
55
+ },
56
+ }
57
+ }