@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
@@ -0,0 +1,96 @@
1
+ ---
2
+ import { createTabs } from '@refraction-ui/tabs'
3
+
4
+ interface Props extends astroHTML.JSX.HTMLAttributes {
5
+ defaultValue?: string
6
+ orientation?: 'horizontal' | 'vertical'
7
+ }
8
+
9
+ const { defaultValue = '', orientation = 'horizontal', class: className, ...props } = Astro.props
10
+ const api = createTabs({ value: defaultValue, orientation })
11
+ ---
12
+
13
+ <div
14
+ data-orientation={orientation}
15
+ data-rfr-tabs
16
+ data-rfr-tabs-value={defaultValue}
17
+ data-rfr-tabs-id-prefix={api.idPrefix}
18
+ class={className}
19
+ {...props}
20
+ >
21
+ <slot />
22
+ </div>
23
+
24
+ <script>
25
+ document.querySelectorAll<HTMLElement>('[data-rfr-tabs]').forEach((root) => {
26
+ const idPrefix = root.getAttribute('data-rfr-tabs-id-prefix') || ''
27
+
28
+ function selectTab(value: string) {
29
+ root.setAttribute('data-rfr-tabs-value', value)
30
+
31
+ // Update all triggers
32
+ root.querySelectorAll<HTMLButtonElement>('[data-rfr-tab-value]').forEach((trigger) => {
33
+ const triggerValue = trigger.getAttribute('data-rfr-tab-value') || ''
34
+ const isSelected = triggerValue === value
35
+ trigger.setAttribute('aria-selected', String(isSelected))
36
+ trigger.setAttribute('data-state', isSelected ? 'active' : 'inactive')
37
+ trigger.setAttribute('tabindex', isSelected ? '0' : '-1')
38
+ })
39
+
40
+ // Update all panels
41
+ root.querySelectorAll<HTMLElement>('[data-rfr-tab-panel]').forEach((panel) => {
42
+ const panelValue = panel.getAttribute('data-rfr-tab-panel') || ''
43
+ const isSelected = panelValue === value
44
+ panel.setAttribute('data-state', isSelected ? 'active' : 'inactive')
45
+ panel.hidden = !isSelected
46
+ })
47
+
48
+ root.dispatchEvent(new CustomEvent('change', { detail: { value } }))
49
+ }
50
+
51
+ // Handle trigger clicks
52
+ root.addEventListener('click', (e) => {
53
+ const trigger = (e.target as HTMLElement).closest<HTMLButtonElement>('[data-rfr-tab-value]')
54
+ if (!trigger || trigger.disabled) return
55
+ const value = trigger.getAttribute('data-rfr-tab-value') || ''
56
+ selectTab(value)
57
+ trigger.focus()
58
+ })
59
+
60
+ // Keyboard navigation within the tablist
61
+ root.addEventListener('keydown', (e) => {
62
+ const trigger = (e.target as HTMLElement).closest<HTMLButtonElement>('[data-rfr-tab-value]')
63
+ if (!trigger) return
64
+
65
+ const orientation = root.getAttribute('data-orientation') || 'horizontal'
66
+ const triggers = Array.from(root.querySelectorAll<HTMLButtonElement>('[data-rfr-tab-value]')).filter((t) => !t.disabled)
67
+ const currentIndex = triggers.indexOf(trigger)
68
+ if (currentIndex === -1) return
69
+
70
+ let nextIndex = -1
71
+ const prevKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp'
72
+ const nextKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown'
73
+
74
+ if (e.key === nextKey) {
75
+ e.preventDefault()
76
+ nextIndex = (currentIndex + 1) % triggers.length
77
+ } else if (e.key === prevKey) {
78
+ e.preventDefault()
79
+ nextIndex = (currentIndex - 1 + triggers.length) % triggers.length
80
+ } else if (e.key === 'Home') {
81
+ e.preventDefault()
82
+ nextIndex = 0
83
+ } else if (e.key === 'End') {
84
+ e.preventDefault()
85
+ nextIndex = triggers.length - 1
86
+ }
87
+
88
+ if (nextIndex >= 0) {
89
+ const nextTrigger = triggers[nextIndex]
90
+ const nextValue = nextTrigger.getAttribute('data-rfr-tab-value') || ''
91
+ selectTab(nextValue)
92
+ nextTrigger.focus()
93
+ }
94
+ })
95
+ })
96
+ </script>
@@ -0,0 +1,20 @@
1
+ ---
2
+ interface Props extends astroHTML.JSX.HTMLAttributes {
3
+ value: string
4
+ isActive?: boolean
5
+ }
6
+
7
+ const { value, isActive = false, class: className, ...props } = Astro.props
8
+ ---
9
+
10
+ <div
11
+ role="tabpanel"
12
+ tabindex={0}
13
+ data-state={isActive ? 'active' : 'inactive'}
14
+ data-rfr-tab-panel={value}
15
+ hidden={!isActive}
16
+ class={className}
17
+ {...props}
18
+ >
19
+ <slot />
20
+ </div>
@@ -0,0 +1,16 @@
1
+ ---
2
+ import { tabsListVariants } from '@refraction-ui/tabs'
3
+ import { cn } from '../shared/index.ts'
4
+
5
+ interface Props extends astroHTML.JSX.HTMLAttributes {}
6
+
7
+ const { class: className, ...props } = Astro.props
8
+ ---
9
+
10
+ <div
11
+ role="tablist"
12
+ class={cn(tabsListVariants(), className)}
13
+ {...props}
14
+ >
15
+ <slot />
16
+ </div>
@@ -0,0 +1,24 @@
1
+ ---
2
+ import { tabsTriggerVariants } from '@refraction-ui/tabs'
3
+ import { cn } from '../shared/index.ts'
4
+
5
+ interface Props extends astroHTML.JSX.HTMLAttributes {
6
+ value: string
7
+ isActive?: boolean
8
+ }
9
+
10
+ const { value, isActive = false, class: className, ...props } = Astro.props
11
+ ---
12
+
13
+ <button
14
+ type="button"
15
+ role="tab"
16
+ aria-selected={isActive}
17
+ tabindex={isActive ? 0 : -1}
18
+ data-state={isActive ? 'active' : 'inactive'}
19
+ data-rfr-tab-value={value}
20
+ class={cn(tabsTriggerVariants(), className)}
21
+ {...props}
22
+ >
23
+ <slot />
24
+ </button>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,51 @@
1
+ ---
2
+ import {
3
+ createTextarea,
4
+ textareaVariants,
5
+ } from '@refraction-ui/textarea'
6
+ import { cn } from '../shared/index.ts'
7
+
8
+ interface Props extends astroHTML.JSX.HTMLAttributes {
9
+ size?: 'sm' | 'default' | 'lg'
10
+ disabled?: boolean
11
+ readonly?: boolean
12
+ required?: boolean
13
+ rows?: number
14
+ maxRows?: number
15
+ 'aria-invalid'?: boolean
16
+ }
17
+
18
+ const {
19
+ size,
20
+ class: className,
21
+ disabled,
22
+ readonly,
23
+ required,
24
+ rows,
25
+ maxRows,
26
+ 'aria-invalid': ariaInvalid,
27
+ ...props
28
+ } = Astro.props
29
+
30
+ const api = createTextarea({
31
+ disabled,
32
+ readOnly: readonly,
33
+ required,
34
+ rows,
35
+ maxRows,
36
+ 'aria-invalid': ariaInvalid === true ? true : undefined,
37
+ })
38
+
39
+ const classes = cn(textareaVariants({ size }), className)
40
+ ---
41
+
42
+ <textarea
43
+ class={classes}
44
+ disabled={disabled}
45
+ readonly={readonly}
46
+ required={required}
47
+ rows={rows}
48
+ {...api.ariaProps}
49
+ {...api.dataAttributes}
50
+ {...props}
51
+ ><slot /></textarea>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,13 @@
1
+ ---
2
+ import { getThemeScript } from '@refraction-ui/theme'
3
+
4
+ interface Props {
5
+ storageKey?: string
6
+ attribute?: 'class' | 'data-theme'
7
+ }
8
+
9
+ const { storageKey = 'rfr-theme', attribute = 'class' } = Astro.props
10
+ const scriptContent = getThemeScript(storageKey, attribute)
11
+ ---
12
+
13
+ <script set:html={scriptContent} />
@@ -0,0 +1,279 @@
1
+ ---
2
+ import { cn } from '../shared/index.ts'
3
+
4
+ interface Props extends astroHTML.JSX.HTMLAttributes {
5
+ /** 'dropdown' shows a menu, 'segmented' shows inline buttons */
6
+ variant?: 'dropdown' | 'segmented'
7
+ /** localStorage key for theme persistence. Default: 'rfr-theme' */
8
+ storageKey?: string
9
+ /** Attribute to set on <html>. Default: 'class' */
10
+ attribute?: 'class' | 'data-theme'
11
+ }
12
+
13
+ const {
14
+ variant = 'segmented',
15
+ storageKey = 'rfr-theme',
16
+ attribute = 'class',
17
+ class: className,
18
+ ...props
19
+ } = Astro.props
20
+ ---
21
+
22
+ {variant === 'segmented' ? (
23
+ <div
24
+ data-rfr-theme-toggle
25
+ data-rfr-theme-variant="segmented"
26
+ data-rfr-theme-storage-key={storageKey}
27
+ data-rfr-theme-attribute={attribute}
28
+ class={cn('inline-flex items-center gap-1 rounded-lg border p-1', className)}
29
+ role="radiogroup"
30
+ aria-label="Theme"
31
+ {...props}
32
+ >
33
+ <button
34
+ type="button"
35
+ role="radio"
36
+ aria-checked="false"
37
+ aria-label="Light"
38
+ data-rfr-theme-mode="light"
39
+ class="inline-flex items-center justify-center rounded-md p-1.5 text-sm transition-colors text-muted-foreground hover:bg-muted"
40
+ >
41
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
42
+ <circle cx="12" cy="12" r="5" />
43
+ <path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
44
+ </svg>
45
+ </button>
46
+ <button
47
+ type="button"
48
+ role="radio"
49
+ aria-checked="false"
50
+ aria-label="Dark"
51
+ data-rfr-theme-mode="dark"
52
+ class="inline-flex items-center justify-center rounded-md p-1.5 text-sm transition-colors text-muted-foreground hover:bg-muted"
53
+ >
54
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
55
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
56
+ </svg>
57
+ </button>
58
+ <button
59
+ type="button"
60
+ role="radio"
61
+ aria-checked="false"
62
+ aria-label="System"
63
+ data-rfr-theme-mode="system"
64
+ class="inline-flex items-center justify-center rounded-md p-1.5 text-sm transition-colors text-muted-foreground hover:bg-muted"
65
+ >
66
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
67
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
68
+ <line x1="8" y1="21" x2="16" y2="21" />
69
+ <line x1="12" y1="17" x2="12" y2="21" />
70
+ </svg>
71
+ </button>
72
+ </div>
73
+ ) : (
74
+ <div
75
+ data-rfr-theme-toggle
76
+ data-rfr-theme-variant="dropdown"
77
+ data-rfr-theme-storage-key={storageKey}
78
+ data-rfr-theme-attribute={attribute}
79
+ class={cn('relative', className)}
80
+ {...props}
81
+ >
82
+ <button
83
+ type="button"
84
+ aria-label="Toggle theme"
85
+ aria-expanded="false"
86
+ data-rfr-theme-dropdown-trigger
87
+ class="inline-flex items-center justify-center rounded-md p-2 text-sm transition-colors hover:bg-muted"
88
+ >
89
+ <svg data-rfr-theme-icon="sun" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
90
+ <circle cx="12" cy="12" r="5" />
91
+ <path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
92
+ </svg>
93
+ <svg data-rfr-theme-icon="moon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none">
94
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
95
+ </svg>
96
+ <svg data-rfr-theme-icon="monitor" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none">
97
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
98
+ <line x1="8" y1="21" x2="16" y2="21" />
99
+ <line x1="12" y1="17" x2="12" y2="21" />
100
+ </svg>
101
+ </button>
102
+ <div
103
+ data-rfr-theme-dropdown-menu
104
+ class="absolute right-0 top-full mt-1 z-50 min-w-[8rem] rounded-md border bg-popover p-1 shadow-md"
105
+ role="menu"
106
+ hidden
107
+ >
108
+ <button
109
+ type="button"
110
+ role="menuitem"
111
+ data-rfr-theme-mode="light"
112
+ class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors hover:bg-accent"
113
+ >
114
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
115
+ <circle cx="12" cy="12" r="5" />
116
+ <path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
117
+ </svg>
118
+ Light
119
+ </button>
120
+ <button
121
+ type="button"
122
+ role="menuitem"
123
+ data-rfr-theme-mode="dark"
124
+ class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors hover:bg-accent"
125
+ >
126
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
127
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
128
+ </svg>
129
+ Dark
130
+ </button>
131
+ <button
132
+ type="button"
133
+ role="menuitem"
134
+ data-rfr-theme-mode="system"
135
+ class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors hover:bg-accent"
136
+ >
137
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
138
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
139
+ <line x1="8" y1="21" x2="16" y2="21" />
140
+ <line x1="12" y1="17" x2="12" y2="21" />
141
+ </svg>
142
+ System
143
+ </button>
144
+ </div>
145
+ </div>
146
+ )}
147
+
148
+ <script>
149
+ function initThemeToggles() {
150
+ const ACTIVE_CLASS = 'bg-accent text-accent-foreground'
151
+ const INACTIVE_CLASS = 'text-muted-foreground hover:bg-muted'
152
+ const ACTIVE_MENU_CLASS = 'bg-accent'
153
+
154
+ const MODE_ICONS: Record<string, string> = {
155
+ light: 'sun',
156
+ dark: 'moon',
157
+ system: 'monitor',
158
+ }
159
+
160
+ document.querySelectorAll<HTMLElement>('[data-rfr-theme-toggle]').forEach((root) => {
161
+ if (root.hasAttribute('data-rfr-theme-init')) return
162
+ root.setAttribute('data-rfr-theme-init', '')
163
+
164
+ const variant = root.getAttribute('data-rfr-theme-variant')
165
+ const storageKey = root.getAttribute('data-rfr-theme-storage-key') || 'rfr-theme'
166
+ const attribute = root.getAttribute('data-rfr-theme-attribute') || 'class'
167
+
168
+ function getCurrentMode(): string {
169
+ try {
170
+ return localStorage.getItem(storageKey) || 'system'
171
+ } catch {
172
+ return 'system'
173
+ }
174
+ }
175
+
176
+ function resolveTheme(mode: string): string {
177
+ if (mode === 'system') {
178
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
179
+ }
180
+ return mode
181
+ }
182
+
183
+ function applyTheme(mode: string) {
184
+ const resolved = resolveTheme(mode)
185
+ const el = document.documentElement
186
+ if (attribute === 'class') {
187
+ el.classList.remove('light', 'dark')
188
+ el.classList.add(resolved)
189
+ } else {
190
+ el.setAttribute(attribute, resolved)
191
+ }
192
+ el.style.colorScheme = resolved
193
+ try {
194
+ localStorage.setItem(storageKey, mode)
195
+ } catch { /* ignore */ }
196
+ }
197
+
198
+ function setMode(mode: string) {
199
+ applyTheme(mode)
200
+ updateUI(mode)
201
+ root.dispatchEvent(new CustomEvent('rfr-theme-change', { detail: { mode, resolved: resolveTheme(mode) }, bubbles: true }))
202
+ }
203
+
204
+ function updateUI(mode: string) {
205
+ if (variant === 'segmented') {
206
+ root.querySelectorAll<HTMLElement>('[data-rfr-theme-mode]').forEach((btn) => {
207
+ const isActive = btn.getAttribute('data-rfr-theme-mode') === mode
208
+ btn.setAttribute('aria-checked', String(isActive))
209
+ // Reset classes
210
+ btn.className = `inline-flex items-center justify-center rounded-md p-1.5 text-sm transition-colors ${isActive ? ACTIVE_CLASS : INACTIVE_CLASS}`
211
+ })
212
+ } else {
213
+ // Dropdown variant
214
+ const trigger = root.querySelector<HTMLElement>('[data-rfr-theme-dropdown-trigger]')
215
+ if (trigger) {
216
+ const iconName = MODE_ICONS[mode] || 'monitor'
217
+ trigger.querySelectorAll<HTMLElement>('[data-rfr-theme-icon]').forEach((svg) => {
218
+ svg.style.display = svg.getAttribute('data-rfr-theme-icon') === iconName ? '' : 'none'
219
+ })
220
+ }
221
+ root.querySelectorAll<HTMLElement>('[data-rfr-theme-mode]').forEach((btn) => {
222
+ const isActive = btn.getAttribute('data-rfr-theme-mode') === mode
223
+ btn.className = `flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors hover:bg-accent ${isActive ? ACTIVE_MENU_CLASS : ''}`
224
+ })
225
+ }
226
+ }
227
+
228
+ // Handle clicks on mode buttons
229
+ root.querySelectorAll<HTMLElement>('[data-rfr-theme-mode]').forEach((btn) => {
230
+ btn.addEventListener('click', () => {
231
+ const mode = btn.getAttribute('data-rfr-theme-mode')
232
+ if (mode) setMode(mode)
233
+
234
+ // Close dropdown if open
235
+ if (variant === 'dropdown') {
236
+ const menu = root.querySelector<HTMLElement>('[data-rfr-theme-dropdown-menu]')
237
+ const trigger = root.querySelector<HTMLElement>('[data-rfr-theme-dropdown-trigger]')
238
+ if (menu) menu.hidden = true
239
+ if (trigger) trigger.setAttribute('aria-expanded', 'false')
240
+ }
241
+ })
242
+ })
243
+
244
+ // Dropdown toggle behavior
245
+ if (variant === 'dropdown') {
246
+ const trigger = root.querySelector<HTMLElement>('[data-rfr-theme-dropdown-trigger]')
247
+ const menu = root.querySelector<HTMLElement>('[data-rfr-theme-dropdown-menu]')
248
+
249
+ trigger?.addEventListener('click', () => {
250
+ const isOpen = !menu?.hidden
251
+ if (menu) menu.hidden = isOpen
252
+ trigger.setAttribute('aria-expanded', String(!isOpen))
253
+ })
254
+
255
+ // Close on outside click
256
+ document.addEventListener('mousedown', (e) => {
257
+ if (!root.contains(e.target as Node) && menu && !menu.hidden) {
258
+ menu.hidden = true
259
+ trigger?.setAttribute('aria-expanded', 'false')
260
+ }
261
+ })
262
+ }
263
+
264
+ // Listen for system preference changes
265
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
266
+ const mode = getCurrentMode()
267
+ if (mode === 'system') {
268
+ applyTheme('system')
269
+ }
270
+ })
271
+
272
+ // Initialize UI
273
+ updateUI(getCurrentMode())
274
+ })
275
+ }
276
+
277
+ initThemeToggles()
278
+ document.addEventListener('astro:after-swap', initThemeToggles)
279
+ </script>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,159 @@
1
+ ---
2
+ import {
3
+ createThreadView,
4
+ threadContainerStyles,
5
+ threadMessageStyles,
6
+ threadAvatarStyles,
7
+ threadContentStyles,
8
+ threadAuthorStyles,
9
+ threadTimestampStyles,
10
+ threadBodyStyles,
11
+ threadReactionsStyles,
12
+ threadReplyIndicatorStyles,
13
+ threadActionsStyles,
14
+ threadAttachmentStyles,
15
+ threadEditedStyles,
16
+ type MessageData,
17
+ } from '@refraction-ui/thread-view'
18
+ import { cn } from '../shared/index.ts'
19
+
20
+ interface Props extends astroHTML.JSX.HTMLAttributes {
21
+ messages: MessageData[]
22
+ currentUserId?: string
23
+ }
24
+
25
+ const {
26
+ messages,
27
+ currentUserId,
28
+ class: className,
29
+ ...props
30
+ } = Astro.props
31
+
32
+ const api = createThreadView({ messages, currentUserId })
33
+ ---
34
+
35
+ <div
36
+ role={api.ariaProps.role}
37
+ aria-label={api.ariaProps['aria-label'] as string}
38
+ aria-live={api.ariaProps['aria-live'] as any}
39
+ id={api.ids.thread}
40
+ class={cn(threadContainerStyles, className)}
41
+ data-rfr-thread-view
42
+ {...props}
43
+ >
44
+ {messages.map((message) => {
45
+ const msgAriaProps = api.getMessageAriaProps(message)
46
+ const replyAriaProps = api.getReplyButtonAriaProps(message.id)
47
+ return (
48
+ <div
49
+ class={threadMessageStyles}
50
+ role={msgAriaProps.role as string}
51
+ aria-label={msgAriaProps['aria-label'] as string}
52
+ data-rfr-thread-message
53
+ data-message-id={message.id}
54
+ >
55
+ {/* Avatar */}
56
+ <div class={threadAvatarStyles}>
57
+ {message.author.avatarUrl ? (
58
+ <img
59
+ src={message.author.avatarUrl}
60
+ alt={message.author.name}
61
+ class="h-full w-full object-cover"
62
+ />
63
+ ) : (
64
+ message.author.name.charAt(0).toUpperCase()
65
+ )}
66
+ </div>
67
+
68
+ {/* Content */}
69
+ <div class={threadContentStyles}>
70
+ <div class="flex items-baseline">
71
+ <span class={threadAuthorStyles}>{message.author.name}</span>
72
+ <span class={threadTimestampStyles}>
73
+ {api.formatTimestamp(message.timestamp)}
74
+ </span>
75
+ {message.edited && (
76
+ <span class={threadEditedStyles}>(edited)</span>
77
+ )}
78
+ </div>
79
+
80
+ <div class={threadBodyStyles}>{message.content}</div>
81
+
82
+ {/* Reactions */}
83
+ {message.reactions && message.reactions.length > 0 && (
84
+ <div class={threadReactionsStyles}>
85
+ {message.reactions.map((reaction, i) => (
86
+ <button
87
+ type="button"
88
+ class={cn(
89
+ 'inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-xs cursor-pointer',
90
+ reaction.userReacted ? 'border-primary bg-primary/10' : 'border-border',
91
+ )}
92
+ data-rfr-thread-reaction
93
+ data-message-id={message.id}
94
+ data-emoji={reaction.emoji}
95
+ >
96
+ {reaction.emoji} {reaction.count}
97
+ </button>
98
+ ))}
99
+ </div>
100
+ )}
101
+
102
+ {/* Reply indicator */}
103
+ {message.replies && message.replies.length > 0 && (
104
+ <div class={threadReplyIndicatorStyles}>
105
+ {message.replies.length} {message.replies.length === 1 ? 'reply' : 'replies'}
106
+ </div>
107
+ )}
108
+
109
+ {/* Attachments */}
110
+ {message.attachments && message.attachments.map((attachment) => (
111
+ <div class={threadAttachmentStyles}>
112
+ <span>&#x1F4CE;</span>
113
+ <span class="truncate">{attachment.name}</span>
114
+ </div>
115
+ ))}
116
+
117
+ {/* Actions (reply button) */}
118
+ <div class={threadActionsStyles}>
119
+ <button
120
+ type="button"
121
+ class="text-xs text-muted-foreground hover:text-foreground cursor-pointer"
122
+ role={replyAriaProps.role as string}
123
+ aria-label={replyAriaProps['aria-label'] as string}
124
+ data-rfr-thread-reply
125
+ data-message-id={message.id}
126
+ >
127
+ Reply
128
+ </button>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ )
133
+ })}
134
+ </div>
135
+
136
+ <script>
137
+ document.querySelectorAll<HTMLElement>('[data-rfr-thread-view]').forEach((root) => {
138
+ // Handle reaction clicks
139
+ root.addEventListener('click', (e) => {
140
+ const reactionBtn = (e.target as HTMLElement).closest<HTMLButtonElement>('[data-rfr-thread-reaction]')
141
+ if (reactionBtn) {
142
+ const messageId = reactionBtn.getAttribute('data-message-id') || ''
143
+ const emoji = reactionBtn.getAttribute('data-emoji') || ''
144
+ root.dispatchEvent(
145
+ new CustomEvent('react', { detail: { messageId, emoji }, bubbles: true }),
146
+ )
147
+ return
148
+ }
149
+
150
+ const replyBtn = (e.target as HTMLElement).closest<HTMLButtonElement>('[data-rfr-thread-reply]')
151
+ if (replyBtn) {
152
+ const messageId = replyBtn.getAttribute('data-message-id') || ''
153
+ root.dispatchEvent(
154
+ new CustomEvent('reply', { detail: { messageId }, bubbles: true }),
155
+ )
156
+ }
157
+ })
158
+ })
159
+ </script>
@@ -0,0 +1 @@
1
+ export {}