@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,149 @@
1
+ import type { KeyboardHandlerMap } from '../shared/index.ts'
2
+ import { generateId, Keys, createKeyboardHandler } from '../shared/index.ts'
3
+
4
+ export interface LanguageOption {
5
+ value: string
6
+ label: string
7
+ group?: string
8
+ }
9
+
10
+ export interface LanguageSelectorProps {
11
+ value?: string | string[]
12
+ onValueChange?: (value: string | string[]) => void
13
+ options: LanguageOption[]
14
+ multiple?: boolean
15
+ }
16
+
17
+ export interface LanguageSelectorAPI {
18
+ /** Current state */
19
+ state: { selectedValues: string[]; isOpen: boolean }
20
+ /** Props to spread on the trigger element */
21
+ triggerProps: {
22
+ 'aria-expanded': boolean
23
+ 'aria-controls': string
24
+ 'aria-haspopup': 'listbox'
25
+ role: 'combobox'
26
+ }
27
+ /** Props to spread on the content/dropdown element */
28
+ contentProps: {
29
+ role: 'listbox'
30
+ id: string
31
+ 'aria-multiselectable'?: boolean
32
+ }
33
+ /** Get props for an individual option */
34
+ getOptionProps: (value: string) => {
35
+ role: 'option'
36
+ 'aria-selected': boolean
37
+ 'data-value': string
38
+ }
39
+ /** Toggle an option's selection */
40
+ toggle: (value: string) => void
41
+ /** Open the dropdown */
42
+ open: () => void
43
+ /** Close the dropdown */
44
+ close: () => void
45
+ /** Keyboard handler map */
46
+ keyboardHandlers: KeyboardHandlerMap
47
+ /** Pre-built keyboard event handler */
48
+ handleKeyDown: (event: KeyboardEvent) => void
49
+ }
50
+
51
+ export function createLanguageSelector(props: LanguageSelectorProps): LanguageSelectorAPI {
52
+ const {
53
+ value: initialValue,
54
+ onValueChange,
55
+ options: _options,
56
+ multiple = false,
57
+ } = props
58
+
59
+ const contentId = generateId('rfr-lang-sel')
60
+
61
+ let selectedValues: string[] = Array.isArray(initialValue)
62
+ ? [...initialValue]
63
+ : initialValue
64
+ ? [initialValue]
65
+ : []
66
+ let isOpen = false
67
+
68
+ const state = { selectedValues: [...selectedValues], isOpen }
69
+
70
+ function setOpen(value: boolean) {
71
+ isOpen = value
72
+ state.isOpen = value
73
+ }
74
+
75
+ function toggle(value: string) {
76
+ if (multiple) {
77
+ const index = selectedValues.indexOf(value)
78
+ if (index >= 0) {
79
+ selectedValues.splice(index, 1)
80
+ } else {
81
+ selectedValues.push(value)
82
+ }
83
+ state.selectedValues = [...selectedValues]
84
+ onValueChange?.([...selectedValues])
85
+ } else {
86
+ selectedValues = [value]
87
+ state.selectedValues = [value]
88
+ onValueChange?.(value)
89
+ setOpen(false)
90
+ }
91
+ }
92
+
93
+ const triggerProps = {
94
+ 'aria-expanded': isOpen,
95
+ 'aria-controls': contentId,
96
+ 'aria-haspopup': 'listbox' as const,
97
+ role: 'combobox' as const,
98
+ }
99
+
100
+ const contentProps: LanguageSelectorAPI['contentProps'] = {
101
+ role: 'listbox' as const,
102
+ id: contentId,
103
+ ...(multiple ? { 'aria-multiselectable': true } : {}),
104
+ }
105
+
106
+ function getOptionProps(value: string) {
107
+ return {
108
+ role: 'option' as const,
109
+ 'aria-selected': selectedValues.includes(value),
110
+ 'data-value': value,
111
+ }
112
+ }
113
+
114
+ const keyboardHandlers: KeyboardHandlerMap = {
115
+ [Keys.Escape]: () => {
116
+ setOpen(false)
117
+ },
118
+ [Keys.Enter]: () => {
119
+ setOpen(!isOpen)
120
+ },
121
+ [Keys.Space]: () => {
122
+ setOpen(!isOpen)
123
+ },
124
+ [Keys.ArrowDown]: () => {
125
+ if (!isOpen) {
126
+ setOpen(true)
127
+ }
128
+ },
129
+ [Keys.ArrowUp]: () => {
130
+ if (!isOpen) {
131
+ setOpen(true)
132
+ }
133
+ },
134
+ }
135
+
136
+ const handleKeyDown = createKeyboardHandler(keyboardHandlers)
137
+
138
+ return {
139
+ state,
140
+ triggerProps,
141
+ contentProps,
142
+ getOptionProps,
143
+ toggle,
144
+ open: () => setOpen(true),
145
+ close: () => setOpen(false),
146
+ keyboardHandlers,
147
+ handleKeyDown,
148
+ }
149
+ }
@@ -0,0 +1 @@
1
+ export * from './link-card.js'
@@ -0,0 +1,9 @@
1
+ export interface LinkCardProps {
2
+ href?: string
3
+ }
4
+
5
+ export function createLinkCard(_props: LinkCardProps = {}) {
6
+ return {
7
+ dataAttributes: { 'data-slot': 'link-card' }
8
+ }
9
+ }
@@ -0,0 +1,8 @@
1
+ export {
2
+ createMarkdownRenderer,
3
+ type MarkdownRendererProps,
4
+ type MarkdownRendererAPI,
5
+ type ComponentDef,
6
+ } from './markdown-renderer.js'
7
+
8
+ export { proseVariants, markdownRendererTokens } from './markdown-renderer.styles.js'
@@ -0,0 +1,31 @@
1
+ import { cva } from '../shared/index.ts'
2
+ import type { TokenContract } from '../shared/index.ts'
3
+
4
+ export const markdownRendererTokens: TokenContract = {
5
+ name: 'markdown-renderer',
6
+ tokens: {
7
+ bg: { variable: '--rfr-markdown-bg', fallback: 'hsl(var(--background))' },
8
+ fg: { variable: '--rfr-markdown-fg', fallback: 'hsl(var(--foreground))' },
9
+ codeBg: { variable: '--rfr-markdown-code-bg', fallback: 'hsl(var(--muted))' },
10
+ linkColor: { variable: '--rfr-markdown-link', fallback: 'hsl(var(--primary))' },
11
+ borderColor: { variable: '--rfr-markdown-border', fallback: 'hsl(var(--border))' },
12
+ },
13
+ }
14
+
15
+ export const proseVariants = cva({
16
+ base: 'prose max-w-none text-foreground leading-relaxed',
17
+ variants: {
18
+ size: {
19
+ sm: 'prose-sm text-sm',
20
+ default: 'prose-base text-base',
21
+ lg: 'prose-lg text-lg',
22
+ },
23
+ theme: {
24
+ light: 'bg-white text-gray-900',
25
+ dark: 'bg-gray-900 text-gray-100',
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ size: 'default',
30
+ },
31
+ })
@@ -0,0 +1,251 @@
1
+ import type { AccessibilityProps } from '../shared/index.ts'
2
+
3
+ export interface ComponentDef {
4
+ type: string
5
+ pattern: RegExp
6
+ props: Record<string, string>
7
+ }
8
+
9
+ export interface MarkdownRendererProps {
10
+ content: string
11
+ components?: Record<string, ComponentDef>
12
+ linkResolver?: (url: string) => string
13
+ }
14
+
15
+ export interface MarkdownRendererAPI {
16
+ /** Rendered HTML string */
17
+ html: string
18
+ /** Extracted component slots from custom component definitions */
19
+ components: Array<{ name: string; props: Record<string, string> }>
20
+ /** ARIA attributes for the rendered content container */
21
+ ariaProps: Partial<AccessibilityProps>
22
+ }
23
+
24
+ /**
25
+ * Escape HTML entities to prevent injection in non-code contexts.
26
+ */
27
+ function escapeHtml(text: string): string {
28
+ return text
29
+ .replace(/&/g, '&amp;')
30
+ .replace(/</g, '&lt;')
31
+ .replace(/>/g, '&gt;')
32
+ .replace(/"/g, '&quot;')
33
+ .replace(/'/g, '&#39;')
34
+ }
35
+
36
+ /**
37
+ * Parse inline markdown elements: bold, italic, code, links.
38
+ */
39
+ function parseInline(text: string, linkResolver?: (url: string) => string): string {
40
+ let result = escapeHtml(text)
41
+
42
+ // Inline code (backticks) — must come before bold/italic to avoid conflicts
43
+ result = result.replace(/`([^`]+)`/g, '<code>$1</code>')
44
+
45
+ // Bold (**text** or __text__)
46
+ result = result.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
47
+ result = result.replace(/__([^_]+)__/g, '<strong>$1</strong>')
48
+
49
+ // Italic (*text* or _text_)
50
+ result = result.replace(/\*([^*]+)\*/g, '<em>$1</em>')
51
+ result = result.replace(/_([^_]+)_/g, '<em>$1</em>')
52
+
53
+ // Links [text](url) — reject javascript: URLs for XSS safety
54
+ result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, text, url) => {
55
+ if (/^\s*javascript\s*:/i.test(url)) {
56
+ return text
57
+ }
58
+ const resolvedUrl = linkResolver ? linkResolver(url) : url
59
+ return `<a href="${resolvedUrl}">${text}</a>`
60
+ })
61
+
62
+ return result
63
+ }
64
+
65
+ /**
66
+ * Parse a full markdown string into HTML.
67
+ * Handles: headings, bold, italic, links, code (inline & block),
68
+ * lists (unordered & ordered), blockquotes, horizontal rules.
69
+ */
70
+ function parseMarkdown(content: string, linkResolver?: (url: string) => string): string {
71
+ const lines = content.split('\n')
72
+ const outputLines: string[] = []
73
+ let inCodeBlock = false
74
+ let codeBlockContent: string[] = []
75
+ let codeBlockLang = ''
76
+ let inList: 'ul' | 'ol' | null = null
77
+ let inBlockquote = false
78
+
79
+ function closeList() {
80
+ if (inList) {
81
+ outputLines.push(inList === 'ul' ? '</ul>' : '</ol>')
82
+ inList = null
83
+ }
84
+ }
85
+
86
+ function closeBlockquote() {
87
+ if (inBlockquote) {
88
+ outputLines.push('</blockquote>')
89
+ inBlockquote = false
90
+ }
91
+ }
92
+
93
+ for (let i = 0; i < lines.length; i++) {
94
+ const line = lines[i]
95
+
96
+ // Code blocks (fenced with ```)
97
+ if (line.trimStart().startsWith('```')) {
98
+ if (inCodeBlock) {
99
+ // Closing code block
100
+ outputLines.push(`<pre><code${codeBlockLang ? ` class="language-${escapeHtml(codeBlockLang)}"` : ''}>${escapeHtml(codeBlockContent.join('\n'))}</code></pre>`)
101
+ codeBlockContent = []
102
+ codeBlockLang = ''
103
+ inCodeBlock = false
104
+ } else {
105
+ // Opening code block
106
+ closeList()
107
+ closeBlockquote()
108
+ inCodeBlock = true
109
+ codeBlockLang = line.trimStart().slice(3).trim()
110
+ }
111
+ continue
112
+ }
113
+
114
+ if (inCodeBlock) {
115
+ codeBlockContent.push(line)
116
+ continue
117
+ }
118
+
119
+ // Horizontal rule (--- or ___ or ***)
120
+ if (/^(\s*[-*_]\s*){3,}$/.test(line)) {
121
+ closeList()
122
+ closeBlockquote()
123
+ outputLines.push('<hr />')
124
+ continue
125
+ }
126
+
127
+ // Headings (# through ######)
128
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/)
129
+ if (headingMatch) {
130
+ closeList()
131
+ closeBlockquote()
132
+ const level = headingMatch[1].length
133
+ const text = parseInline(headingMatch[2], linkResolver)
134
+ outputLines.push(`<h${level}>${text}</h${level}>`)
135
+ continue
136
+ }
137
+
138
+ // Blockquotes (> text)
139
+ const blockquoteMatch = line.match(/^>\s?(.*)$/)
140
+ if (blockquoteMatch) {
141
+ closeList()
142
+ if (!inBlockquote) {
143
+ inBlockquote = true
144
+ outputLines.push('<blockquote>')
145
+ }
146
+ const text = blockquoteMatch[1].trim()
147
+ if (text) {
148
+ outputLines.push(`<p>${parseInline(text, linkResolver)}</p>`)
149
+ }
150
+ continue
151
+ } else if (inBlockquote) {
152
+ closeBlockquote()
153
+ }
154
+
155
+ // Unordered list items (- or * at start)
156
+ const ulMatch = line.match(/^[\s]*[-*+]\s+(.+)$/)
157
+ if (ulMatch) {
158
+ closeBlockquote()
159
+ if (inList !== 'ul') {
160
+ closeList()
161
+ inList = 'ul'
162
+ outputLines.push('<ul>')
163
+ }
164
+ outputLines.push(`<li>${parseInline(ulMatch[1], linkResolver)}</li>`)
165
+ continue
166
+ }
167
+
168
+ // Ordered list items (1. 2. etc.)
169
+ const olMatch = line.match(/^[\s]*\d+\.\s+(.+)$/)
170
+ if (olMatch) {
171
+ closeBlockquote()
172
+ if (inList !== 'ol') {
173
+ closeList()
174
+ inList = 'ol'
175
+ outputLines.push('<ol>')
176
+ }
177
+ outputLines.push(`<li>${parseInline(olMatch[1], linkResolver)}</li>`)
178
+ continue
179
+ }
180
+
181
+ // If we were in a list and hit a non-list line, close the list
182
+ if (inList) {
183
+ closeList()
184
+ }
185
+
186
+ // Empty line
187
+ if (line.trim() === '') {
188
+ continue
189
+ }
190
+
191
+ // Regular paragraph
192
+ outputLines.push(`<p>${parseInline(line, linkResolver)}</p>`)
193
+ }
194
+
195
+ // Close any open blocks
196
+ if (inCodeBlock) {
197
+ outputLines.push(`<pre><code${codeBlockLang ? ` class="language-${escapeHtml(codeBlockLang)}"` : ''}>${escapeHtml(codeBlockContent.join('\n'))}</code></pre>`)
198
+ }
199
+ closeList()
200
+ closeBlockquote()
201
+
202
+ return outputLines.join('\n')
203
+ }
204
+
205
+ /**
206
+ * Extract custom component slots from content based on component definitions.
207
+ */
208
+ function extractComponents(
209
+ content: string,
210
+ components?: Record<string, ComponentDef>,
211
+ ): Array<{ name: string; props: Record<string, string> }> {
212
+ if (!components) return []
213
+
214
+ const extracted: Array<{ name: string; props: Record<string, string> }> = []
215
+
216
+ for (const [name, def] of Object.entries(components)) {
217
+ const matches = Array.from(content.matchAll(def.pattern))
218
+ matches.forEach(() => {
219
+ extracted.push({
220
+ name,
221
+ props: { ...def.props },
222
+ })
223
+ })
224
+ }
225
+
226
+ return extracted
227
+ }
228
+
229
+ /**
230
+ * Create a headless markdown renderer.
231
+ *
232
+ * Uses the headless pattern — returns an API object with rendered HTML,
233
+ * extracted component slots, and ARIA props for the container.
234
+ */
235
+ export function createMarkdownRenderer(props: MarkdownRendererProps): MarkdownRendererAPI {
236
+ const { content, components, linkResolver } = props
237
+
238
+ const html = parseMarkdown(content, linkResolver)
239
+ const extractedComponents = extractComponents(content, components)
240
+
241
+ const ariaProps: Partial<AccessibilityProps> = {
242
+ role: 'document',
243
+ 'aria-label': 'Rendered markdown content',
244
+ }
245
+
246
+ return {
247
+ html,
248
+ components: extractedComponents,
249
+ ariaProps,
250
+ }
251
+ }
@@ -0,0 +1,14 @@
1
+ export {
2
+ createMobileNav,
3
+ type MobileNavProps,
4
+ type MobileNavState,
5
+ type MobileNavAPI,
6
+ } from './mobile-nav.js'
7
+
8
+ export {
9
+ mobileNavVariants,
10
+ mobileNavContentVariants,
11
+ mobileNavTriggerVariants,
12
+ mobileNavLinkVariants,
13
+ mobileNavTokens,
14
+ } from './mobile-nav.styles.js'
@@ -0,0 +1,39 @@
1
+ import { cva } from '../shared/index.ts'
2
+ import type { TokenContract } from '../shared/index.ts'
3
+
4
+ export const mobileNavTokens: TokenContract = {
5
+ name: 'mobile-nav',
6
+ tokens: {
7
+ bg: { variable: '--rfr-mobile-nav-bg', fallback: 'hsl(var(--background))' },
8
+ fg: { variable: '--rfr-mobile-nav-fg', fallback: 'hsl(var(--foreground))' },
9
+ border: { variable: '--rfr-mobile-nav-border', fallback: 'hsl(var(--border))' },
10
+ },
11
+ }
12
+
13
+ export const mobileNavVariants = cva({
14
+ base: 'relative',
15
+ variants: {},
16
+ })
17
+
18
+ export const mobileNavContentVariants = cva({
19
+ base: 'w-full overflow-hidden border-b bg-background transition-all duration-200',
20
+ variants: {
21
+ state: {
22
+ open: 'max-h-screen opacity-100 animate-in slide-in-from-top',
23
+ closed: 'max-h-0 opacity-0 pointer-events-none animate-out slide-out-to-top',
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ state: 'closed',
28
+ },
29
+ })
30
+
31
+ export const mobileNavTriggerVariants = cva({
32
+ base: 'inline-flex items-center justify-center rounded-md p-2 transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
33
+ variants: {},
34
+ })
35
+
36
+ export const mobileNavLinkVariants = cva({
37
+ base: 'block w-full px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2',
38
+ variants: {},
39
+ })
@@ -0,0 +1,74 @@
1
+ import type { AccessibilityProps, KeyboardHandlerMap } from '../shared/index.ts'
2
+ import { Keys, generateId } from '../shared/index.ts'
3
+
4
+ export interface MobileNavProps {
5
+ open?: boolean
6
+ onOpenChange?: (open: boolean) => void
7
+ id?: string
8
+ }
9
+
10
+ export interface MobileNavState {
11
+ open: boolean
12
+ }
13
+
14
+ export interface MobileNavAPI {
15
+ /** Current state */
16
+ state: MobileNavState
17
+ /** ARIA and interaction props for the trigger button */
18
+ triggerProps: Partial<AccessibilityProps> & Record<string, string | boolean | undefined>
19
+ /** ARIA and data attributes for the content panel */
20
+ contentProps: Partial<AccessibilityProps> & Record<string, string | boolean | undefined>
21
+ /** Toggle open state */
22
+ toggle: () => void
23
+ /** Open the nav */
24
+ open: () => void
25
+ /** Close the nav */
26
+ close: () => void
27
+ /** Keyboard handlers (Escape closes) */
28
+ keyboardHandlers: KeyboardHandlerMap
29
+ }
30
+
31
+ export function createMobileNav(props: MobileNavProps = {}): MobileNavAPI {
32
+ const isOpen = props.open ?? false
33
+ const id = props.id ?? generateId('rfr-mobile-nav')
34
+ const contentId = `${id}-content`
35
+
36
+ const state: MobileNavState = {
37
+ open: isOpen,
38
+ }
39
+
40
+ const triggerProps: MobileNavAPI['triggerProps'] = {
41
+ 'aria-expanded': isOpen,
42
+ 'aria-controls': contentId,
43
+ 'aria-label': 'Toggle menu',
44
+ }
45
+
46
+ const contentProps: MobileNavAPI['contentProps'] = {
47
+ id: contentId,
48
+ role: 'menu',
49
+ 'data-state': isOpen ? 'open' : 'closed',
50
+ }
51
+
52
+ function setOpen(value: boolean) {
53
+ state.open = value
54
+ props.onOpenChange?.(value)
55
+ }
56
+
57
+ const toggle = () => setOpen(!state.open)
58
+ const open = () => setOpen(true)
59
+ const close = () => setOpen(false)
60
+
61
+ const keyboardHandlers: KeyboardHandlerMap = {
62
+ [Keys.Escape]: () => close(),
63
+ }
64
+
65
+ return {
66
+ state,
67
+ triggerProps,
68
+ contentProps,
69
+ toggle,
70
+ open,
71
+ close,
72
+ keyboardHandlers,
73
+ }
74
+ }
@@ -0,0 +1,9 @@
1
+ export {
2
+ createNavbar,
3
+ type NavLink,
4
+ type NavbarVariant,
5
+ type NavbarProps,
6
+ type NavbarAPI,
7
+ } from './navbar.js'
8
+
9
+ export { navbarVariants, navLinkVariants } from './navbar.styles.js'
@@ -0,0 +1,29 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ export const navbarVariants = cva({
4
+ base: 'sticky top-0 z-40 w-full border-b',
5
+ variants: {
6
+ variant: {
7
+ solid: 'bg-background',
8
+ blur: 'bg-background/80 backdrop-blur-md',
9
+ gradient: 'bg-gradient-to-b from-background to-background/80 backdrop-blur-sm',
10
+ transparent: 'bg-transparent border-transparent',
11
+ },
12
+ },
13
+ defaultVariants: {
14
+ variant: 'blur',
15
+ },
16
+ })
17
+
18
+ export const navLinkVariants = cva({
19
+ base: 'text-sm font-medium transition-colors hover:text-foreground',
20
+ variants: {
21
+ active: {
22
+ true: 'text-foreground',
23
+ false: 'text-muted-foreground',
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ active: 'false',
28
+ },
29
+ })
@@ -0,0 +1,39 @@
1
+ export interface NavLink {
2
+ label: string
3
+ href: string
4
+ icon?: string
5
+ }
6
+
7
+ export type NavbarVariant = 'solid' | 'blur' | 'gradient' | 'transparent'
8
+
9
+ export interface NavbarProps {
10
+ links?: NavLink[]
11
+ currentPath?: string
12
+ variant?: NavbarVariant
13
+ }
14
+
15
+ export interface NavbarAPI {
16
+ ariaProps: Record<string, string>
17
+ isActive: (href: string) => boolean
18
+ linkAriaProps: (href: string) => Record<string, string>
19
+ }
20
+
21
+ export function createNavbar(props: NavbarProps = {}): NavbarAPI {
22
+ const { currentPath = '/' } = props
23
+
24
+ function isActive(href: string): boolean {
25
+ if (href === '/') return currentPath === '/'
26
+ return currentPath.startsWith(href)
27
+ }
28
+
29
+ function linkAriaProps(href: string): Record<string, string> {
30
+ const active = isActive(href)
31
+ return active ? { 'aria-current': 'page' } : {}
32
+ }
33
+
34
+ return {
35
+ ariaProps: { role: 'navigation', 'aria-label': 'Main navigation' },
36
+ isActive,
37
+ linkAriaProps,
38
+ }
39
+ }
@@ -0,0 +1,13 @@
1
+ export {
2
+ createOtpInput,
3
+ type OtpInputProps,
4
+ type OtpInputAPI,
5
+ type OtpSlotProps,
6
+ type OtpInputType,
7
+ } from './otp-input.js'
8
+
9
+ export {
10
+ otpInputContainerVariants,
11
+ otpInputSlotVariants,
12
+ otpInputTokens,
13
+ } from './otp-input.styles.js'