@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,371 @@
1
+ ---
2
+ import {
3
+ createDiffViewer,
4
+ diffViewerVariants,
5
+ sidebarVariants,
6
+ sidebarItemVariants,
7
+ tabBarVariants,
8
+ tabVariants,
9
+ statusBarVariants,
10
+ type DiffFile,
11
+ type DiffViewerTheme,
12
+ type DiffViewMode,
13
+ } from '@refraction-ui/diff-viewer'
14
+ import { cn } from '../shared/index.ts'
15
+
16
+ interface Props extends astroHTML.JSX.HTMLAttributes {
17
+ files: DiffFile[]
18
+ original?: string
19
+ modified?: string
20
+ language?: string
21
+ theme?: DiffViewerTheme
22
+ viewMode?: DiffViewMode
23
+ showSidebar?: boolean
24
+ showTabs?: boolean
25
+ showStatusBar?: boolean
26
+ sidebarWidth?: number
27
+ activeFileIndex?: number
28
+ statusBarTitle?: string
29
+ statusBarStatus?: string
30
+ }
31
+
32
+ const {
33
+ files,
34
+ original = '',
35
+ modified = '',
36
+ language,
37
+ theme = 'dark',
38
+ viewMode = 'side-by-side',
39
+ showSidebar = true,
40
+ showTabs = true,
41
+ showStatusBar = true,
42
+ sidebarWidth = 220,
43
+ activeFileIndex = 0,
44
+ statusBarTitle,
45
+ statusBarStatus,
46
+ class: className,
47
+ ...props
48
+ } = Astro.props
49
+
50
+ const api = createDiffViewer({
51
+ files,
52
+ activeFileIndex,
53
+ viewMode,
54
+ theme,
55
+ language,
56
+ sidebarOpen: showSidebar,
57
+ })
58
+
59
+ const activeFile = files[activeFileIndex]
60
+
61
+ // Compute a simple line-by-line diff for static rendering
62
+ function computeDiffLines(original: string, modified: string) {
63
+ const origLines = original.split('\n')
64
+ const modLines = modified.split('\n')
65
+ const lines: Array<{ type: 'same' | 'add' | 'del'; origNum?: number; modNum?: number; content: string }> = []
66
+
67
+ // Simple LCS-based diff
68
+ const m = origLines.length
69
+ const n = modLines.length
70
+ const dp: number[][] = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0))
71
+ for (let i = 1; i <= m; i++) {
72
+ for (let j = 1; j <= n; j++) {
73
+ dp[i][j] = origLines[i - 1] === modLines[j - 1]
74
+ ? dp[i - 1][j - 1] + 1
75
+ : Math.max(dp[i - 1][j], dp[i][j - 1])
76
+ }
77
+ }
78
+
79
+ // Backtrack to get diff
80
+ const result: Array<{ type: 'same' | 'add' | 'del'; origIdx?: number; modIdx?: number; content: string }> = []
81
+ let i = m, j = n
82
+ while (i > 0 || j > 0) {
83
+ if (i > 0 && j > 0 && origLines[i - 1] === modLines[j - 1]) {
84
+ result.unshift({ type: 'same', origIdx: i, modIdx: j, content: origLines[i - 1] })
85
+ i--; j--
86
+ } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
87
+ result.unshift({ type: 'add', modIdx: j, content: modLines[j - 1] })
88
+ j--
89
+ } else if (i > 0) {
90
+ result.unshift({ type: 'del', origIdx: i, content: origLines[i - 1] })
91
+ i--
92
+ }
93
+ }
94
+
95
+ return result
96
+ }
97
+
98
+ const diffLines = computeDiffLines(original, modified)
99
+ ---
100
+
101
+ <div
102
+ data-rfr-diff-viewer
103
+ data-rfr-diff-viewer-files={JSON.stringify(files)}
104
+ data-rfr-diff-viewer-active={String(activeFileIndex)}
105
+ data-rfr-diff-viewer-view-mode={viewMode}
106
+ class={cn(diffViewerVariants({ theme }), className)}
107
+ {...api.ariaProps}
108
+ {...api.dataAttributes}
109
+ {...props}
110
+ >
111
+ {/* Main area: sidebar + diff content */}
112
+ <div style="flex: 1; display: flex; overflow: hidden;">
113
+ {/* File sidebar */}
114
+ {showSidebar && (
115
+ <div
116
+ data-rfr-diff-viewer-sidebar
117
+ class={sidebarVariants({ theme })}
118
+ style={`width: ${sidebarWidth}px`}
119
+ >
120
+ <div class="px-2.5 py-2 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
121
+ Files ({files.length})
122
+ </div>
123
+ {files.map((f, idx) => {
124
+ const active = idx === activeFileIndex
125
+ const fname = f.path.split('/').pop() || f.path
126
+ const dir = f.path.split('/').slice(0, -1).join('/')
127
+ return (
128
+ <div
129
+ data-rfr-diff-viewer-file={String(idx)}
130
+ class={sidebarItemVariants({ active: active ? 'true' : 'false', theme })}
131
+ >
132
+ <div class="flex items-center gap-1">
133
+ <span class="text-[10px]">{api.getFileStatusIcon(f.status)}</span>
134
+ <span class={active ? '' : 'opacity-70'}>{fname}</span>
135
+ <span class="ml-auto text-[10px]">
136
+ <span class="text-green-500">+{f.additions}</span>
137
+ {f.deletions > 0 && <span class="text-red-500">-{f.deletions}</span>}
138
+ </span>
139
+ </div>
140
+ {dir && (
141
+ <div class="text-[10px] opacity-50 mt-0.5 pl-4">{dir}</div>
142
+ )}
143
+ </div>
144
+ )
145
+ })}
146
+ </div>
147
+ )}
148
+
149
+ {/* Diff content area */}
150
+ <div style="flex: 1; display: flex; flex-direction: column; overflow: hidden;">
151
+ {/* Tab bar */}
152
+ {showTabs && activeFile && (
153
+ <div class={tabBarVariants({ theme })}>
154
+ {files.map((f, idx) => {
155
+ const active = idx === activeFileIndex
156
+ const fname = f.path.split('/').pop() || f.path
157
+ return (
158
+ <div
159
+ data-rfr-diff-viewer-tab={String(idx)}
160
+ class={tabVariants({ active: active ? 'true' : 'false', theme })}
161
+ >
162
+ <span class="text-[9px]">{api.getFileStatusIcon(f.status)}</span>
163
+ {fname}
164
+ </div>
165
+ )
166
+ })}
167
+ </div>
168
+ )}
169
+
170
+ {/* Diff display */}
171
+ <div style="flex: 1; overflow: auto;" data-rfr-diff-viewer-content>
172
+ {viewMode === 'side-by-side' ? (
173
+ <div class="grid grid-cols-2 font-mono text-xs" style="min-width: 100%;">
174
+ {/* Left (original) column */}
175
+ <div class="border-r border-gray-700">
176
+ <div class="px-3 py-1.5 border-b bg-muted/30 text-xs text-muted-foreground font-semibold">
177
+ Original
178
+ </div>
179
+ {diffLines.map((line) => (
180
+ <div
181
+ class={cn(
182
+ 'px-3 py-0.5 whitespace-pre',
183
+ line.type === 'del' && 'bg-red-500/10 text-red-400',
184
+ line.type === 'add' && 'invisible h-0 p-0',
185
+ )}
186
+ >
187
+ {line.type === 'del' && (
188
+ <span class="inline-block w-5 text-right mr-2 text-muted-foreground select-none">{line.origIdx}</span>
189
+ )}
190
+ {line.type === 'same' && (
191
+ <span class="inline-block w-5 text-right mr-2 text-muted-foreground select-none">{line.origIdx}</span>
192
+ )}
193
+ {line.type !== 'add' && (
194
+ <span>{line.type === 'del' ? '- ' : ' '}{line.content}</span>
195
+ )}
196
+ </div>
197
+ ))}
198
+ </div>
199
+ {/* Right (modified) column */}
200
+ <div>
201
+ <div class="px-3 py-1.5 border-b bg-muted/30 text-xs text-muted-foreground font-semibold">
202
+ Modified
203
+ </div>
204
+ {diffLines.map((line) => (
205
+ <div
206
+ class={cn(
207
+ 'px-3 py-0.5 whitespace-pre',
208
+ line.type === 'add' && 'bg-green-500/10 text-green-400',
209
+ line.type === 'del' && 'invisible h-0 p-0',
210
+ )}
211
+ >
212
+ {line.type === 'add' && (
213
+ <span class="inline-block w-5 text-right mr-2 text-muted-foreground select-none">{line.modIdx}</span>
214
+ )}
215
+ {line.type === 'same' && (
216
+ <span class="inline-block w-5 text-right mr-2 text-muted-foreground select-none">{line.modIdx}</span>
217
+ )}
218
+ {line.type !== 'del' && (
219
+ <span>{line.type === 'add' ? '+ ' : ' '}{line.content}</span>
220
+ )}
221
+ </div>
222
+ ))}
223
+ </div>
224
+ </div>
225
+ ) : (
226
+ /* Unified/inline view */
227
+ <div class="font-mono text-xs">
228
+ <div class="px-3 py-1.5 border-b bg-muted/30 text-xs text-muted-foreground font-semibold">
229
+ Unified Diff
230
+ </div>
231
+ {diffLines.map((line) => (
232
+ <div
233
+ class={cn(
234
+ 'px-3 py-0.5 whitespace-pre',
235
+ line.type === 'add' && 'bg-green-500/10 text-green-400',
236
+ line.type === 'del' && 'bg-red-500/10 text-red-400',
237
+ )}
238
+ >
239
+ <span class="inline-block w-5 text-right mr-1 text-muted-foreground select-none">
240
+ {line.origIdx ?? ''}
241
+ </span>
242
+ <span class="inline-block w-5 text-right mr-2 text-muted-foreground select-none">
243
+ {line.modIdx ?? ''}
244
+ </span>
245
+ <span>
246
+ {line.type === 'add' ? '+ ' : line.type === 'del' ? '- ' : ' '}{line.content}
247
+ </span>
248
+ </div>
249
+ ))}
250
+ </div>
251
+ )}
252
+ </div>
253
+ </div>
254
+ </div>
255
+
256
+ {/* Status bar */}
257
+ {showStatusBar && (
258
+ <div class={statusBarVariants({ theme })}>
259
+ {statusBarTitle && <span>{statusBarTitle}</span>}
260
+ {activeFile && <span>{activeFile.path}</span>}
261
+ <span style="margin-left: auto;">
262
+ +{api.totalAdditions()} -{api.totalDeletions()}
263
+ </span>
264
+ {statusBarStatus && <span>{statusBarStatus}</span>}
265
+ <span>
266
+ {files.length} {files.length === 1 ? 'file' : 'files'}
267
+ </span>
268
+ </div>
269
+ )}
270
+ </div>
271
+
272
+ <script>
273
+ function initDiffViewers() {
274
+ document.querySelectorAll<HTMLElement>('[data-rfr-diff-viewer]').forEach((root) => {
275
+ if (root.hasAttribute('data-rfr-diff-viewer-init')) return
276
+ root.setAttribute('data-rfr-diff-viewer-init', '')
277
+
278
+ let activeIdx = parseInt(root.getAttribute('data-rfr-diff-viewer-active') || '0', 10)
279
+ const sidebar = root.querySelector<HTMLElement>('[data-rfr-diff-viewer-sidebar]')
280
+
281
+ // File selection (sidebar items)
282
+ const fileItems = root.querySelectorAll<HTMLElement>('[data-rfr-diff-viewer-file]')
283
+ fileItems.forEach((item) => {
284
+ item.addEventListener('click', () => {
285
+ const idx = parseInt(item.getAttribute('data-rfr-diff-viewer-file') || '0', 10)
286
+ selectFile(idx)
287
+ })
288
+ })
289
+
290
+ // Tab click
291
+ const tabs = root.querySelectorAll<HTMLElement>('[data-rfr-diff-viewer-tab]')
292
+ tabs.forEach((tab) => {
293
+ tab.addEventListener('click', () => {
294
+ const idx = parseInt(tab.getAttribute('data-rfr-diff-viewer-tab') || '0', 10)
295
+ selectFile(idx)
296
+ })
297
+ })
298
+
299
+ function selectFile(idx: number) {
300
+ activeIdx = idx
301
+ root.setAttribute('data-rfr-diff-viewer-active', String(idx))
302
+ root.dispatchEvent(
303
+ new CustomEvent('rfr-diff-viewer-file-select', {
304
+ detail: { index: idx },
305
+ bubbles: true,
306
+ }),
307
+ )
308
+
309
+ // Update active states visually
310
+ fileItems.forEach((item) => {
311
+ const itemIdx = item.getAttribute('data-rfr-diff-viewer-file')
312
+ const isActive = itemIdx === String(idx)
313
+ // Toggle active class - swap border color
314
+ if (isActive) {
315
+ item.classList.add('border-blue-500', 'bg-gray-800', 'text-gray-100')
316
+ item.classList.remove('border-transparent', 'text-gray-400')
317
+ } else {
318
+ item.classList.remove('border-blue-500', 'bg-gray-800', 'text-gray-100')
319
+ item.classList.add('border-transparent', 'text-gray-400')
320
+ }
321
+ })
322
+
323
+ tabs.forEach((tab) => {
324
+ const tabIdx = tab.getAttribute('data-rfr-diff-viewer-tab')
325
+ const isActive = tabIdx === String(idx)
326
+ if (isActive) {
327
+ tab.classList.add('bg-gray-950', 'text-gray-100')
328
+ tab.classList.remove('text-gray-400')
329
+ } else {
330
+ tab.classList.remove('bg-gray-950', 'text-gray-100')
331
+ tab.classList.add('text-gray-400')
332
+ }
333
+ })
334
+ }
335
+
336
+ // Sidebar toggle
337
+ function toggleSidebar() {
338
+ if (!sidebar) return
339
+ const isHidden = sidebar.hidden
340
+ sidebar.hidden = !isHidden
341
+ root.dispatchEvent(
342
+ new CustomEvent('rfr-diff-viewer-sidebar-toggle', {
343
+ detail: { open: isHidden },
344
+ bubbles: true,
345
+ }),
346
+ )
347
+ }
348
+
349
+ // Keyboard shortcuts
350
+ function onKeyDown(e: KeyboardEvent) {
351
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return
352
+
353
+ if (e.key === 'j') {
354
+ const files = root.querySelectorAll('[data-rfr-diff-viewer-file]')
355
+ const next = Math.min(activeIdx + 1, files.length - 1)
356
+ selectFile(next)
357
+ } else if (e.key === 'k') {
358
+ const prev = Math.max(activeIdx - 1, 0)
359
+ selectFile(prev)
360
+ } else if (e.key === 'b') {
361
+ toggleSidebar()
362
+ }
363
+ }
364
+
365
+ window.addEventListener('keydown', onKeyDown)
366
+ })
367
+ }
368
+
369
+ initDiffViewers()
370
+ document.addEventListener('astro:after-swap', initDiffViewers)
371
+ </script>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,138 @@
1
+ ---
2
+ import { cn } from '../shared/index.ts'
3
+
4
+ interface Props extends astroHTML.JSX.HTMLAttributes {
5
+ /** Whether the menu starts open */
6
+ defaultOpen?: boolean
7
+ }
8
+
9
+ const { defaultOpen = false, class: className, ...props } = Astro.props
10
+ ---
11
+
12
+ <div
13
+ data-rfr-dropdown-menu
14
+ data-rfr-dropdown-default-open={defaultOpen}
15
+ data-state={defaultOpen ? 'open' : 'closed'}
16
+ class={cn('relative inline-block', className)}
17
+ {...props}
18
+ >
19
+ <slot />
20
+ </div>
21
+
22
+ <script>
23
+ function initDropdownMenus() {
24
+ document.querySelectorAll<HTMLElement>('[data-rfr-dropdown-menu]').forEach((root) => {
25
+ if (root.hasAttribute('data-rfr-dropdown-init')) return
26
+ root.setAttribute('data-rfr-dropdown-init', '')
27
+
28
+ const defaultOpen = root.getAttribute('data-rfr-dropdown-default-open') === 'true'
29
+ let isOpen = defaultOpen
30
+
31
+ const trigger = root.querySelector<HTMLElement>('[data-rfr-dropdown-trigger]')
32
+ const content = root.querySelector<HTMLElement>('[data-rfr-dropdown-content]')
33
+
34
+ function getItems(): HTMLElement[] {
35
+ if (!content) return []
36
+ return Array.from(content.querySelectorAll<HTMLElement>('[role="menuitem"]:not([data-disabled])'))
37
+ }
38
+
39
+ function focusItem(index: number) {
40
+ const items = getItems()
41
+ if (items.length === 0) return
42
+ const clamped = Math.max(0, Math.min(index, items.length - 1))
43
+ items[clamped].focus()
44
+ }
45
+
46
+ function getCurrentIndex(): number {
47
+ const items = getItems()
48
+ return items.indexOf(document.activeElement as HTMLElement)
49
+ }
50
+
51
+ function setState(open: boolean) {
52
+ isOpen = open
53
+ const state = open ? 'open' : 'closed'
54
+ root.setAttribute('data-state', state)
55
+ trigger?.setAttribute('aria-expanded', String(open))
56
+ content?.setAttribute('data-state', state)
57
+
58
+ if (open) {
59
+ content?.removeAttribute('hidden')
60
+ // Focus first menu item
61
+ requestAnimationFrame(() => focusItem(0))
62
+ } else {
63
+ content?.setAttribute('hidden', '')
64
+ trigger?.focus()
65
+ }
66
+
67
+ root.dispatchEvent(new CustomEvent('rfr-dropdown-change', { detail: { open }, bubbles: true }))
68
+ }
69
+
70
+ // Trigger click
71
+ trigger?.addEventListener('click', () => setState(!isOpen))
72
+
73
+ // Click outside to close
74
+ document.addEventListener('click', (e) => {
75
+ if (!isOpen) return
76
+ if (!root.contains(e.target as Node)) {
77
+ setState(false)
78
+ }
79
+ })
80
+
81
+ // Keyboard navigation
82
+ root.addEventListener('keydown', (e) => {
83
+ if (!isOpen) return
84
+
85
+ switch (e.key) {
86
+ case 'Escape':
87
+ e.preventDefault()
88
+ setState(false)
89
+ break
90
+ case 'ArrowDown': {
91
+ e.preventDefault()
92
+ const idx = getCurrentIndex()
93
+ focusItem(idx + 1)
94
+ break
95
+ }
96
+ case 'ArrowUp': {
97
+ e.preventDefault()
98
+ const idx = getCurrentIndex()
99
+ focusItem(idx - 1)
100
+ break
101
+ }
102
+ case 'Home':
103
+ e.preventDefault()
104
+ focusItem(0)
105
+ break
106
+ case 'End':
107
+ e.preventDefault()
108
+ focusItem(getItems().length - 1)
109
+ break
110
+ case 'Enter':
111
+ case ' ':
112
+ // Let the menuitem handle its own click via the browser
113
+ if ((document.activeElement as HTMLElement)?.getAttribute('role') === 'menuitem') {
114
+ e.preventDefault()
115
+ ;(document.activeElement as HTMLElement).click()
116
+ }
117
+ break
118
+ }
119
+ })
120
+
121
+ // Menu item clicks close the menu
122
+ content?.addEventListener('click', (e) => {
123
+ const item = (e.target as HTMLElement).closest<HTMLElement>('[role="menuitem"]')
124
+ if (item && !item.hasAttribute('data-disabled')) {
125
+ setState(false)
126
+ }
127
+ })
128
+
129
+ // Set initial state
130
+ if (!defaultOpen) {
131
+ content?.setAttribute('hidden', '')
132
+ }
133
+ })
134
+ }
135
+
136
+ initDropdownMenus()
137
+ document.addEventListener('astro:after-swap', initDropdownMenus)
138
+ </script>
@@ -0,0 +1,20 @@
1
+ ---
2
+ import { menuContentVariants } from '@refraction-ui/dropdown-menu'
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
+ data-rfr-dropdown-content
12
+ data-state="closed"
13
+ role="menu"
14
+ tabindex="-1"
15
+ hidden
16
+ class={cn(menuContentVariants(), className)}
17
+ {...props}
18
+ >
19
+ <slot />
20
+ </div>
@@ -0,0 +1,21 @@
1
+ ---
2
+ import { menuItemVariants } from '@refraction-ui/dropdown-menu'
3
+ import { cn } from '../shared/index.ts'
4
+
5
+ interface Props extends astroHTML.JSX.HTMLAttributes {
6
+ disabled?: boolean
7
+ }
8
+
9
+ const { disabled = false, class: className, ...props } = Astro.props
10
+ ---
11
+
12
+ <div
13
+ role="menuitem"
14
+ tabindex={disabled ? -1 : 0}
15
+ data-disabled={disabled ? '' : undefined}
16
+ aria-disabled={disabled || undefined}
17
+ class={cn(menuItemVariants(), className)}
18
+ {...props}
19
+ >
20
+ <slot />
21
+ </div>
@@ -0,0 +1,14 @@
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
+ <div
10
+ class={cn('px-2 py-1.5 text-sm font-semibold', className)}
11
+ {...props}
12
+ >
13
+ <slot />
14
+ </div>
@@ -0,0 +1,14 @@
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
+ <div
10
+ role="separator"
11
+ class={cn('-mx-1 my-1 h-px bg-muted', className)}
12
+ {...props}
13
+ >
14
+ </div>
@@ -0,0 +1,18 @@
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
+ <button
10
+ type="button"
11
+ data-rfr-dropdown-trigger
12
+ aria-haspopup="menu"
13
+ aria-expanded="false"
14
+ class={cn(className)}
15
+ {...props}
16
+ >
17
+ <slot />
18
+ </button>
@@ -0,0 +1 @@
1
+ export {}