@refraction-ui/astro 0.4.0 → 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 +20 -19
  246. package/src/index.ts +0 -94
@@ -0,0 +1,263 @@
1
+ ---
2
+ import {
3
+ createDataTable,
4
+ tableVariants,
5
+ headerVariants,
6
+ cellVariants,
7
+ rowVariants,
8
+ type ColumnDef,
9
+ type SortDirection,
10
+ } from '@refraction-ui/data-table'
11
+ import { cn } from '../shared/index.ts'
12
+
13
+ interface Props extends astroHTML.JSX.HTMLAttributes {
14
+ columns: ColumnDef[]
15
+ data: Record<string, unknown>[]
16
+ sortBy?: string
17
+ sortDir?: SortDirection
18
+ emptyMessage?: string
19
+ }
20
+
21
+ const {
22
+ columns,
23
+ data,
24
+ sortBy,
25
+ sortDir = 'asc',
26
+ emptyMessage = 'No data available',
27
+ class: className,
28
+ ...props
29
+ } = Astro.props
30
+
31
+ const api = createDataTable({ columns, data, sortBy, sortDir })
32
+ const sortedData = api.state.sortedData
33
+ const hasFilterable = columns.some((c: ColumnDef) => c.filterable)
34
+ ---
35
+
36
+ <div class="relative w-full overflow-auto" data-rfr-data-table data-rfr-data-table-columns={JSON.stringify(columns)} data-rfr-data-table-data={JSON.stringify(data)} {...props}>
37
+ <table class={cn(tableVariants(), className)} role="table">
38
+ <thead>
39
+ <tr role="row">
40
+ {columns.map((col: ColumnDef) => {
41
+ const hProps = api.getHeaderProps(col)
42
+ return (
43
+ <th
44
+ class={headerVariants({ sortable: col.sortable ? 'true' : 'false' })}
45
+ role={hProps.role as string}
46
+ aria-sort={hProps['aria-sort'] as any}
47
+ data-rfr-data-table-header={col.id}
48
+ data-sortable={col.sortable ? 'true' : undefined}
49
+ >
50
+ {col.header}
51
+ {col.sortable && sortBy === col.id && (
52
+ <span aria-hidden="true">{sortDir === 'asc' ? ' \u2191' : ' \u2193'}</span>
53
+ )}
54
+ </th>
55
+ )
56
+ })}
57
+ </tr>
58
+ {hasFilterable && (
59
+ <tr role="row" data-filter-row="true">
60
+ {columns.map((col: ColumnDef) => (
61
+ <th>
62
+ {col.filterable ? (
63
+ <input
64
+ type="text"
65
+ aria-label={`Filter ${col.header}`}
66
+ placeholder="Filter..."
67
+ data-rfr-data-table-filter={col.id}
68
+ class="w-full px-2 py-1 text-sm border rounded"
69
+ />
70
+ ) : null}
71
+ </th>
72
+ ))}
73
+ </tr>
74
+ )}
75
+ </thead>
76
+ <tbody data-rfr-data-table-body>
77
+ {sortedData.length === 0 ? (
78
+ <tr role="row">
79
+ <td
80
+ colspan={columns.length}
81
+ class="text-center p-4 text-muted-foreground"
82
+ role="cell"
83
+ >
84
+ {emptyMessage}
85
+ </td>
86
+ </tr>
87
+ ) : (
88
+ sortedData.map((row: Record<string, unknown>, rowIndex: number) => (
89
+ <tr
90
+ role="row"
91
+ class={rowVariants()}
92
+ data-row-index={rowIndex}
93
+ >
94
+ {columns.map((col: ColumnDef) => (
95
+ <td
96
+ role="cell"
97
+ class={cellVariants()}
98
+ data-column={col.id}
99
+ >
100
+ {String(col.accessor(row) ?? '')}
101
+ </td>
102
+ ))}
103
+ </tr>
104
+ ))
105
+ )}
106
+ </tbody>
107
+ </table>
108
+ </div>
109
+
110
+ <script>
111
+ function initDataTables() {
112
+ document.querySelectorAll<HTMLElement>('[data-rfr-data-table]').forEach((root) => {
113
+ if (root.hasAttribute('data-rfr-data-table-init')) return
114
+ root.setAttribute('data-rfr-data-table-init', '')
115
+
116
+ let columns: Array<{ id: string; header: string; sortable?: boolean; filterable?: boolean }>
117
+ let data: Array<Record<string, unknown>>
118
+
119
+ try {
120
+ columns = JSON.parse(root.getAttribute('data-rfr-data-table-columns') || '[]')
121
+ data = JSON.parse(root.getAttribute('data-rfr-data-table-data') || '[]')
122
+ } catch {
123
+ return
124
+ }
125
+
126
+ let sortBy: string | null = null
127
+ let sortDir: 'asc' | 'desc' = 'asc'
128
+ const filters: Record<string, string> = {}
129
+
130
+ const tbody = root.querySelector<HTMLElement>('[data-rfr-data-table-body]')
131
+ if (!tbody) return
132
+
133
+ // Build accessor functions from column IDs
134
+ function getCellValue(row: Record<string, unknown>, colId: string): unknown {
135
+ return row[colId]
136
+ }
137
+
138
+ function getFilteredSortedData(): Array<Record<string, unknown>> {
139
+ let result = [...data]
140
+
141
+ // Apply filters
142
+ for (const [colId, filterValue] of Object.entries(filters)) {
143
+ if (!filterValue) continue
144
+ const lowerFilter = filterValue.toLowerCase()
145
+ result = result.filter((row) => {
146
+ const cellValue = getCellValue(row, colId)
147
+ return String(cellValue ?? '').toLowerCase().includes(lowerFilter)
148
+ })
149
+ }
150
+
151
+ // Apply sort
152
+ if (sortBy) {
153
+ const sBy = sortBy
154
+ result.sort((a, b) => {
155
+ const aStr = String(getCellValue(a, sBy) ?? '')
156
+ const bStr = String(getCellValue(b, sBy) ?? '')
157
+ const cmp = aStr.localeCompare(bStr, undefined, { numeric: true })
158
+ return sortDir === 'asc' ? cmp : -cmp
159
+ })
160
+ }
161
+
162
+ return result
163
+ }
164
+
165
+ function renderRows() {
166
+ const sorted = getFilteredSortedData()
167
+ tbody!.innerHTML = ''
168
+
169
+ if (sorted.length === 0) {
170
+ const tr = document.createElement('tr')
171
+ tr.setAttribute('role', 'row')
172
+ const td = document.createElement('td')
173
+ td.setAttribute('colspan', String(columns.length))
174
+ td.setAttribute('role', 'cell')
175
+ td.className = 'text-center p-4 text-muted-foreground'
176
+ td.textContent = root.querySelector('td[role="cell"]')?.textContent || 'No data available'
177
+ tr.appendChild(td)
178
+ tbody!.appendChild(tr)
179
+ return
180
+ }
181
+
182
+ for (let i = 0; i < sorted.length; i++) {
183
+ const row = sorted[i]
184
+ const tr = document.createElement('tr')
185
+ tr.setAttribute('role', 'row')
186
+ tr.setAttribute('data-row-index', String(i))
187
+ // Apply row hover styles
188
+ tr.className = 'border-b transition-colors hover:bg-muted/50'
189
+
190
+ for (const col of columns) {
191
+ const td = document.createElement('td')
192
+ td.setAttribute('role', 'cell')
193
+ td.setAttribute('data-column', col.id)
194
+ td.className = 'p-2 align-middle border-b'
195
+ td.textContent = String(getCellValue(row, col.id) ?? '')
196
+ tr.appendChild(td)
197
+ }
198
+
199
+ tbody!.appendChild(tr)
200
+ }
201
+
202
+ root.dispatchEvent(
203
+ new CustomEvent('rfr-data-table-change', {
204
+ detail: { sortBy, sortDir, filters: { ...filters } },
205
+ bubbles: true,
206
+ }),
207
+ )
208
+ }
209
+
210
+ // Sort click handlers
211
+ const headers = root.querySelectorAll<HTMLElement>('[data-rfr-data-table-header]')
212
+ headers.forEach((th) => {
213
+ const colId = th.getAttribute('data-rfr-data-table-header')!
214
+ const isSortable = th.getAttribute('data-sortable') === 'true'
215
+ if (!isSortable) return
216
+
217
+ th.addEventListener('click', () => {
218
+ if (sortBy === colId) {
219
+ sortDir = sortDir === 'asc' ? 'desc' : 'asc'
220
+ } else {
221
+ sortBy = colId
222
+ sortDir = 'asc'
223
+ }
224
+
225
+ // Update sort indicators on all headers
226
+ headers.forEach((h) => {
227
+ const hColId = h.getAttribute('data-rfr-data-table-header')
228
+ const indicator = h.querySelector('span[aria-hidden]')
229
+ if (hColId === sortBy) {
230
+ h.setAttribute('aria-sort', sortDir === 'asc' ? 'ascending' : 'descending')
231
+ if (indicator) {
232
+ indicator.textContent = sortDir === 'asc' ? ' \u2191' : ' \u2193'
233
+ } else {
234
+ const span = document.createElement('span')
235
+ span.setAttribute('aria-hidden', 'true')
236
+ span.textContent = sortDir === 'asc' ? ' \u2191' : ' \u2193'
237
+ h.appendChild(span)
238
+ }
239
+ } else {
240
+ h.setAttribute('aria-sort', 'none')
241
+ if (indicator) indicator.remove()
242
+ }
243
+ })
244
+
245
+ renderRows()
246
+ })
247
+ })
248
+
249
+ // Filter input handlers
250
+ const filterInputs = root.querySelectorAll<HTMLInputElement>('[data-rfr-data-table-filter]')
251
+ filterInputs.forEach((input) => {
252
+ const colId = input.getAttribute('data-rfr-data-table-filter')!
253
+ input.addEventListener('input', () => {
254
+ filters[colId] = input.value
255
+ renderRows()
256
+ })
257
+ })
258
+ })
259
+ }
260
+
261
+ initDataTables()
262
+ document.addEventListener('astro:after-swap', initDataTables)
263
+ </script>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,398 @@
1
+ ---
2
+ import {
3
+ createDatePicker,
4
+ formatDate,
5
+ datePickerTriggerStyles,
6
+ datePickerDropdownStyles,
7
+ datePickerDayVariants,
8
+ datePickerTimeStyles,
9
+ datePickerTimeInputStyles,
10
+ type CalendarDay,
11
+ } from '@refraction-ui/date-picker'
12
+ import { cn } from '../shared/index.ts'
13
+
14
+ interface Props extends astroHTML.JSX.HTMLAttributes {
15
+ /** Selected date value (ISO string) */
16
+ value?: string
17
+ /** Minimum selectable date (ISO string) */
18
+ minDate?: string
19
+ /** Maximum selectable date (ISO string) */
20
+ maxDate?: string
21
+ /** Show time selection inputs */
22
+ showTime?: boolean
23
+ /** Date format string */
24
+ format?: string
25
+ /** Placeholder text */
26
+ placeholder?: string
27
+ /** Whether the picker is disabled */
28
+ disabled?: boolean
29
+ }
30
+
31
+ const {
32
+ value,
33
+ minDate,
34
+ maxDate,
35
+ showTime = false,
36
+ format,
37
+ placeholder = 'Select date...',
38
+ disabled = false,
39
+ class: className,
40
+ ...props
41
+ } = Astro.props
42
+
43
+ const parsedValue = value ? new Date(value) : undefined
44
+ const parsedMin = minDate ? new Date(minDate) : undefined
45
+ const parsedMax = maxDate ? new Date(maxDate) : undefined
46
+
47
+ const api = createDatePicker({
48
+ value: parsedValue,
49
+ minDate: parsedMin,
50
+ maxDate: parsedMax,
51
+ showTime,
52
+ format,
53
+ placeholder,
54
+ })
55
+
56
+ const displayValue = api.formatDisplay()
57
+ const DAY_HEADERS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
58
+ const currentMonth = api.state.currentMonth
59
+ const monthLabel = currentMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
60
+
61
+ function getDayState(day: CalendarDay): 'default' | 'selected' | 'today' | 'disabled' | 'outside' {
62
+ if (day.isDisabled) return 'disabled'
63
+ if (day.isSelected) return 'selected'
64
+ if (day.isToday) return 'today'
65
+ if (!day.isCurrentMonth) return 'outside'
66
+ return 'default'
67
+ }
68
+ ---
69
+
70
+ <div
71
+ data-rfr-datepicker
72
+ data-rfr-datepicker-value={value || ''}
73
+ data-rfr-datepicker-min={minDate || ''}
74
+ data-rfr-datepicker-max={maxDate || ''}
75
+ data-rfr-datepicker-show-time={showTime}
76
+ data-rfr-datepicker-format={format || (showTime ? 'YYYY-MM-DD HH:mm' : 'YYYY-MM-DD')}
77
+ data-rfr-datepicker-placeholder={placeholder}
78
+ data-rfr-datepicker-year={currentMonth.getFullYear()}
79
+ data-rfr-datepicker-month={currentMonth.getMonth()}
80
+ data-state="closed"
81
+ class={cn('relative inline-block', className)}
82
+ {...props}
83
+ >
84
+ <!-- Trigger button -->
85
+ <button
86
+ type="button"
87
+ data-rfr-datepicker-trigger
88
+ class={cn(datePickerTriggerStyles, disabled && 'opacity-50 cursor-not-allowed')}
89
+ aria-expanded="false"
90
+ aria-haspopup="dialog"
91
+ disabled={disabled}
92
+ >{displayValue}</button>
93
+
94
+ <!-- Dropdown (initially hidden) -->
95
+ <div
96
+ data-rfr-datepicker-dropdown
97
+ class={datePickerDropdownStyles}
98
+ role="dialog"
99
+ aria-modal="true"
100
+ hidden
101
+ >
102
+ <!-- Month navigation -->
103
+ <div class="flex items-center justify-between mb-3">
104
+ <button
105
+ type="button"
106
+ data-rfr-datepicker-prev
107
+ class="p-1 hover:bg-accent rounded cursor-pointer"
108
+ aria-label="Previous month"
109
+ >&lsaquo;</button>
110
+ <span data-rfr-datepicker-label class="text-sm font-medium">{monthLabel}</span>
111
+ <button
112
+ type="button"
113
+ data-rfr-datepicker-next
114
+ class="p-1 hover:bg-accent rounded cursor-pointer"
115
+ aria-label="Next month"
116
+ >&rsaquo;</button>
117
+ </div>
118
+
119
+ <!-- Calendar grid -->
120
+ <div class="grid grid-cols-7 gap-0 text-center" role="grid">
121
+ {DAY_HEADERS.map((d) => (
122
+ <div class="text-xs text-muted-foreground py-1 font-medium">{d}</div>
123
+ ))}
124
+ <div data-rfr-datepicker-grid class="contents">
125
+ {api.days.map((day) => {
126
+ const state = getDayState(day)
127
+ return (
128
+ <button
129
+ type="button"
130
+ class={datePickerDayVariants({ state })}
131
+ disabled={day.isDisabled}
132
+ data-rfr-datepicker-day
133
+ data-date={day.date.toISOString()}
134
+ aria-selected={day.isSelected}
135
+ aria-disabled={day.isDisabled}
136
+ >{day.date.getDate()}</button>
137
+ )
138
+ })}
139
+ </div>
140
+ </div>
141
+
142
+ <!-- Time inputs (optional) -->
143
+ {showTime && (
144
+ <div class={datePickerTimeStyles} data-rfr-datepicker-time>
145
+ <span class="text-sm text-muted-foreground">Time:</span>
146
+ <input
147
+ type="number"
148
+ min="0"
149
+ max="23"
150
+ value={parsedValue ? parsedValue.getHours() : 0}
151
+ class={datePickerTimeInputStyles}
152
+ aria-label="Hours"
153
+ data-rfr-datepicker-hours
154
+ />
155
+ <span class="text-muted-foreground">:</span>
156
+ <input
157
+ type="number"
158
+ min="0"
159
+ max="59"
160
+ value={parsedValue ? parsedValue.getMinutes() : 0}
161
+ class={datePickerTimeInputStyles}
162
+ aria-label="Minutes"
163
+ data-rfr-datepicker-minutes
164
+ />
165
+ </div>
166
+ )}
167
+ </div>
168
+ </div>
169
+
170
+ <script>
171
+ const MONTH_NAMES = [
172
+ 'January', 'February', 'March', 'April', 'May', 'June',
173
+ 'July', 'August', 'September', 'October', 'November', 'December',
174
+ ]
175
+ const DAY_VARIANT_DEFAULT = 'inline-flex h-8 w-8 items-center justify-center rounded-md text-sm cursor-pointer hover:bg-accent hover:text-accent-foreground'
176
+ const DAY_VARIANT_SELECTED = 'inline-flex h-8 w-8 items-center justify-center rounded-md text-sm cursor-pointer hover:bg-accent hover:text-accent-foreground bg-primary text-primary-foreground hover:bg-primary/90'
177
+ const DAY_VARIANT_TODAY = 'inline-flex h-8 w-8 items-center justify-center rounded-md text-sm cursor-pointer hover:bg-accent hover:text-accent-foreground border border-accent-foreground'
178
+ const DAY_VARIANT_DISABLED = 'inline-flex h-8 w-8 items-center justify-center rounded-md text-sm cursor-pointer hover:bg-accent hover:text-accent-foreground text-muted-foreground opacity-50 pointer-events-none'
179
+ const DAY_VARIANT_OUTSIDE = 'inline-flex h-8 w-8 items-center justify-center rounded-md text-sm cursor-pointer hover:bg-accent hover:text-accent-foreground text-muted-foreground opacity-30'
180
+
181
+ function isSameDay(a: Date, b: Date): boolean {
182
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()
183
+ }
184
+
185
+ function padZero(n: number): string {
186
+ return n < 10 ? `0${n}` : `${n}`
187
+ }
188
+
189
+ function formatDateStr(date: Date | null, fmt: string, showTime: boolean): string {
190
+ if (!date) return ''
191
+ let result = fmt
192
+ .replace('YYYY', String(date.getFullYear()))
193
+ .replace('MM', padZero(date.getMonth() + 1))
194
+ .replace('DD', padZero(date.getDate()))
195
+ if (showTime) {
196
+ result = result.replace('HH', padZero(date.getHours())).replace('mm', padZero(date.getMinutes()))
197
+ }
198
+ return result
199
+ }
200
+
201
+ function isDateDisabled(date: Date, minStr: string, maxStr: string): boolean {
202
+ if (minStr) {
203
+ const min = new Date(minStr)
204
+ const startOfMin = new Date(min.getFullYear(), min.getMonth(), min.getDate())
205
+ if (date < startOfMin) return true
206
+ }
207
+ if (maxStr) {
208
+ const max = new Date(maxStr)
209
+ const endOfMax = new Date(max.getFullYear(), max.getMonth(), max.getDate(), 23, 59, 59, 999)
210
+ if (date > endOfMax) return true
211
+ }
212
+ return false
213
+ }
214
+
215
+ function renderDatePickerGrid(root: HTMLElement) {
216
+ const year = parseInt(root.getAttribute('data-rfr-datepicker-year') || '0', 10)
217
+ const month = parseInt(root.getAttribute('data-rfr-datepicker-month') || '0', 10)
218
+ const valueStr = root.getAttribute('data-rfr-datepicker-value') || ''
219
+ const minStr = root.getAttribute('data-rfr-datepicker-min') || ''
220
+ const maxStr = root.getAttribute('data-rfr-datepicker-max') || ''
221
+ const selectedDate = valueStr ? new Date(valueStr) : null
222
+ const today = new Date()
223
+
224
+ const label = root.querySelector<HTMLElement>('[data-rfr-datepicker-label]')
225
+ if (label) label.textContent = `${MONTH_NAMES[month]} ${year}`
226
+
227
+ const grid = root.querySelector<HTMLElement>('[data-rfr-datepicker-grid]')
228
+ if (!grid) return
229
+
230
+ grid.innerHTML = ''
231
+ const first = new Date(year, month, 1)
232
+ const startDow = first.getDay()
233
+ const gridStart = new Date(year, month, 1 - startDow)
234
+
235
+ for (let i = 0; i < 42; i++) {
236
+ const date = new Date(gridStart.getFullYear(), gridStart.getMonth(), gridStart.getDate() + i)
237
+ const isToday = isSameDay(date, today)
238
+ const isSelected = selectedDate ? isSameDay(date, selectedDate) : false
239
+ const isCurrentMonth = date.getMonth() === month
240
+ const isDisabled = isDateDisabled(date, minStr, maxStr)
241
+
242
+ let variant = DAY_VARIANT_DEFAULT
243
+ if (isDisabled) variant = DAY_VARIANT_DISABLED
244
+ else if (isSelected) variant = DAY_VARIANT_SELECTED
245
+ else if (isToday) variant = DAY_VARIANT_TODAY
246
+ else if (!isCurrentMonth) variant = DAY_VARIANT_OUTSIDE
247
+
248
+ const btn = document.createElement('button')
249
+ btn.type = 'button'
250
+ btn.className = variant
251
+ btn.disabled = isDisabled
252
+ btn.setAttribute('data-rfr-datepicker-day', '')
253
+ btn.setAttribute('data-date', date.toISOString())
254
+ btn.setAttribute('aria-selected', String(isSelected))
255
+ btn.setAttribute('aria-disabled', String(isDisabled))
256
+ btn.textContent = String(date.getDate())
257
+ grid.appendChild(btn)
258
+ }
259
+ }
260
+
261
+ function initDatePickers() {
262
+ document.querySelectorAll<HTMLElement>('[data-rfr-datepicker]').forEach((root) => {
263
+ if (root.hasAttribute('data-rfr-datepicker-init')) return
264
+ root.setAttribute('data-rfr-datepicker-init', '')
265
+
266
+ const trigger = root.querySelector<HTMLButtonElement>('[data-rfr-datepicker-trigger]')
267
+ const dropdown = root.querySelector<HTMLElement>('[data-rfr-datepicker-dropdown]')
268
+ const showTime = root.getAttribute('data-rfr-datepicker-show-time') === 'true'
269
+ const fmt = root.getAttribute('data-rfr-datepicker-format') || 'YYYY-MM-DD'
270
+ const placeholder = root.getAttribute('data-rfr-datepicker-placeholder') || 'Select date...'
271
+
272
+ let isOpen = false
273
+
274
+ function setOpen(open: boolean) {
275
+ isOpen = open
276
+ root.setAttribute('data-state', open ? 'open' : 'closed')
277
+ trigger?.setAttribute('aria-expanded', String(open))
278
+ if (dropdown) {
279
+ if (open) {
280
+ dropdown.removeAttribute('hidden')
281
+ renderDatePickerGrid(root)
282
+ } else {
283
+ dropdown.setAttribute('hidden', '')
284
+ }
285
+ }
286
+ }
287
+
288
+ function updateTriggerLabel() {
289
+ const valueStr = root.getAttribute('data-rfr-datepicker-value') || ''
290
+ const date = valueStr ? new Date(valueStr) : null
291
+ if (trigger) {
292
+ trigger.textContent = date ? formatDateStr(date, fmt, showTime) : placeholder
293
+ }
294
+ }
295
+
296
+ // Toggle dropdown
297
+ trigger?.addEventListener('click', () => {
298
+ if (trigger.disabled) return
299
+ setOpen(!isOpen)
300
+ })
301
+
302
+ // Prev month
303
+ root.querySelector('[data-rfr-datepicker-prev]')?.addEventListener('click', () => {
304
+ const year = parseInt(root.getAttribute('data-rfr-datepicker-year') || '0', 10)
305
+ const month = parseInt(root.getAttribute('data-rfr-datepicker-month') || '0', 10)
306
+ const prev = new Date(year, month - 1, 1)
307
+ root.setAttribute('data-rfr-datepicker-year', String(prev.getFullYear()))
308
+ root.setAttribute('data-rfr-datepicker-month', String(prev.getMonth()))
309
+ renderDatePickerGrid(root)
310
+ })
311
+
312
+ // Next month
313
+ root.querySelector('[data-rfr-datepicker-next]')?.addEventListener('click', () => {
314
+ const year = parseInt(root.getAttribute('data-rfr-datepicker-year') || '0', 10)
315
+ const month = parseInt(root.getAttribute('data-rfr-datepicker-month') || '0', 10)
316
+ const next = new Date(year, month + 1, 1)
317
+ root.setAttribute('data-rfr-datepicker-year', String(next.getFullYear()))
318
+ root.setAttribute('data-rfr-datepicker-month', String(next.getMonth()))
319
+ renderDatePickerGrid(root)
320
+ })
321
+
322
+ // Day click (delegated on grid's parent)
323
+ const gridParent = root.querySelector('[role="grid"]')
324
+ gridParent?.addEventListener('click', (e) => {
325
+ const btn = (e.target as HTMLElement).closest<HTMLButtonElement>('[data-rfr-datepicker-day]')
326
+ if (!btn || btn.disabled) return
327
+ const dateStr = btn.getAttribute('data-date')
328
+ if (!dateStr) return
329
+
330
+ const selectedDate = new Date(dateStr)
331
+ // Preserve existing time if showTime
332
+ if (showTime) {
333
+ const hoursInput = root.querySelector<HTMLInputElement>('[data-rfr-datepicker-hours]')
334
+ const minutesInput = root.querySelector<HTMLInputElement>('[data-rfr-datepicker-minutes]')
335
+ if (hoursInput) selectedDate.setHours(parseInt(hoursInput.value, 10) || 0)
336
+ if (minutesInput) selectedDate.setMinutes(parseInt(minutesInput.value, 10) || 0)
337
+ }
338
+
339
+ root.setAttribute('data-rfr-datepicker-value', selectedDate.toISOString())
340
+ updateTriggerLabel()
341
+ renderDatePickerGrid(root)
342
+
343
+ if (!showTime) {
344
+ setOpen(false)
345
+ }
346
+
347
+ root.dispatchEvent(new CustomEvent('rfr-date-change', {
348
+ detail: { date: selectedDate, iso: selectedDate.toISOString() },
349
+ bubbles: true,
350
+ }))
351
+ })
352
+
353
+ // Time input changes
354
+ const hoursInput = root.querySelector<HTMLInputElement>('[data-rfr-datepicker-hours]')
355
+ const minutesInput = root.querySelector<HTMLInputElement>('[data-rfr-datepicker-minutes]')
356
+
357
+ function handleTimeChange() {
358
+ const valueStr = root.getAttribute('data-rfr-datepicker-value') || ''
359
+ const date = valueStr ? new Date(valueStr) : new Date()
360
+ if (hoursInput) {
361
+ const h = parseInt(hoursInput.value, 10)
362
+ if (!isNaN(h) && h >= 0 && h <= 23) date.setHours(h)
363
+ }
364
+ if (minutesInput) {
365
+ const m = parseInt(minutesInput.value, 10)
366
+ if (!isNaN(m) && m >= 0 && m <= 59) date.setMinutes(m)
367
+ }
368
+ root.setAttribute('data-rfr-datepicker-value', date.toISOString())
369
+ updateTriggerLabel()
370
+ root.dispatchEvent(new CustomEvent('rfr-date-change', {
371
+ detail: { date, iso: date.toISOString() },
372
+ bubbles: true,
373
+ }))
374
+ }
375
+
376
+ hoursInput?.addEventListener('change', handleTimeChange)
377
+ minutesInput?.addEventListener('change', handleTimeChange)
378
+
379
+ // Close on outside click
380
+ document.addEventListener('mousedown', (e) => {
381
+ if (isOpen && !root.contains(e.target as Node)) {
382
+ setOpen(false)
383
+ }
384
+ })
385
+
386
+ // Close on Escape
387
+ document.addEventListener('keydown', (e) => {
388
+ if (isOpen && e.key === 'Escape') {
389
+ e.preventDefault()
390
+ setOpen(false)
391
+ }
392
+ })
393
+ })
394
+ }
395
+
396
+ initDatePickers()
397
+ document.addEventListener('astro:after-swap', initDatePickers)
398
+ </script>
@@ -0,0 +1 @@
1
+ export {}