@refraction-ui/astro 0.4.2 → 0.4.4

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 (334) hide show
  1. package/dist/accordion/index.ts +1 -0
  2. package/dist/ai/ai-manager.ts +121 -0
  3. package/dist/ai/index.ts +23 -0
  4. package/dist/ai/mock-providers.ts +68 -0
  5. package/dist/ai/sanitizer.ts +174 -0
  6. package/dist/ai/tts-manager.ts +80 -0
  7. package/dist/ai/types.ts +72 -0
  8. package/dist/animated-text/animated-text.styles.ts +28 -0
  9. package/dist/animated-text/animated-text.ts +83 -0
  10. package/dist/animated-text/index.ts +15 -0
  11. package/dist/app-shell/app-shell.ts +252 -0
  12. package/dist/app-shell/auth-shell.ts +84 -0
  13. package/dist/app-shell/index.ts +22 -0
  14. package/dist/app-shell/page-shell.ts +125 -0
  15. package/dist/astro-accordion/Button.astro +1 -1
  16. package/dist/astro-ai/index.ts +1 -1
  17. package/dist/astro-animated-text/AnimatedText.astro +1 -1
  18. package/dist/astro-animated-text/TypewriterText.astro +1 -1
  19. package/dist/astro-app-shell/AppShell.astro +1 -1
  20. package/dist/astro-app-shell/AuthShell.astro +1 -1
  21. package/dist/astro-app-shell/AuthShellCard.astro +1 -1
  22. package/dist/astro-app-shell/PageShell.astro +1 -1
  23. package/dist/astro-app-shell/PageShellSection.astro +1 -1
  24. package/dist/astro-app-shell/index.ts +1 -1
  25. package/dist/astro-auth/index.ts +2 -2
  26. package/dist/astro-avatar/Avatar.astro +1 -1
  27. package/dist/astro-avatar/AvatarFallback.astro +1 -1
  28. package/dist/astro-avatar/AvatarImage.astro +1 -1
  29. package/dist/astro-avatar-group/AvatarGroup.astro +1 -1
  30. package/dist/astro-badge/Badge.astro +1 -1
  31. package/dist/astro-bottom-nav/BottomNav.astro +1 -1
  32. package/dist/astro-breadcrumbs/Breadcrumbs.astro +1 -1
  33. package/dist/astro-button/Button.astro +1 -1
  34. package/dist/astro-calendar/Calendar.astro +1 -1
  35. package/dist/astro-card/Card.astro +1 -1
  36. package/dist/astro-card/CardContent.astro +1 -1
  37. package/dist/astro-card/CardDescription.astro +1 -1
  38. package/dist/astro-card/CardFooter.astro +1 -1
  39. package/dist/astro-card/CardHeader.astro +1 -1
  40. package/dist/astro-card/CardTitle.astro +1 -1
  41. package/dist/astro-card-grid/CardGrid.astro +1 -1
  42. package/dist/astro-carousel/Button.astro +1 -1
  43. package/dist/astro-charts/Bars.astro +1 -1
  44. package/dist/astro-charts/Chart.astro +1 -1
  45. package/dist/astro-charts/Circles.astro +1 -1
  46. package/dist/astro-charts/Histogram.astro +1 -1
  47. package/dist/astro-charts/Line.astro +1 -1
  48. package/dist/astro-charts/PieChart.astro +1 -1
  49. package/dist/astro-charts/ScatterPlot.astro +1 -1
  50. package/dist/astro-charts/index.ts +1 -1
  51. package/dist/astro-checkbox/Checkbox.astro +1 -1
  52. package/dist/astro-code-editor/CodeEditor.astro +1 -1
  53. package/dist/astro-collapsible/Collapsible.astro +1 -1
  54. package/dist/astro-collapsible/CollapsibleContent.astro +1 -1
  55. package/dist/astro-command/Command.astro +1 -1
  56. package/dist/astro-command/CommandGroup.astro +1 -1
  57. package/dist/astro-command/CommandInput.astro +1 -1
  58. package/dist/astro-command/CommandItem.astro +1 -1
  59. package/dist/astro-command-input/CommandInput.astro +1 -1
  60. package/dist/astro-content-protection/ContentProtection.astro +1 -1
  61. package/dist/astro-data-table/DataTable.astro +1 -1
  62. package/dist/astro-date-picker/DatePicker.astro +1 -1
  63. package/dist/astro-device-frame/DeviceFrame.astro +1 -1
  64. package/dist/astro-dialog/DialogContent.astro +1 -1
  65. package/dist/astro-dialog/DialogOverlay.astro +1 -1
  66. package/dist/astro-diff-viewer/DiffViewer.astro +1 -1
  67. package/dist/astro-dropdown-menu/DropdownMenuContent.astro +1 -1
  68. package/dist/astro-dropdown-menu/DropdownMenuItem.astro +1 -1
  69. package/dist/astro-emoji-picker/EmojiPicker.astro +1 -1
  70. package/dist/astro-feedback-dialog/FeedbackDialog.astro +1 -1
  71. package/dist/astro-file-upload/FileUpload.astro +1 -1
  72. package/dist/astro-footer/Footer.astro +1 -1
  73. package/dist/astro-inline-editor/InlineEditor.astro +1 -1
  74. package/dist/astro-input/Input.astro +1 -1
  75. package/dist/astro-input-group/InputGroup.astro +1 -1
  76. package/dist/astro-input-group/InputGroupAddon.astro +1 -1
  77. package/dist/astro-input-group/InputGroupButton.astro +1 -1
  78. package/dist/astro-install-prompt/InstallPrompt.astro +1 -1
  79. package/dist/astro-keyboard-shortcut/ShortcutBadge.astro +1 -1
  80. package/dist/astro-language-selector/LanguageSelector.astro +1 -1
  81. package/dist/astro-link-card/LinkCard.astro +1 -1
  82. package/dist/astro-markdown-renderer/MarkdownRenderer.astro +1 -1
  83. package/dist/astro-mobile-nav/MobileNav.astro +1 -1
  84. package/dist/astro-mobile-nav/MobileNavLink.astro +1 -1
  85. package/dist/astro-mobile-nav/MobileNavTrigger.astro +1 -1
  86. package/dist/astro-navbar/Navbar.astro +1 -1
  87. package/dist/astro-otp-input/OtpInput.astro +1 -1
  88. package/dist/astro-payment/Payment.astro +1 -1
  89. package/dist/astro-popover/PopoverContent.astro +1 -1
  90. package/dist/astro-presence-indicator/PresenceIndicator.astro +1 -1
  91. package/dist/astro-progress-display/BadgeDisplay.astro +1 -1
  92. package/dist/astro-progress-display/ProgressBar.astro +1 -1
  93. package/dist/astro-progress-display/StatsGrid.astro +1 -1
  94. package/dist/astro-radio/RadioGroup.astro +1 -1
  95. package/dist/astro-radio/RadioItem.astro +1 -1
  96. package/dist/astro-reaction-bar/ReactionBar.astro +1 -1
  97. package/dist/astro-resizable-layout/ResizableDivider.astro +1 -1
  98. package/dist/astro-resizable-layout/ResizableLayout.astro +1 -1
  99. package/dist/astro-resizable-layout/ResizablePane.astro +1 -1
  100. package/dist/astro-resizable-layout/index.ts +1 -1
  101. package/dist/astro-rich-editor/index.ts +2 -2
  102. package/dist/astro-search-bar/SearchBar.astro +1 -1
  103. package/dist/astro-search-bar/SearchResults.astro +1 -1
  104. package/dist/astro-select/Select.astro +1 -1
  105. package/dist/astro-select/SelectContent.astro +1 -1
  106. package/dist/astro-select/SelectItem.astro +1 -1
  107. package/dist/astro-select/SelectTrigger.astro +1 -1
  108. package/dist/astro-sidebar/Sidebar.astro +1 -1
  109. package/dist/astro-skeleton/Skeleton.astro +1 -1
  110. package/dist/astro-slide-viewer/SlideViewer.astro +1 -1
  111. package/dist/astro-status-indicator/StatusIndicator.astro +1 -1
  112. package/dist/astro-switch/Switch.astro +1 -1
  113. package/dist/astro-table-of-contents/TableOfContents.astro +1 -1
  114. package/dist/astro-tabs/Tabs.astro +1 -1
  115. package/dist/astro-tabs/TabsList.astro +1 -1
  116. package/dist/astro-tabs/TabsTrigger.astro +1 -1
  117. package/dist/astro-textarea/Textarea.astro +1 -1
  118. package/dist/astro-theme/ThemeScript.astro +1 -1
  119. package/dist/astro-thread-view/ThreadView.astro +1 -1
  120. package/dist/astro-toast/Toast.astro +1 -1
  121. package/dist/astro-tooltip/TooltipContent.astro +1 -1
  122. package/dist/astro-version-selector/VersionSelector.astro +1 -1
  123. package/dist/astro-video-player/VideoPlayer.astro +1 -1
  124. package/dist/auth/adapters/mock.ts +74 -0
  125. package/dist/auth/auth-machine.ts +120 -0
  126. package/dist/auth/index.ts +22 -0
  127. package/dist/auth/rbac.ts +44 -0
  128. package/dist/auth/types.ts +44 -0
  129. package/dist/avatar/avatar.styles.ts +47 -0
  130. package/dist/avatar/avatar.ts +73 -0
  131. package/dist/avatar/index.ts +15 -0
  132. package/dist/avatar-group/avatar-group.styles.ts +63 -0
  133. package/dist/avatar-group/avatar-group.ts +106 -0
  134. package/dist/avatar-group/index.ts +18 -0
  135. package/dist/badge/badge.styles.ts +24 -0
  136. package/dist/badge/badge.ts +48 -0
  137. package/dist/badge/index.ts +10 -0
  138. package/dist/bottom-nav/bottom-nav.styles.ts +18 -0
  139. package/dist/bottom-nav/bottom-nav.ts +33 -0
  140. package/dist/bottom-nav/index.ts +2 -0
  141. package/dist/breadcrumbs/breadcrumbs.styles.ts +20 -0
  142. package/dist/breadcrumbs/breadcrumbs.ts +72 -0
  143. package/dist/breadcrumbs/index.ts +8 -0
  144. package/dist/button/button.styles.ts +38 -0
  145. package/dist/button/button.ts +64 -0
  146. package/dist/button/index.ts +10 -0
  147. package/dist/calendar/calendar.styles.ts +21 -0
  148. package/dist/calendar/calendar.ts +202 -0
  149. package/dist/calendar/index.ts +9 -0
  150. package/dist/callout/callout.ts +1 -0
  151. package/dist/callout/index.ts +1 -0
  152. package/dist/card/card.styles.ts +55 -0
  153. package/dist/card/card.ts +45 -0
  154. package/dist/card/index.ts +20 -0
  155. package/dist/card-grid/card-grid.ts +9 -0
  156. package/dist/card-grid/index.ts +1 -0
  157. package/dist/carousel/index.ts +1 -0
  158. package/dist/charts/axis.ts +50 -0
  159. package/dist/charts/chart.ts +73 -0
  160. package/dist/charts/colorblind.ts +100 -0
  161. package/dist/charts/data.ts +72 -0
  162. package/dist/charts/geometry.ts +54 -0
  163. package/dist/charts/index.ts +61 -0
  164. package/dist/charts/scales.ts +151 -0
  165. package/dist/checkbox/checkbox.styles.ts +33 -0
  166. package/dist/checkbox/checkbox.ts +73 -0
  167. package/dist/checkbox/index.ts +10 -0
  168. package/dist/code-block/code-block.ts +1 -0
  169. package/dist/code-block/index.ts +1 -0
  170. package/dist/code-editor/code-editor.styles.ts +32 -0
  171. package/dist/code-editor/code-editor.ts +140 -0
  172. package/dist/code-editor/index.ts +9 -0
  173. package/dist/collapsible/collapsible.styles.ts +14 -0
  174. package/dist/collapsible/collapsible.ts +93 -0
  175. package/dist/collapsible/index.ts +7 -0
  176. package/dist/command/command.styles.ts +27 -0
  177. package/dist/command/command.ts +179 -0
  178. package/dist/command/index.ts +14 -0
  179. package/dist/command-input/command-input.ts +176 -0
  180. package/dist/command-input/index.ts +1 -0
  181. package/dist/content-protection/content-protection.styles.ts +9 -0
  182. package/dist/content-protection/content-protection.ts +68 -0
  183. package/dist/content-protection/index.ts +8 -0
  184. package/dist/data-table/data-table.styles.ts +55 -0
  185. package/dist/data-table/data-table.ts +183 -0
  186. package/dist/data-table/index.ts +15 -0
  187. package/dist/date-picker/date-picker.styles.ts +31 -0
  188. package/dist/date-picker/date-picker.ts +333 -0
  189. package/dist/date-picker/index.ts +18 -0
  190. package/dist/device-frame/device-frame.styles.ts +21 -0
  191. package/dist/device-frame/device-frame.ts +112 -0
  192. package/dist/device-frame/index.ts +10 -0
  193. package/dist/dialog/dialog.styles.ts +8 -0
  194. package/dist/dialog/dialog.ts +133 -0
  195. package/dist/dialog/index.ts +8 -0
  196. package/dist/diff-viewer/diff-viewer.styles.ts +124 -0
  197. package/dist/diff-viewer/diff-viewer.ts +255 -0
  198. package/dist/diff-viewer/index.ts +20 -0
  199. package/dist/dropdown-menu/dropdown-menu.styles.ts +9 -0
  200. package/dist/dropdown-menu/dropdown-menu.ts +139 -0
  201. package/dist/dropdown-menu/index.ts +9 -0
  202. package/dist/emoji-picker/emoji-data.ts +282 -0
  203. package/dist/emoji-picker/emoji-picker.styles.ts +32 -0
  204. package/dist/emoji-picker/emoji-picker.ts +165 -0
  205. package/dist/emoji-picker/index.ts +25 -0
  206. package/dist/feedback-dialog/feedback-dialog.styles.ts +15 -0
  207. package/dist/feedback-dialog/feedback-dialog.ts +147 -0
  208. package/dist/feedback-dialog/index.ts +11 -0
  209. package/dist/file-upload/file-upload.styles.ts +30 -0
  210. package/dist/file-upload/file-upload.ts +260 -0
  211. package/dist/file-upload/index.ts +19 -0
  212. package/dist/footer/footer.styles.ts +5 -0
  213. package/dist/footer/footer.ts +32 -0
  214. package/dist/footer/index.ts +9 -0
  215. package/dist/index.ts +1 -1
  216. package/dist/inline-editor/index.ts +13 -0
  217. package/dist/inline-editor/inline-editor.styles.ts +42 -0
  218. package/dist/inline-editor/inline-editor.ts +96 -0
  219. package/dist/input/index.ts +9 -0
  220. package/dist/input/input.styles.ts +15 -0
  221. package/dist/input/input.ts +84 -0
  222. package/dist/input-group/index.ts +13 -0
  223. package/dist/input-group/input-group.styles.ts +50 -0
  224. package/dist/input-group/input-group.ts +44 -0
  225. package/dist/install-prompt/index.ts +9 -0
  226. package/dist/install-prompt/install-prompt.styles.ts +5 -0
  227. package/dist/install-prompt/install-prompt.ts +68 -0
  228. package/dist/keyboard-shortcut/index.ts +12 -0
  229. package/dist/keyboard-shortcut/keyboard-shortcut.styles.ts +8 -0
  230. package/dist/keyboard-shortcut/keyboard-shortcut.ts +153 -0
  231. package/dist/language-selector/index.ts +8 -0
  232. package/dist/language-selector/language-selector.styles.ts +28 -0
  233. package/dist/language-selector/language-selector.ts +149 -0
  234. package/dist/link-card/index.ts +1 -0
  235. package/dist/link-card/link-card.ts +9 -0
  236. package/dist/markdown-renderer/index.ts +8 -0
  237. package/dist/markdown-renderer/markdown-renderer.styles.ts +31 -0
  238. package/dist/markdown-renderer/markdown-renderer.ts +251 -0
  239. package/dist/mobile-nav/index.ts +14 -0
  240. package/dist/mobile-nav/mobile-nav.styles.ts +39 -0
  241. package/dist/mobile-nav/mobile-nav.ts +74 -0
  242. package/dist/navbar/index.ts +9 -0
  243. package/dist/navbar/navbar.styles.ts +29 -0
  244. package/dist/navbar/navbar.ts +39 -0
  245. package/dist/otp-input/index.ts +13 -0
  246. package/dist/otp-input/otp-input.styles.ts +51 -0
  247. package/dist/otp-input/otp-input.ts +198 -0
  248. package/dist/pagination/index.ts +1 -0
  249. package/dist/payment/index.ts +1 -0
  250. package/dist/payment/payment.ts +16 -0
  251. package/dist/popover/index.ts +7 -0
  252. package/dist/popover/popover.styles.ts +16 -0
  253. package/dist/popover/popover.ts +89 -0
  254. package/dist/presence-indicator/index.ts +14 -0
  255. package/dist/presence-indicator/presence-indicator.styles.ts +29 -0
  256. package/dist/presence-indicator/presence-indicator.ts +63 -0
  257. package/dist/progress-display/index.ts +15 -0
  258. package/dist/progress-display/progress-display.styles.ts +72 -0
  259. package/dist/progress-display/progress-display.ts +60 -0
  260. package/dist/radio/index.ts +8 -0
  261. package/dist/radio/radio.styles.ts +34 -0
  262. package/dist/radio/radio.ts +70 -0
  263. package/dist/reaction-bar/index.ts +14 -0
  264. package/dist/reaction-bar/reaction-bar.styles.ts +23 -0
  265. package/dist/reaction-bar/reaction-bar.ts +87 -0
  266. package/dist/resizable-layout/index.ts +12 -0
  267. package/dist/resizable-layout/resizable-layout.styles.ts +50 -0
  268. package/dist/resizable-layout/resizable-layout.ts +139 -0
  269. package/dist/rich-editor/editor.ts +342 -0
  270. package/dist/rich-editor/emoji.ts +175 -0
  271. package/dist/rich-editor/history.ts +79 -0
  272. package/dist/rich-editor/index.ts +106 -0
  273. package/dist/rich-editor/markdown-shortcuts.ts +248 -0
  274. package/dist/rich-editor/mentions.ts +162 -0
  275. package/dist/rich-editor/model.ts +206 -0
  276. package/dist/rich-editor/operations.ts +772 -0
  277. package/dist/rich-editor/selection.ts +157 -0
  278. package/dist/rich-editor/serialization.ts +711 -0
  279. package/dist/rich-editor/slash-commands.ts +212 -0
  280. package/dist/search-bar/index.ts +7 -0
  281. package/dist/search-bar/search-bar.styles.ts +29 -0
  282. package/dist/search-bar/search-bar.ts +130 -0
  283. package/dist/select/index.ts +13 -0
  284. package/dist/select/select.styles.ts +52 -0
  285. package/dist/select/select.ts +152 -0
  286. package/dist/sidebar/index.ts +9 -0
  287. package/dist/sidebar/sidebar.styles.ts +27 -0
  288. package/dist/sidebar/sidebar.ts +59 -0
  289. package/dist/skeleton/index.ts +8 -0
  290. package/dist/skeleton/skeleton.styles.ts +16 -0
  291. package/dist/skeleton/skeleton.ts +39 -0
  292. package/dist/skip-to-content/index.ts +1 -0
  293. package/dist/skip-to-content/skip-to-content.ts +1 -0
  294. package/dist/slide-viewer/index.ts +16 -0
  295. package/dist/slide-viewer/slide-viewer.styles.ts +57 -0
  296. package/dist/slide-viewer/slide-viewer.ts +152 -0
  297. package/dist/slider/index.ts +1 -0
  298. package/dist/slider/slider.ts +2 -0
  299. package/dist/status-indicator/index.ts +15 -0
  300. package/dist/status-indicator/status-indicator.styles.ts +41 -0
  301. package/dist/status-indicator/status-indicator.ts +66 -0
  302. package/dist/steps/index.ts +1 -0
  303. package/dist/steps/steps.ts +1 -0
  304. package/dist/switch/index.ts +7 -0
  305. package/dist/switch/switch.styles.ts +55 -0
  306. package/dist/switch/switch.ts +63 -0
  307. package/dist/table-of-contents/index.ts +1 -0
  308. package/dist/table-of-contents/table-of-contents.ts +37 -0
  309. package/dist/tabs/index.ts +8 -0
  310. package/dist/tabs/tabs.styles.ts +9 -0
  311. package/dist/tabs/tabs.ts +133 -0
  312. package/dist/textarea/index.ts +7 -0
  313. package/dist/textarea/textarea.styles.ts +15 -0
  314. package/dist/textarea/textarea.ts +59 -0
  315. package/dist/theme/dom-adapters.ts +60 -0
  316. package/dist/theme/index.ts +18 -0
  317. package/dist/theme/theme-machine.ts +130 -0
  318. package/dist/theme/theme-script.ts +18 -0
  319. package/dist/thread-view/index.ts +26 -0
  320. package/dist/thread-view/thread-view.styles.ts +35 -0
  321. package/dist/thread-view/thread-view.ts +176 -0
  322. package/dist/toast/index.ts +13 -0
  323. package/dist/toast/toast.styles.ts +19 -0
  324. package/dist/toast/toast.ts +203 -0
  325. package/dist/tooltip/index.ts +7 -0
  326. package/dist/tooltip/tooltip.styles.ts +16 -0
  327. package/dist/tooltip/tooltip.ts +109 -0
  328. package/dist/version-selector/index.ts +12 -0
  329. package/dist/version-selector/version-selector.styles.ts +41 -0
  330. package/dist/version-selector/version-selector.ts +136 -0
  331. package/dist/video-player/index.ts +12 -0
  332. package/dist/video-player/video-player.styles.ts +42 -0
  333. package/dist/video-player/video-player.ts +100 -0
  334. package/package.json +1 -1
@@ -0,0 +1,133 @@
1
+ import type { AccessibilityProps, KeyboardHandlerMap } from '../shared/index.ts'
2
+ import { Keys, generateId } from '../shared/index.ts'
3
+
4
+ export interface TabsProps {
5
+ /** The active tab value (controlled) */
6
+ value?: string
7
+ /** The default active tab value (uncontrolled) */
8
+ defaultValue?: string
9
+ /** Callback when active tab changes */
10
+ onValueChange?: (value: string) => void
11
+ /** Orientation of the tab list */
12
+ orientation?: 'horizontal' | 'vertical'
13
+ }
14
+
15
+ export interface TabsState {
16
+ activeValue: string
17
+ }
18
+
19
+ export interface TabsAPI {
20
+ /** Current tabs state */
21
+ state: TabsState
22
+ /** ARIA attributes for the tab list */
23
+ listProps: Partial<AccessibilityProps> & Record<string, unknown>
24
+ /** Factory for individual tab trigger props */
25
+ getTabProps(value: string): Partial<AccessibilityProps> & Record<string, unknown>
26
+ /** Factory for individual tab panel props */
27
+ getPanelProps(value: string): Partial<AccessibilityProps> & Record<string, unknown>
28
+ /** Select a tab by value */
29
+ select(value: string): void
30
+ /** Keyboard handlers for the tab list */
31
+ keyboardHandlers: KeyboardHandlerMap
32
+ /** Generated ID prefix for aria linking */
33
+ idPrefix: string
34
+ }
35
+
36
+ export function createTabs(props: TabsProps = {}): TabsAPI {
37
+ const {
38
+ value: controlledValue,
39
+ defaultValue = '',
40
+ onValueChange,
41
+ orientation = 'horizontal',
42
+ } = props
43
+
44
+ let uncontrolledValue = controlledValue ?? defaultValue
45
+ const idPrefix = generateId('rfr-tabs')
46
+
47
+ function getActiveValue(): string {
48
+ if (controlledValue !== undefined) return controlledValue
49
+ return uncontrolledValue
50
+ }
51
+
52
+ function select(value: string): void {
53
+ uncontrolledValue = value
54
+ onValueChange?.(value)
55
+ }
56
+
57
+ function getTabId(value: string): string {
58
+ return `${idPrefix}-tab-${value}`
59
+ }
60
+
61
+ function getPanelId(value: string): string {
62
+ return `${idPrefix}-panel-${value}`
63
+ }
64
+
65
+ function getTabProps(value: string): Partial<AccessibilityProps> & Record<string, unknown> {
66
+ const isSelected = getActiveValue() === value
67
+ return {
68
+ role: 'tab',
69
+ 'aria-selected': isSelected,
70
+ 'aria-controls': getPanelId(value),
71
+ tabIndex: isSelected ? 0 : -1,
72
+ id: getTabId(value),
73
+ 'data-state': isSelected ? 'active' : 'inactive',
74
+ }
75
+ }
76
+
77
+ function getPanelProps(value: string): Partial<AccessibilityProps> & Record<string, unknown> {
78
+ const isSelected = getActiveValue() === value
79
+ return {
80
+ role: 'tabpanel',
81
+ 'aria-labelledby': getTabId(value),
82
+ id: getPanelId(value),
83
+ hidden: !isSelected || undefined,
84
+ tabIndex: 0,
85
+ 'data-state': isSelected ? 'active' : 'inactive',
86
+ }
87
+ }
88
+
89
+ const listProps: Partial<AccessibilityProps> & Record<string, unknown> = {
90
+ role: 'tablist',
91
+ 'aria-orientation': orientation,
92
+ }
93
+
94
+ const keyboardHandlers: KeyboardHandlerMap = orientation === 'horizontal'
95
+ ? {
96
+ [Keys.ArrowLeft]: (e) => {
97
+ e.preventDefault()
98
+ },
99
+ [Keys.ArrowRight]: (e) => {
100
+ e.preventDefault()
101
+ },
102
+ [Keys.Home]: (e) => {
103
+ e.preventDefault()
104
+ },
105
+ [Keys.End]: (e) => {
106
+ e.preventDefault()
107
+ },
108
+ }
109
+ : {
110
+ [Keys.ArrowUp]: (e) => {
111
+ e.preventDefault()
112
+ },
113
+ [Keys.ArrowDown]: (e) => {
114
+ e.preventDefault()
115
+ },
116
+ [Keys.Home]: (e) => {
117
+ e.preventDefault()
118
+ },
119
+ [Keys.End]: (e) => {
120
+ e.preventDefault()
121
+ },
122
+ }
123
+
124
+ return {
125
+ state: { activeValue: getActiveValue() },
126
+ listProps,
127
+ getTabProps,
128
+ getPanelProps,
129
+ select,
130
+ keyboardHandlers,
131
+ idPrefix,
132
+ }
133
+ }
@@ -0,0 +1,7 @@
1
+ export {
2
+ createTextarea,
3
+ type TextareaProps,
4
+ type TextareaAPI,
5
+ } from './textarea.js'
6
+
7
+ export { textareaVariants } from './textarea.styles.js'
@@ -0,0 +1,15 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ export const textareaVariants = cva({
4
+ base: 'flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
5
+ variants: {
6
+ size: {
7
+ sm: 'min-h-[40px] text-xs',
8
+ default: 'min-h-[60px]',
9
+ lg: 'min-h-[80px] text-base',
10
+ },
11
+ },
12
+ defaultVariants: {
13
+ size: 'default',
14
+ },
15
+ })
@@ -0,0 +1,59 @@
1
+ import type { AccessibilityProps } from '../shared/index.ts'
2
+
3
+ export interface TextareaProps {
4
+ disabled?: boolean
5
+ readOnly?: boolean
6
+ required?: boolean
7
+ 'aria-invalid'?: boolean
8
+ rows?: number
9
+ maxRows?: number
10
+ }
11
+
12
+ export interface TextareaAPI {
13
+ /** ARIA attributes to spread on the element */
14
+ ariaProps: Partial<AccessibilityProps> & { 'aria-invalid'?: boolean; 'aria-required'?: boolean }
15
+ /** Data attributes for CSS styling hooks */
16
+ dataAttributes: Record<string, string>
17
+ }
18
+
19
+ export function createTextarea(props: TextareaProps = {}): TextareaAPI {
20
+ const {
21
+ disabled = false,
22
+ readOnly = false,
23
+ required = false,
24
+ 'aria-invalid': ariaInvalid,
25
+ } = props
26
+
27
+ const ariaProps: TextareaAPI['ariaProps'] = {}
28
+
29
+ if (disabled) {
30
+ ariaProps['aria-disabled'] = true
31
+ }
32
+
33
+ if (ariaInvalid) {
34
+ ariaProps['aria-invalid'] = true
35
+ }
36
+
37
+ if (required) {
38
+ ariaProps['aria-required'] = true
39
+ }
40
+
41
+ const dataAttributes: Record<string, string> = {}
42
+
43
+ if (disabled) {
44
+ dataAttributes['data-disabled'] = ''
45
+ }
46
+
47
+ if (readOnly) {
48
+ dataAttributes['data-readonly'] = ''
49
+ }
50
+
51
+ if (ariaInvalid) {
52
+ dataAttributes['data-invalid'] = ''
53
+ }
54
+
55
+ return {
56
+ ariaProps,
57
+ dataAttributes,
58
+ }
59
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Browser DOM adapters for the theme machine.
3
+ * These bridge the headless core to browser APIs.
4
+ */
5
+
6
+ import type { StorageAdapter, MediaQueryAdapter, ResolvedTheme } from './theme-machine.js'
7
+
8
+ /** localStorage adapter */
9
+ export function createLocalStorageAdapter(): StorageAdapter {
10
+ return {
11
+ get(key) {
12
+ try {
13
+ return localStorage.getItem(key)
14
+ } catch {
15
+ return null
16
+ }
17
+ },
18
+ set(key, value) {
19
+ try {
20
+ localStorage.setItem(key, value)
21
+ } catch {
22
+ // localStorage unavailable (SSR, incognito quota exceeded, etc.)
23
+ }
24
+ },
25
+ }
26
+ }
27
+
28
+ /** matchMedia adapter */
29
+ export function createMediaQueryAdapter(): MediaQueryAdapter {
30
+ return {
31
+ matches(query) {
32
+ if (typeof window === 'undefined') return false
33
+ return window.matchMedia(query).matches
34
+ },
35
+ subscribe(query, callback) {
36
+ if (typeof window === 'undefined') return () => {}
37
+ const mql = window.matchMedia(query)
38
+ const handler = (e: MediaQueryListEvent) => callback(e.matches)
39
+ mql.addEventListener('change', handler)
40
+ return () => mql.removeEventListener('change', handler)
41
+ },
42
+ }
43
+ }
44
+
45
+ /** Apply resolved theme to the document */
46
+ export function applyThemeToDOM(
47
+ resolved: ResolvedTheme,
48
+ attribute: 'class' | 'data-theme' = 'class',
49
+ ): void {
50
+ if (typeof document === 'undefined') return
51
+
52
+ const root = document.documentElement
53
+ if (attribute === 'class') {
54
+ root.classList.remove('light', 'dark')
55
+ root.classList.add(resolved)
56
+ } else {
57
+ root.setAttribute(attribute, resolved)
58
+ }
59
+ root.style.colorScheme = resolved
60
+ }
@@ -0,0 +1,18 @@
1
+ export {
2
+ createTheme,
3
+ type ThemeMode,
4
+ type ResolvedTheme,
5
+ type ThemeState,
6
+ type ThemeConfig,
7
+ type ThemeAPI,
8
+ type StorageAdapter,
9
+ type MediaQueryAdapter,
10
+ } from './theme-machine.js'
11
+
12
+ export { getThemeScript } from './theme-script.js'
13
+
14
+ export {
15
+ createLocalStorageAdapter,
16
+ createMediaQueryAdapter,
17
+ applyThemeToDOM,
18
+ } from './dom-adapters.js'
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Headless theme state machine — pure TypeScript, zero DOM dependencies.
3
+ * Manages light/dark/system mode with system preference tracking.
4
+ */
5
+
6
+ export type ThemeMode = 'light' | 'dark' | 'system'
7
+ export type ResolvedTheme = 'light' | 'dark'
8
+
9
+ export interface ThemeState {
10
+ /** User's chosen mode */
11
+ mode: ThemeMode
12
+ /** Resolved theme after system preference detection */
13
+ resolved: ResolvedTheme
14
+ }
15
+
16
+ export interface ThemeConfig {
17
+ /** Initial mode. Default: 'system' */
18
+ defaultMode?: ThemeMode
19
+ /** localStorage key. Default: 'rfr-theme' */
20
+ storageKey?: string
21
+ /** HTML attribute to set. Default: 'class' */
22
+ attribute?: 'class' | 'data-theme'
23
+ }
24
+
25
+ export interface StorageAdapter {
26
+ get(key: string): string | null
27
+ set(key: string, value: string): void
28
+ }
29
+
30
+ export interface MediaQueryAdapter {
31
+ matches(query: string): boolean
32
+ subscribe(query: string, callback: (matches: boolean) => void): () => void
33
+ }
34
+
35
+ export interface ThemeAPI {
36
+ /** Get current state */
37
+ getState(): ThemeState
38
+ /** Set mode (light/dark/system) */
39
+ setMode(mode: ThemeMode): void
40
+ /** Subscribe to state changes */
41
+ subscribe(fn: (state: ThemeState) => void): () => void
42
+ /** Clean up subscriptions */
43
+ destroy(): void
44
+ }
45
+
46
+ function resolveTheme(mode: ThemeMode, systemPrefersDark: boolean): ResolvedTheme {
47
+ if (mode === 'system') {
48
+ return systemPrefersDark ? 'dark' : 'light'
49
+ }
50
+ return mode
51
+ }
52
+
53
+ export function createTheme(
54
+ config: ThemeConfig = {},
55
+ storage?: StorageAdapter,
56
+ mediaQuery?: MediaQueryAdapter,
57
+ ): ThemeAPI {
58
+ const {
59
+ defaultMode = 'system',
60
+ storageKey = 'rfr-theme',
61
+ } = config
62
+
63
+ const listeners = new Set<(state: ThemeState) => void>()
64
+ let cleanupMediaQuery: (() => void) | null = null
65
+
66
+ // Read persisted mode or use default
67
+ const persisted = storage?.get(storageKey) as ThemeMode | null
68
+ let mode: ThemeMode = persisted && ['light', 'dark', 'system'].includes(persisted)
69
+ ? persisted
70
+ : defaultMode
71
+
72
+ // Detect system preference
73
+ let systemPrefersDark = mediaQuery?.matches('(prefers-color-scheme: dark)') ?? false
74
+
75
+ let state: ThemeState = {
76
+ mode,
77
+ resolved: resolveTheme(mode, systemPrefersDark),
78
+ }
79
+
80
+ function notify() {
81
+ for (const fn of listeners) {
82
+ fn(state)
83
+ }
84
+ }
85
+
86
+ function updateState(newMode: ThemeMode) {
87
+ mode = newMode
88
+ state = { mode, resolved: resolveTheme(mode, systemPrefersDark) }
89
+ storage?.set(storageKey, mode)
90
+ notify()
91
+ }
92
+
93
+ // Listen for system preference changes
94
+ if (mediaQuery) {
95
+ cleanupMediaQuery = mediaQuery.subscribe(
96
+ '(prefers-color-scheme: dark)',
97
+ (matches) => {
98
+ systemPrefersDark = matches
99
+ if (mode === 'system') {
100
+ state = { mode, resolved: resolveTheme(mode, systemPrefersDark) }
101
+ notify()
102
+ }
103
+ },
104
+ )
105
+ }
106
+
107
+ return {
108
+ getState() {
109
+ return state
110
+ },
111
+
112
+ setMode(newMode: ThemeMode) {
113
+ if (newMode !== mode) {
114
+ updateState(newMode)
115
+ }
116
+ },
117
+
118
+ subscribe(fn: (state: ThemeState) => void) {
119
+ listeners.add(fn)
120
+ return () => {
121
+ listeners.delete(fn)
122
+ }
123
+ },
124
+
125
+ destroy() {
126
+ listeners.clear()
127
+ cleanupMediaQuery?.()
128
+ },
129
+ }
130
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Inline script for preventing theme flash on page load.
3
+ * Inject this as a <script> tag in the <head> before any CSS.
4
+ * Works with any framework (React, Angular, Astro, plain HTML).
5
+ */
6
+
7
+ export function getThemeScript(
8
+ storageKey = 'rfr-theme',
9
+ attribute: 'class' | 'data-theme' = 'class',
10
+ ): string {
11
+ // This string is injected as innerHTML of a <script> tag.
12
+ // It runs before any CSS/JS loads, preventing flash of wrong theme.
13
+ return `(function(){try{var m=localStorage.getItem('${storageKey}');var s=window.matchMedia('(prefers-color-scheme:dark)').matches;var t=m==='dark'||(m!=='light'&&s)?'dark':'light';var d=document.documentElement;${
14
+ attribute === 'class'
15
+ ? "d.classList.remove('light','dark');d.classList.add(t);"
16
+ : `d.setAttribute('${attribute}',t);`
17
+ }d.style.colorScheme=t;}catch(e){}})()`
18
+ }
@@ -0,0 +1,26 @@
1
+ export {
2
+ createThreadView,
3
+ formatTimestamp,
4
+ formatRelativeTime,
5
+ type ThreadViewProps,
6
+ type ThreadViewState,
7
+ type ThreadViewAPI,
8
+ type MessageData,
9
+ type MessageReaction,
10
+ type MessageAttachment,
11
+ } from './thread-view.js'
12
+
13
+ export {
14
+ threadContainerStyles,
15
+ threadMessageStyles,
16
+ threadAvatarStyles,
17
+ threadContentStyles,
18
+ threadAuthorStyles,
19
+ threadTimestampStyles,
20
+ threadBodyStyles,
21
+ threadReactionsStyles,
22
+ threadReplyIndicatorStyles,
23
+ threadActionsStyles,
24
+ threadAttachmentStyles,
25
+ threadEditedStyles,
26
+ } from './thread-view.styles.js'
@@ -0,0 +1,35 @@
1
+ export const threadContainerStyles =
2
+ 'flex flex-col gap-1'
3
+
4
+ export const threadMessageStyles =
5
+ 'flex gap-3 px-4 py-2 hover:bg-accent/50 rounded-md transition-colors group'
6
+
7
+ export const threadAvatarStyles =
8
+ 'h-9 w-9 rounded-full bg-muted flex items-center justify-center text-sm font-medium overflow-hidden flex-shrink-0'
9
+
10
+ export const threadContentStyles =
11
+ 'flex-1 min-w-0'
12
+
13
+ export const threadAuthorStyles =
14
+ 'font-semibold text-sm'
15
+
16
+ export const threadTimestampStyles =
17
+ 'text-xs text-muted-foreground ml-2'
18
+
19
+ export const threadBodyStyles =
20
+ 'text-sm mt-0.5 whitespace-pre-wrap break-words'
21
+
22
+ export const threadReactionsStyles =
23
+ 'flex flex-wrap gap-1 mt-1'
24
+
25
+ export const threadReplyIndicatorStyles =
26
+ 'flex items-center gap-1 mt-1 text-xs text-primary cursor-pointer hover:underline'
27
+
28
+ export const threadActionsStyles =
29
+ 'flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity'
30
+
31
+ export const threadAttachmentStyles =
32
+ 'flex items-center gap-2 mt-1 p-2 rounded border text-xs bg-muted/50'
33
+
34
+ export const threadEditedStyles =
35
+ 'text-xs text-muted-foreground ml-1'
@@ -0,0 +1,176 @@
1
+ import type { AccessibilityProps } from '../shared/index.ts'
2
+ import { generateId } from '../shared/index.ts'
3
+
4
+ export interface MessageReaction {
5
+ emoji: string
6
+ count: number
7
+ userReacted: boolean
8
+ }
9
+
10
+ export interface MessageAttachment {
11
+ id: string
12
+ name: string
13
+ url: string
14
+ type: string
15
+ size?: number
16
+ }
17
+
18
+ export interface MessageData {
19
+ /** Unique message ID */
20
+ id: string
21
+ /** Author display info */
22
+ author: {
23
+ id: string
24
+ name: string
25
+ avatarUrl?: string
26
+ }
27
+ /** Message content (can contain markdown) */
28
+ content: string
29
+ /** Message timestamp */
30
+ timestamp: Date
31
+ /** Reactions on this message */
32
+ reactions?: MessageReaction[]
33
+ /** Threaded replies */
34
+ replies?: MessageData[]
35
+ /** File attachments */
36
+ attachments?: MessageAttachment[]
37
+ /** Whether this message has been edited */
38
+ edited?: boolean
39
+ }
40
+
41
+ export interface ThreadViewProps {
42
+ /** Messages to display */
43
+ messages: MessageData[]
44
+ /** Callback when a reply is sent */
45
+ onReply?: (messageId: string, content: string) => void
46
+ /** Callback when a reaction is toggled */
47
+ onReact?: (messageId: string, emoji: string) => void
48
+ /** Current user ID (for highlighting own messages) */
49
+ currentUserId?: string
50
+ }
51
+
52
+ export interface ThreadViewState {
53
+ messages: MessageData[]
54
+ replyingTo: string | null
55
+ }
56
+
57
+ export interface ThreadViewAPI {
58
+ /** Current state */
59
+ state: ThreadViewState
60
+ /** Start replying to a message */
61
+ startReply(messageId: string): void
62
+ /** Cancel reply */
63
+ cancelReply(): void
64
+ /** Send a reply */
65
+ reply(messageId: string, content: string): void
66
+ /** Toggle a reaction on a message */
67
+ react(messageId: string, emoji: string): void
68
+ /** Format a timestamp for display */
69
+ formatTimestamp(date: Date): string
70
+ /** Format relative time (e.g., "2 minutes ago") */
71
+ formatRelativeTime(date: Date): string
72
+ /** ARIA props for the thread container */
73
+ ariaProps: Partial<AccessibilityProps> & Record<string, unknown>
74
+ /** Get ARIA props for a message */
75
+ getMessageAriaProps(message: MessageData): Record<string, unknown>
76
+ /** Get ARIA props for a reply button */
77
+ getReplyButtonAriaProps(messageId: string): Record<string, unknown>
78
+ /** Generated IDs */
79
+ ids: {
80
+ thread: string
81
+ label: string
82
+ }
83
+ }
84
+
85
+ export function formatTimestamp(date: Date): string {
86
+ const hours = date.getHours()
87
+ const minutes = date.getMinutes()
88
+ const ampm = hours >= 12 ? 'PM' : 'AM'
89
+ const displayHours = hours % 12 || 12
90
+ const displayMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`
91
+ return `${displayHours}:${displayMinutes} ${ampm}`
92
+ }
93
+
94
+ export function formatRelativeTime(date: Date, now?: Date): string {
95
+ const reference = now ?? new Date()
96
+ const diffMs = reference.getTime() - date.getTime()
97
+ const diffSeconds = Math.floor(diffMs / 1000)
98
+ const diffMinutes = Math.floor(diffSeconds / 60)
99
+ const diffHours = Math.floor(diffMinutes / 60)
100
+ const diffDays = Math.floor(diffHours / 24)
101
+
102
+ if (diffSeconds < 60) return 'just now'
103
+ if (diffMinutes < 60) return `${diffMinutes} minute${diffMinutes === 1 ? '' : 's'} ago`
104
+ if (diffHours < 24) return `${diffHours} hour${diffHours === 1 ? '' : 's'} ago`
105
+ if (diffDays < 7) return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`
106
+
107
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
108
+ }
109
+
110
+ export function createThreadView(props: ThreadViewProps): ThreadViewAPI {
111
+ const { messages, onReply, onReact, currentUserId } = props
112
+
113
+ let replyingTo: string | null = null
114
+
115
+ const threadId = generateId('rfr-thread')
116
+ const labelId = generateId('rfr-thread-label')
117
+
118
+ function startReply(messageId: string): void {
119
+ replyingTo = messageId
120
+ }
121
+
122
+ function cancelReply(): void {
123
+ replyingTo = null
124
+ }
125
+
126
+ function reply(messageId: string, content: string): void {
127
+ onReply?.(messageId, content)
128
+ replyingTo = null
129
+ }
130
+
131
+ function react(messageId: string, emoji: string): void {
132
+ onReact?.(messageId, emoji)
133
+ }
134
+
135
+ const ariaProps: Partial<AccessibilityProps> & Record<string, unknown> = {
136
+ role: 'log',
137
+ 'aria-label': 'Message thread',
138
+ 'aria-live': 'polite',
139
+ id: threadId,
140
+ }
141
+
142
+ function getMessageAriaProps(message: MessageData): Record<string, unknown> {
143
+ const isOwn = currentUserId && message.author.id === currentUserId
144
+ return {
145
+ role: 'article',
146
+ 'aria-label': `Message from ${message.author.name}${isOwn ? ' (you)' : ''} at ${formatTimestamp(message.timestamp)}`,
147
+ }
148
+ }
149
+
150
+ function getReplyButtonAriaProps(_messageId: string): Record<string, unknown> {
151
+ return {
152
+ role: 'button',
153
+ 'aria-label': `Reply to message`,
154
+ }
155
+ }
156
+
157
+ return {
158
+ state: {
159
+ messages,
160
+ get replyingTo() { return replyingTo },
161
+ },
162
+ startReply,
163
+ cancelReply,
164
+ reply,
165
+ react,
166
+ formatTimestamp,
167
+ formatRelativeTime,
168
+ ariaProps,
169
+ getMessageAriaProps,
170
+ getReplyButtonAriaProps,
171
+ ids: {
172
+ thread: threadId,
173
+ label: labelId,
174
+ },
175
+ }
176
+ }
@@ -0,0 +1,13 @@
1
+ export {
2
+ createToast,
3
+ createToastManager,
4
+ getToastIcon,
5
+ type ToastVariant,
6
+ type ToastProps,
7
+ type ToastAPI,
8
+ type ToastState,
9
+ type ToastEntry,
10
+ type ToastManagerAPI,
11
+ } from './toast.js'
12
+
13
+ export { toastVariants } from './toast.styles.js'