@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,306 @@
1
+ ---
2
+ import {
3
+ createFileUpload,
4
+ formatFileSize,
5
+ fileUploadDropZoneVariants,
6
+ fileUploadFileListStyles,
7
+ fileUploadFileItemStyles,
8
+ fileUploadProgressStyles,
9
+ fileUploadProgressBarStyles,
10
+ fileUploadRemoveButtonStyles,
11
+ } from '@refraction-ui/file-upload'
12
+ import { cn } from '../shared/index.ts'
13
+
14
+ interface Props extends astroHTML.JSX.HTMLAttributes {
15
+ accept?: string
16
+ maxSize?: number
17
+ maxFiles?: number
18
+ multiple?: boolean
19
+ }
20
+
21
+ const {
22
+ accept,
23
+ maxSize,
24
+ maxFiles,
25
+ multiple = false,
26
+ class: className,
27
+ ...props
28
+ } = Astro.props
29
+
30
+ const api = createFileUpload({ accept, maxSize, maxFiles, multiple })
31
+ ---
32
+
33
+ <div
34
+ data-rfr-file-upload
35
+ data-rfr-file-upload-accept={accept}
36
+ data-rfr-file-upload-max-size={maxSize ? String(maxSize) : undefined}
37
+ data-rfr-file-upload-max-files={maxFiles ? String(maxFiles) : undefined}
38
+ data-rfr-file-upload-multiple={multiple ? 'true' : undefined}
39
+ class={className}
40
+ {...props}
41
+ >
42
+ {/* Hidden file input */}
43
+ <input
44
+ data-rfr-file-upload-input
45
+ type="file"
46
+ accept={accept}
47
+ multiple={multiple}
48
+ aria-hidden="true"
49
+ tabindex="-1"
50
+ style="display: none;"
51
+ />
52
+
53
+ {/* Drop zone */}
54
+ <div
55
+ data-rfr-file-upload-dropzone
56
+ role="button"
57
+ aria-label="Drop files here or click to upload"
58
+ tabindex="0"
59
+ class={fileUploadDropZoneVariants({ state: 'idle' })}
60
+ >
61
+ <slot>
62
+ <div class="text-2xl mb-2">&#x1F4C1;</div>
63
+ <p class="text-sm text-muted-foreground">
64
+ Drag & drop files here, or click to select
65
+ </p>
66
+ {accept && (
67
+ <p class="text-xs text-muted-foreground mt-1">
68
+ Accepted: {accept}
69
+ </p>
70
+ )}
71
+ {maxSize && (
72
+ <p class="text-xs text-muted-foreground">
73
+ Max size: {formatFileSize(maxSize)}
74
+ </p>
75
+ )}
76
+ </slot>
77
+ </div>
78
+
79
+ {/* File list (populated client-side) */}
80
+ <div data-rfr-file-upload-list class={fileUploadFileListStyles} hidden></div>
81
+ </div>
82
+
83
+ <script>
84
+ function initFileUploads() {
85
+ document.querySelectorAll<HTMLElement>('[data-rfr-file-upload]').forEach((root) => {
86
+ if (root.hasAttribute('data-rfr-file-upload-init')) return
87
+ root.setAttribute('data-rfr-file-upload-init', '')
88
+
89
+ const input = root.querySelector<HTMLInputElement>('[data-rfr-file-upload-input]')
90
+ const dropzone = root.querySelector<HTMLElement>('[data-rfr-file-upload-dropzone]')
91
+ const fileListEl = root.querySelector<HTMLElement>('[data-rfr-file-upload-list]')
92
+ if (!input || !dropzone || !fileListEl) return
93
+
94
+ const accept = root.getAttribute('data-rfr-file-upload-accept') || ''
95
+ const maxSize = parseInt(root.getAttribute('data-rfr-file-upload-max-size') || '0', 10) || 0
96
+ const maxFiles = parseInt(root.getAttribute('data-rfr-file-upload-max-files') || '0', 10) || 0
97
+ const multiple = root.getAttribute('data-rfr-file-upload-multiple') === 'true'
98
+
99
+ interface FileItem {
100
+ id: string
101
+ name: string
102
+ size: number
103
+ type: string
104
+ }
105
+
106
+ let files: FileItem[] = []
107
+ let idCounter = 0
108
+
109
+ function formatSize(bytes: number): string {
110
+ if (bytes === 0) return '0 B'
111
+ const units = ['B', 'KB', 'MB', 'GB']
112
+ const i = Math.floor(Math.log(bytes) / Math.log(1024))
113
+ const size = bytes / Math.pow(1024, i)
114
+ return `${size.toFixed(i === 0 ? 0 : 1)} ${units[i]}`
115
+ }
116
+
117
+ function matchesAccept(fileType: string, fileName: string): boolean {
118
+ if (!accept) return true
119
+ const types = accept.split(',').map((t: string) => t.trim())
120
+ for (const a of types) {
121
+ if (a.startsWith('.')) {
122
+ if (fileName.toLowerCase().endsWith(a.toLowerCase())) return true
123
+ } else if (a.endsWith('/*')) {
124
+ if (fileType.startsWith(a.slice(0, -2))) return true
125
+ } else {
126
+ if (fileType === a) return true
127
+ }
128
+ }
129
+ return false
130
+ }
131
+
132
+ function addFiles(incoming: Array<{ name: string; size: number; type: string }>) {
133
+ const errors: Array<{ file: string; message: string }> = []
134
+ const validFiles: FileItem[] = []
135
+
136
+ let toProcess = multiple ? incoming : [incoming[0]]
137
+ const available = maxFiles ? maxFiles - files.length : Infinity
138
+
139
+ for (const file of toProcess) {
140
+ if (validFiles.length >= available) {
141
+ errors.push({ file: file.name, message: `Maximum of ${maxFiles} files allowed` })
142
+ continue
143
+ }
144
+ if (accept && !matchesAccept(file.type, file.name)) {
145
+ errors.push({ file: file.name, message: `File type not accepted` })
146
+ continue
147
+ }
148
+ if (maxSize && file.size > maxSize) {
149
+ errors.push({ file: file.name, message: `File too large (max ${formatSize(maxSize)})` })
150
+ continue
151
+ }
152
+ validFiles.push({
153
+ id: `rfr-file-${++idCounter}`,
154
+ name: file.name,
155
+ size: file.size,
156
+ type: file.type,
157
+ })
158
+ }
159
+
160
+ if (errors.length > 0) {
161
+ root.dispatchEvent(
162
+ new CustomEvent('rfr-file-upload-error', {
163
+ detail: { errors },
164
+ bubbles: true,
165
+ }),
166
+ )
167
+ }
168
+
169
+ if (validFiles.length > 0) {
170
+ if (!multiple) {
171
+ files = validFiles.slice(0, 1)
172
+ } else {
173
+ files = [...files, ...validFiles]
174
+ }
175
+ renderFileList()
176
+ root.dispatchEvent(
177
+ new CustomEvent('rfr-file-upload-change', {
178
+ detail: { files: [...files] },
179
+ bubbles: true,
180
+ }),
181
+ )
182
+ }
183
+ }
184
+
185
+ function removeFile(id: string) {
186
+ files = files.filter((f) => f.id !== id)
187
+ renderFileList()
188
+ root.dispatchEvent(
189
+ new CustomEvent('rfr-file-upload-change', {
190
+ detail: { files: [...files] },
191
+ bubbles: true,
192
+ }),
193
+ )
194
+ }
195
+
196
+ function renderFileList() {
197
+ if (files.length === 0) {
198
+ fileListEl!.hidden = true
199
+ fileListEl!.innerHTML = ''
200
+ return
201
+ }
202
+
203
+ fileListEl!.hidden = false
204
+ fileListEl!.innerHTML = ''
205
+
206
+ for (const file of files) {
207
+ const item = document.createElement('div')
208
+ item.className = 'flex items-center gap-3 rounded-md border p-3 text-sm'
209
+
210
+ const info = document.createElement('div')
211
+ info.className = 'flex-1 min-w-0'
212
+
213
+ const nameEl = document.createElement('div')
214
+ nameEl.className = 'font-medium truncate'
215
+ nameEl.textContent = file.name
216
+
217
+ const sizeEl = document.createElement('div')
218
+ sizeEl.className = 'text-xs text-muted-foreground'
219
+ sizeEl.textContent = formatSize(file.size)
220
+
221
+ info.appendChild(nameEl)
222
+ info.appendChild(sizeEl)
223
+ item.appendChild(info)
224
+
225
+ const removeBtn = document.createElement('button')
226
+ removeBtn.type = 'button'
227
+ removeBtn.className = 'ml-auto text-muted-foreground hover:text-foreground cursor-pointer'
228
+ removeBtn.setAttribute('aria-label', `Remove ${file.name}`)
229
+ removeBtn.textContent = '\u00D7'
230
+ removeBtn.addEventListener('click', (e) => {
231
+ e.stopPropagation()
232
+ removeFile(file.id)
233
+ })
234
+
235
+ item.appendChild(removeBtn)
236
+ fileListEl!.appendChild(item)
237
+ }
238
+ }
239
+
240
+ // Drop zone click -> open file dialog
241
+ dropzone.addEventListener('click', () => input!.click())
242
+ dropzone.addEventListener('keydown', (e) => {
243
+ if (e.key === 'Enter' || e.key === ' ') {
244
+ e.preventDefault()
245
+ input!.click()
246
+ }
247
+ })
248
+
249
+ // Drag events
250
+ const idleClass = 'border-muted-foreground/25'
251
+ const dragClass = 'border-primary'
252
+ const dragBgClass = 'bg-primary/5'
253
+
254
+ dropzone.addEventListener('dragenter', (e) => {
255
+ e.preventDefault()
256
+ e.stopPropagation()
257
+ dropzone.classList.add(dragClass, dragBgClass)
258
+ dropzone.classList.remove(idleClass)
259
+ })
260
+
261
+ dropzone.addEventListener('dragleave', (e) => {
262
+ e.preventDefault()
263
+ e.stopPropagation()
264
+ dropzone.classList.remove(dragClass, dragBgClass)
265
+ dropzone.classList.add(idleClass)
266
+ })
267
+
268
+ dropzone.addEventListener('dragover', (e) => {
269
+ e.preventDefault()
270
+ e.stopPropagation()
271
+ })
272
+
273
+ dropzone.addEventListener('drop', (e) => {
274
+ e.preventDefault()
275
+ e.stopPropagation()
276
+ dropzone.classList.remove(dragClass, dragBgClass)
277
+ dropzone.classList.add(idleClass)
278
+
279
+ const droppedFiles = Array.from(e.dataTransfer?.files || []).map((f) => ({
280
+ name: f.name,
281
+ size: f.size,
282
+ type: f.type,
283
+ }))
284
+ if (droppedFiles.length > 0) {
285
+ addFiles(droppedFiles)
286
+ }
287
+ })
288
+
289
+ // File input change
290
+ input.addEventListener('change', () => {
291
+ const selected = Array.from(input!.files || []).map((f) => ({
292
+ name: f.name,
293
+ size: f.size,
294
+ type: f.type,
295
+ }))
296
+ if (selected.length > 0) {
297
+ addFiles(selected)
298
+ }
299
+ input!.value = ''
300
+ })
301
+ })
302
+ }
303
+
304
+ initFileUploads()
305
+ document.addEventListener('astro:after-swap', initFileUploads)
306
+ </script>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,61 @@
1
+ ---
2
+ import {
3
+ createFooter,
4
+ footerVariants,
5
+ type FooterColumn,
6
+ type SocialLink,
7
+ } from '@refraction-ui/footer'
8
+ import { cn } from '../shared/index.ts'
9
+
10
+ interface Props extends astroHTML.JSX.HTMLAttributes {
11
+ copyright?: string
12
+ socialLinks?: SocialLink[]
13
+ columns?: FooterColumn[]
14
+ }
15
+
16
+ const { copyright, socialLinks = [], columns = [], class: className, ...props } = Astro.props
17
+ const api = createFooter({ copyright, socialLinks, columns })
18
+ const classes = cn(footerVariants(), className)
19
+ ---
20
+
21
+ <footer class={classes} {...api.ariaProps} {...props}>
22
+ <div class="mx-auto max-w-7xl px-4">
23
+ {columns.length > 0 && (
24
+ <div class="grid grid-cols-2 gap-8 md:grid-cols-4">
25
+ {columns.map((column) => (
26
+ <div>
27
+ <h3 class="mb-3 text-sm font-semibold">{column.title}</h3>
28
+ <ul class="space-y-2">
29
+ {column.links.map((link) => (
30
+ <li>
31
+ <a
32
+ href={link.href}
33
+ class="text-sm text-muted-foreground transition-colors hover:text-foreground"
34
+ >
35
+ {link.label}
36
+ </a>
37
+ </li>
38
+ ))}
39
+ </ul>
40
+ </div>
41
+ ))}
42
+ </div>
43
+ )}
44
+ <div class="flex items-center justify-between pt-8">
45
+ <p class="text-sm text-muted-foreground">{api.copyrightText}</p>
46
+ {socialLinks.length > 0 && (
47
+ <div class="flex items-center gap-4">
48
+ {socialLinks.map((link) => (
49
+ <a
50
+ href={link.href}
51
+ class="text-muted-foreground transition-colors hover:text-foreground"
52
+ aria-label={link.label}
53
+ >
54
+ {link.icon ?? link.label}
55
+ </a>
56
+ ))}
57
+ </div>
58
+ )}
59
+ </div>
60
+ </div>
61
+ </footer>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,4 @@
1
+ ---
2
+ // Astro component
3
+ ---
4
+ <div></div>
@@ -0,0 +1 @@
1
+ export * from './astro-icon-system';
@@ -0,0 +1,169 @@
1
+ ---
2
+ import {
3
+ createInlineEditor,
4
+ editorVariants,
5
+ toolbarVariants,
6
+ previewVariants,
7
+ } from '@refraction-ui/inline-editor'
8
+ import { cn } from '../shared/index.ts'
9
+
10
+ interface Props extends astroHTML.JSX.HTMLAttributes {
11
+ value: string
12
+ }
13
+
14
+ const { value, class: className, ...props } = Astro.props
15
+
16
+ const api = createInlineEditor({ value })
17
+ ---
18
+
19
+ <div
20
+ data-rfr-inline-editor
21
+ data-rfr-inline-editor-value={value}
22
+ class={cn('contents', className)}
23
+ {...props}
24
+ >
25
+ {/* View mode (shown by default) */}
26
+ <div
27
+ data-rfr-inline-editor-view
28
+ class={editorVariants({ state: 'viewing' })}
29
+ role="button"
30
+ tabindex="0"
31
+ aria-label="Click to edit"
32
+ >
33
+ <div class={previewVariants()} data-rfr-inline-editor-display>
34
+ {value}
35
+ </div>
36
+ </div>
37
+
38
+ {/* Edit mode (hidden by default) */}
39
+ <div
40
+ data-rfr-inline-editor-edit
41
+ class={editorVariants({ state: 'editing' })}
42
+ hidden
43
+ >
44
+ {/* Toolbar */}
45
+ <div class={toolbarVariants()} role="toolbar" aria-label="Formatting toolbar">
46
+ {api.toolbarActions.map((action) => (
47
+ <button
48
+ type="button"
49
+ data-rfr-inline-editor-toolbar-action={action.syntax}
50
+ aria-label={action.name}
51
+ class="px-2 py-1 rounded text-xs hover:bg-muted transition-colors"
52
+ >
53
+ {action.name}
54
+ </button>
55
+ ))}
56
+ </div>
57
+
58
+ {/* Editor area: side-by-side textarea + preview */}
59
+ <div class="flex gap-2 p-2">
60
+ <textarea
61
+ data-rfr-inline-editor-textarea
62
+ class="flex-1 min-h-[100px] resize-y border rounded p-2"
63
+ aria-label="Editor content"
64
+ >{value}</textarea>
65
+ <div
66
+ class={cn('flex-1', previewVariants())}
67
+ aria-label="Preview"
68
+ data-rfr-inline-editor-preview
69
+ >
70
+ {value}
71
+ </div>
72
+ </div>
73
+
74
+ {/* Action buttons */}
75
+ <div class="flex justify-end gap-2 p-2 border-t">
76
+ <button type="button" data-rfr-inline-editor-cancel>Cancel</button>
77
+ <button type="button" data-rfr-inline-editor-save>Save</button>
78
+ </div>
79
+ </div>
80
+ </div>
81
+
82
+ <script>
83
+ function initInlineEditors() {
84
+ document.querySelectorAll<HTMLElement>('[data-rfr-inline-editor]').forEach((root) => {
85
+ if (root.hasAttribute('data-rfr-inline-editor-init')) return
86
+ root.setAttribute('data-rfr-inline-editor-init', '')
87
+
88
+ const viewEl = root.querySelector<HTMLElement>('[data-rfr-inline-editor-view]')
89
+ const editEl = root.querySelector<HTMLElement>('[data-rfr-inline-editor-edit]')
90
+ const textarea = root.querySelector<HTMLTextAreaElement>('[data-rfr-inline-editor-textarea]')
91
+ const preview = root.querySelector<HTMLElement>('[data-rfr-inline-editor-preview]')
92
+ const display = root.querySelector<HTMLElement>('[data-rfr-inline-editor-display]')
93
+ const cancelBtn = root.querySelector<HTMLButtonElement>('[data-rfr-inline-editor-cancel]')
94
+ const saveBtn = root.querySelector<HTMLButtonElement>('[data-rfr-inline-editor-save]')
95
+ const toolbarBtns = root.querySelectorAll<HTMLButtonElement>('[data-rfr-inline-editor-toolbar-action]')
96
+
97
+ if (!viewEl || !editEl || !textarea || !preview || !display) return
98
+
99
+ let originalValue = root.getAttribute('data-rfr-inline-editor-value') || ''
100
+
101
+ function enterEditMode() {
102
+ viewEl!.hidden = true
103
+ editEl!.removeAttribute('hidden')
104
+ textarea!.value = originalValue
105
+ preview!.textContent = originalValue
106
+ textarea!.focus()
107
+ }
108
+
109
+ function exitEditMode() {
110
+ editEl!.hidden = true
111
+ viewEl!.removeAttribute('hidden')
112
+ }
113
+
114
+ // Click or keyboard to enter edit mode
115
+ viewEl.addEventListener('click', enterEditMode)
116
+ viewEl.addEventListener('keydown', (e) => {
117
+ if (e.key === 'Enter' || e.key === ' ') {
118
+ e.preventDefault()
119
+ enterEditMode()
120
+ }
121
+ })
122
+
123
+ // Live preview as user types
124
+ textarea.addEventListener('input', () => {
125
+ preview!.textContent = textarea!.value
126
+ })
127
+
128
+ // Toolbar buttons insert syntax at cursor
129
+ toolbarBtns.forEach((btn) => {
130
+ btn.addEventListener('click', () => {
131
+ const syntax = btn.getAttribute('data-rfr-inline-editor-toolbar-action') || ''
132
+ const start = textarea!.selectionStart
133
+ const end = textarea!.selectionEnd
134
+ const val = textarea!.value
135
+ textarea!.value = val.substring(0, start) + syntax + val.substring(end)
136
+ textarea!.selectionStart = textarea!.selectionEnd = start + syntax.length
137
+ textarea!.focus()
138
+ preview!.textContent = textarea!.value
139
+ })
140
+ })
141
+
142
+ // Cancel button
143
+ cancelBtn?.addEventListener('click', () => {
144
+ exitEditMode()
145
+ root.dispatchEvent(
146
+ new CustomEvent('rfr-inline-editor-cancel', { bubbles: true }),
147
+ )
148
+ })
149
+
150
+ // Save button
151
+ saveBtn?.addEventListener('click', () => {
152
+ const newValue = textarea!.value
153
+ originalValue = newValue
154
+ display!.textContent = newValue
155
+ root.setAttribute('data-rfr-inline-editor-value', newValue)
156
+ exitEditMode()
157
+ root.dispatchEvent(
158
+ new CustomEvent('rfr-inline-editor-save', {
159
+ detail: { value: newValue },
160
+ bubbles: true,
161
+ }),
162
+ )
163
+ })
164
+ })
165
+ }
166
+
167
+ initInlineEditors()
168
+ document.addEventListener('astro:after-swap', initInlineEditors)
169
+ </script>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,49 @@
1
+ ---
2
+ import {
3
+ createInput,
4
+ inputVariants,
5
+ type InputType,
6
+ } from '@refraction-ui/input'
7
+ import { cn } from '../shared/index.ts'
8
+
9
+ interface Props extends astroHTML.JSX.HTMLAttributes {
10
+ type?: string
11
+ size?: 'sm' | 'default' | 'lg'
12
+ disabled?: boolean
13
+ readonly?: boolean
14
+ required?: boolean
15
+ 'aria-invalid'?: boolean
16
+ }
17
+
18
+ const {
19
+ type = 'text',
20
+ size,
21
+ class: className,
22
+ disabled,
23
+ readonly,
24
+ required,
25
+ 'aria-invalid': ariaInvalid,
26
+ ...props
27
+ } = Astro.props
28
+
29
+ const api = createInput({
30
+ type: type as InputType,
31
+ disabled,
32
+ readOnly: readonly,
33
+ required,
34
+ 'aria-invalid': ariaInvalid === true ? true : undefined,
35
+ })
36
+
37
+ const classes = cn(inputVariants({ size }), className)
38
+ ---
39
+
40
+ <input
41
+ type={type}
42
+ class={classes}
43
+ disabled={disabled}
44
+ readonly={readonly}
45
+ required={required}
46
+ {...api.ariaProps}
47
+ {...api.dataAttributes}
48
+ {...props}
49
+ />
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,29 @@
1
+ ---
2
+ import {
3
+ createInputGroup,
4
+ inputGroupVariants,
5
+ type InputGroupOrientation,
6
+ } from '@refraction-ui/input-group'
7
+ import { cn } from '../shared/index.ts'
8
+
9
+ interface Props extends astroHTML.JSX.HTMLAttributes {
10
+ orientation?: InputGroupOrientation
11
+ }
12
+
13
+ const { orientation = 'horizontal', class: className, ...props } = Astro.props
14
+ const api = createInputGroup({
15
+ orientation,
16
+ id: props.id,
17
+ 'aria-label': props['aria-label'],
18
+ 'aria-labelledby': props['aria-labelledby'],
19
+ })
20
+ ---
21
+
22
+ <div
23
+ class={cn(inputGroupVariants({ orientation }), className)}
24
+ {...api.ariaProps}
25
+ {...api.dataAttributes}
26
+ {...props}
27
+ >
28
+ <slot />
29
+ </div>
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { inputGroupAddonVariants, type InputGroupOrientation } from '@refraction-ui/input-group'
3
+ import { cn } from '../shared/index.ts'
4
+
5
+ interface Props extends astroHTML.JSX.HTMLAttributes {
6
+ orientation?: InputGroupOrientation
7
+ }
8
+
9
+ const { orientation = 'horizontal', class: className, ...props } = Astro.props
10
+ ---
11
+
12
+ <div class={cn(inputGroupAddonVariants({ orientation }), className)} {...props}>
13
+ <slot />
14
+ </div>
@@ -0,0 +1,19 @@
1
+ ---
2
+ import { inputGroupButtonVariants, type InputGroupOrientation } from '@refraction-ui/input-group'
3
+ import { cn } from '../shared/index.ts'
4
+
5
+ interface Props extends astroHTML.JSX.HTMLAttributes {
6
+ orientation?: InputGroupOrientation
7
+ type?: string
8
+ }
9
+
10
+ const { orientation = 'horizontal', type, class: className, ...props } = Astro.props
11
+ ---
12
+
13
+ <button
14
+ type={(type ?? 'button') as any}
15
+ class={cn(inputGroupButtonVariants({ orientation }), className)}
16
+ {...props}
17
+ >
18
+ <slot />
19
+ </button>
@@ -0,0 +1,11 @@
1
+ ---
2
+ import { cn } from '../shared/index.ts'
3
+
4
+ interface Props extends astroHTML.JSX.HTMLAttributes {}
5
+
6
+ const { class: className, ...props } = Astro.props
7
+ ---
8
+
9
+ <span class={cn('flex items-center px-3 text-sm text-muted-foreground', className)} {...props}>
10
+ <slot />
11
+ </span>
@@ -0,0 +1 @@
1
+ export {}