beads-kanban-ui 0.1.0

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 (154) hide show
  1. package/.designs/beads-kanban-ui-bj0.md +73 -0
  2. package/.designs/beads-kanban-ui-qxq.md +144 -0
  3. package/.designs/epic-support.md +282 -0
  4. package/.env.local.example +2 -0
  5. package/.eslintrc.json +3 -0
  6. package/.gitattributes +3 -0
  7. package/.github/workflows/release.yml +123 -0
  8. package/.history/README_20260121193710.md +227 -0
  9. package/.history/README_20260121193918.md +227 -0
  10. package/.history/README_20260121193921.md +227 -0
  11. package/.history/README_20260121193933.md +227 -0
  12. package/.history/README_20260121193934.md +227 -0
  13. package/.history/README_20260121193944.md +227 -0
  14. package/.history/README_20260121193953.md +227 -0
  15. package/.history/src/app/page_20260121133429.tsx +134 -0
  16. package/.history/src/app/page_20260121133928.tsx +134 -0
  17. package/.history/src/app/page_20260121144850.tsx +138 -0
  18. package/.history/src/app/page_20260121144854.tsx +138 -0
  19. package/.history/src/app/page_20260121144858.tsx +138 -0
  20. package/.history/src/app/page_20260121144902.tsx +138 -0
  21. package/.history/src/app/page_20260121144906.tsx +138 -0
  22. package/.history/src/app/page_20260121144911.tsx +138 -0
  23. package/.history/src/app/page_20260121144928.tsx +138 -0
  24. package/.playwright-mcp/.playwright-mcp/morphing-dialog-wheel-scroll-fix.png +0 -0
  25. package/.playwright-mcp/beams-test.png +0 -0
  26. package/.playwright-mcp/card-verification.png +0 -0
  27. package/.playwright-mcp/design-doc-dialog-fix-verification.png +0 -0
  28. package/.playwright-mcp/dialog-width-test.png +0 -0
  29. package/.playwright-mcp/homepage.png +0 -0
  30. package/.playwright-mcp/morphing-dialog-expanded.png +0 -0
  31. package/.playwright-mcp/morphing-dialog-fixes-final.png +0 -0
  32. package/.playwright-mcp/morphing-dialog-open.png +0 -0
  33. package/.playwright-mcp/page-2026-01-21T14-08-31-529Z.png +0 -0
  34. package/.playwright-mcp/page-2026-01-21T14-09-23-431Z.png +0 -0
  35. package/.playwright-mcp/page-2026-01-21T14-10-28-773Z.png +0 -0
  36. package/.playwright-mcp/page-2026-01-21T14-10-47-432Z.png +0 -0
  37. package/.playwright-mcp/page-2026-01-21T14-11-12-350Z.png +0 -0
  38. package/.playwright-mcp/screenshot-after-click.png +0 -0
  39. package/.playwright-mcp/screenshot-after-dialog-click.png +0 -0
  40. package/.playwright-mcp/sheet-restored-after-dialog-close.png +0 -0
  41. package/.playwright-mcp/test-1-sheet-open-with-overlay.png +0 -0
  42. package/.playwright-mcp/test-2-morphing-dialog-with-overlay.png +0 -0
  43. package/.playwright-mcp/test-3-sheet-open-dark-overlay.png +0 -0
  44. package/.playwright-mcp/test-4-morphing-dialog-with-dark-overlay.png +0 -0
  45. package/.playwright-mcp/test-5-morphing-dialog-scrolled.png +0 -0
  46. package/.playwright-mcp/test-6-sheet-restored-after-dialog-close.png +0 -0
  47. package/.playwright-mcp/wheel-scroll-fixed.png +0 -0
  48. package/README.md +243 -0
  49. package/Screenshots/bead-detail.png +0 -0
  50. package/Screenshots/dashboard.png +0 -0
  51. package/Screenshots/kanban-board.png +0 -0
  52. package/components.json +27 -0
  53. package/logo/logo.svg +1 -0
  54. package/next.config.js +9 -0
  55. package/npm/README.md +37 -0
  56. package/npm/bin/cli.js +107 -0
  57. package/npm/package.json +20 -0
  58. package/npm/scripts/postinstall.js +132 -0
  59. package/package.json +62 -0
  60. package/postcss.config.js +6 -0
  61. package/public/logo.svg +1 -0
  62. package/restart.sh +5 -0
  63. package/server/Cargo.lock +1685 -0
  64. package/server/Cargo.toml +24 -0
  65. package/server/src/db.rs +570 -0
  66. package/server/src/main.rs +141 -0
  67. package/server/src/routes/beads.rs +413 -0
  68. package/server/src/routes/cli.rs +150 -0
  69. package/server/src/routes/fs.rs +360 -0
  70. package/server/src/routes/git.rs +169 -0
  71. package/server/src/routes/mod.rs +107 -0
  72. package/server/src/routes/projects.rs +177 -0
  73. package/server/src/routes/watch.rs +211 -0
  74. package/src/app/globals.css +101 -0
  75. package/src/app/layout.tsx +36 -0
  76. package/src/app/page.tsx +348 -0
  77. package/src/app/project/kanban-board.tsx +356 -0
  78. package/src/app/project/page.tsx +18 -0
  79. package/src/app/settings/page.tsx +224 -0
  80. package/src/components/Beams.css +5 -0
  81. package/src/components/Beams.jsx +307 -0
  82. package/src/components/Galaxy.css +5 -0
  83. package/src/components/Galaxy.jsx +333 -0
  84. package/src/components/activity-timeline.tsx +172 -0
  85. package/src/components/add-project-dialog.tsx +219 -0
  86. package/src/components/bead-card.tsx +196 -0
  87. package/src/components/bead-detail.tsx +306 -0
  88. package/src/components/color-picker.tsx +101 -0
  89. package/src/components/comment-input.tsx +155 -0
  90. package/src/components/comment-list.tsx +147 -0
  91. package/src/components/dependency-badge.tsx +106 -0
  92. package/src/components/design-doc-dialog.tsx +58 -0
  93. package/src/components/design-doc-preview.tsx +97 -0
  94. package/src/components/design-doc-viewer.tsx +199 -0
  95. package/src/components/editable-project-name.tsx +178 -0
  96. package/src/components/epic-card.tsx +263 -0
  97. package/src/components/folder-browser.tsx +273 -0
  98. package/src/components/footer.tsx +27 -0
  99. package/src/components/kanban/default.tsx +184 -0
  100. package/src/components/kanban-column.tsx +167 -0
  101. package/src/components/project-card.tsx +191 -0
  102. package/src/components/quick-filter-bar.tsx +279 -0
  103. package/src/components/scan-directory-dialog.tsx +368 -0
  104. package/src/components/status-donut.tsx +197 -0
  105. package/src/components/subtask-list.tsx +128 -0
  106. package/src/components/tag-picker.tsx +252 -0
  107. package/src/components/ui/.gitkeep +0 -0
  108. package/src/components/ui/alert-dialog.tsx +141 -0
  109. package/src/components/ui/avatar.tsx +67 -0
  110. package/src/components/ui/badge.tsx +230 -0
  111. package/src/components/ui/button.tsx +433 -0
  112. package/src/components/ui/card/index.tsx +24 -0
  113. package/src/components/ui/card/roiui-card.module.css +197 -0
  114. package/src/components/ui/card/roiui-card.tsx +154 -0
  115. package/src/components/ui/card/shadcn-card.tsx +76 -0
  116. package/src/components/ui/chart.tsx +369 -0
  117. package/src/components/ui/dialog.tsx +122 -0
  118. package/src/components/ui/dropdown-menu.tsx +201 -0
  119. package/src/components/ui/input.tsx +22 -0
  120. package/src/components/ui/kanban.tsx +522 -0
  121. package/src/components/ui/morphing-dialog.tsx +457 -0
  122. package/src/components/ui/popover.tsx +33 -0
  123. package/src/components/ui/progress.tsx +28 -0
  124. package/src/components/ui/scroll-area.tsx +48 -0
  125. package/src/components/ui/select.tsx +159 -0
  126. package/src/components/ui/separator.tsx +31 -0
  127. package/src/components/ui/sheet.tsx +142 -0
  128. package/src/components/ui/skeleton.tsx +15 -0
  129. package/src/components/ui/toast.tsx +129 -0
  130. package/src/components/ui/toaster.tsx +35 -0
  131. package/src/components/ui/tooltip.tsx +30 -0
  132. package/src/hooks/.gitkeep +0 -0
  133. package/src/hooks/use-bead-filters.ts +261 -0
  134. package/src/hooks/use-beads.ts +162 -0
  135. package/src/hooks/use-branch-statuses.ts +161 -0
  136. package/src/hooks/use-epics.ts +173 -0
  137. package/src/hooks/use-file-watcher.ts +111 -0
  138. package/src/hooks/use-keyboard-navigation.ts +282 -0
  139. package/src/hooks/use-project.ts +61 -0
  140. package/src/hooks/use-projects.ts +93 -0
  141. package/src/hooks/use-toast.ts +194 -0
  142. package/src/hooks/useClickOutside.tsx +26 -0
  143. package/src/lib/.gitkeep +0 -0
  144. package/src/lib/api.ts +186 -0
  145. package/src/lib/beads-parser.ts +252 -0
  146. package/src/lib/cli.ts +193 -0
  147. package/src/lib/db.ts +145 -0
  148. package/src/lib/design-doc.ts +74 -0
  149. package/src/lib/epic-parser.ts +242 -0
  150. package/src/lib/git.ts +102 -0
  151. package/src/lib/utils.ts +12 -0
  152. package/src/types/index.ts +107 -0
  153. package/tailwind.config.ts +85 -0
  154. package/tsconfig.json +26 -0
@@ -0,0 +1,457 @@
1
+ 'use client';
2
+
3
+ import React, {
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useId,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ } from 'react';
12
+ import {
13
+ motion,
14
+ AnimatePresence,
15
+ MotionConfig,
16
+ Transition,
17
+ Variant,
18
+ } from 'motion/react';
19
+ import { createPortal } from 'react-dom';
20
+ import { RemoveScroll } from 'react-remove-scroll';
21
+ import { cn } from '@/lib/utils';
22
+ import { XIcon } from 'lucide-react';
23
+ import useClickOutside from '@/hooks/useClickOutside';
24
+
25
+ export type MorphingDialogContextType = {
26
+ isOpen: boolean;
27
+ setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
28
+ uniqueId: string;
29
+ triggerRef: React.RefObject<HTMLButtonElement>;
30
+ contentRef: React.RefObject<HTMLDivElement>;
31
+ contentElement: HTMLDivElement | null;
32
+ setContentElement: React.Dispatch<React.SetStateAction<HTMLDivElement | null>>;
33
+ };
34
+
35
+ const MorphingDialogContext =
36
+ React.createContext<MorphingDialogContextType | null>(null);
37
+
38
+ function useMorphingDialog() {
39
+ const context = useContext(MorphingDialogContext);
40
+ if (!context) {
41
+ throw new Error(
42
+ 'useMorphingDialog must be used within a MorphingDialogProvider'
43
+ );
44
+ }
45
+ return context;
46
+ }
47
+
48
+ export type MorphingDialogProviderProps = {
49
+ children: React.ReactNode;
50
+ transition?: Transition;
51
+ onOpenChange?: (isOpen: boolean) => void;
52
+ defaultOpen?: boolean;
53
+ };
54
+
55
+ function MorphingDialogProvider({
56
+ children,
57
+ transition,
58
+ onOpenChange,
59
+ defaultOpen = false,
60
+ }: MorphingDialogProviderProps) {
61
+ const [isOpen, setIsOpen] = useState(defaultOpen);
62
+ const [contentElement, setContentElement] = useState<HTMLDivElement | null>(null);
63
+ const uniqueId = useId();
64
+ const triggerRef = useRef<HTMLButtonElement>(null!);
65
+ const contentRef = useRef<HTMLDivElement>(null!);
66
+
67
+ // Call onOpenChange callback when isOpen changes (after render)
68
+ useEffect(() => {
69
+ onOpenChange?.(isOpen);
70
+ }, [isOpen, onOpenChange]);
71
+
72
+ const contextValue = useMemo(
73
+ () => ({
74
+ isOpen,
75
+ setIsOpen,
76
+ uniqueId,
77
+ triggerRef,
78
+ contentRef,
79
+ contentElement,
80
+ setContentElement,
81
+ }),
82
+ [isOpen, contentElement]
83
+ );
84
+
85
+ return (
86
+ <MorphingDialogContext.Provider value={contextValue}>
87
+ <MotionConfig transition={transition}>{children}</MotionConfig>
88
+ </MorphingDialogContext.Provider>
89
+ );
90
+ }
91
+
92
+ export type MorphingDialogProps = {
93
+ children: React.ReactNode;
94
+ transition?: Transition;
95
+ onOpenChange?: (isOpen: boolean) => void;
96
+ defaultOpen?: boolean;
97
+ };
98
+
99
+ function MorphingDialog({ children, transition, onOpenChange, defaultOpen }: MorphingDialogProps) {
100
+ return (
101
+ <MorphingDialogProvider onOpenChange={onOpenChange} defaultOpen={defaultOpen}>
102
+ <MotionConfig transition={transition}>{children}</MotionConfig>
103
+ </MorphingDialogProvider>
104
+ );
105
+ }
106
+
107
+ export type MorphingDialogTriggerProps = {
108
+ children: React.ReactNode;
109
+ className?: string;
110
+ style?: React.CSSProperties;
111
+ triggerRef?: React.RefObject<HTMLButtonElement>;
112
+ };
113
+
114
+ function MorphingDialogTrigger({
115
+ children,
116
+ className,
117
+ style,
118
+ triggerRef,
119
+ }: MorphingDialogTriggerProps) {
120
+ const { setIsOpen, isOpen, uniqueId } = useMorphingDialog();
121
+
122
+ const handleClick = useCallback(() => {
123
+ setIsOpen(!isOpen);
124
+ }, [isOpen, setIsOpen]);
125
+
126
+ const handleKeyDown = useCallback(
127
+ (event: React.KeyboardEvent) => {
128
+ if (event.key === 'Enter' || event.key === ' ') {
129
+ event.preventDefault();
130
+ setIsOpen(!isOpen);
131
+ }
132
+ },
133
+ [isOpen, setIsOpen]
134
+ );
135
+
136
+ return (
137
+ <motion.button
138
+ ref={triggerRef}
139
+ layoutId={`dialog-${uniqueId}`}
140
+ className={cn('relative cursor-pointer', className)}
141
+ onClick={handleClick}
142
+ onKeyDown={handleKeyDown}
143
+ style={style}
144
+ aria-haspopup='dialog'
145
+ aria-expanded={isOpen}
146
+ aria-controls={`motion-ui-morphing-dialog-content-${uniqueId}`}
147
+ aria-label={`Open dialog ${uniqueId}`}
148
+ >
149
+ {children}
150
+ </motion.button>
151
+ );
152
+ }
153
+
154
+ export type MorphingDialogContentProps = {
155
+ children: React.ReactNode;
156
+ className?: string;
157
+ style?: React.CSSProperties;
158
+ };
159
+
160
+ function MorphingDialogContent({
161
+ children,
162
+ className,
163
+ style,
164
+ }: MorphingDialogContentProps) {
165
+ const { setIsOpen, isOpen, uniqueId, triggerRef, contentRef, setContentElement } = useMorphingDialog();
166
+ // Use contentRef from context so RemoveScroll can allow scrolling inside this element
167
+ const containerRef = contentRef;
168
+ const [firstFocusableElement, setFirstFocusableElement] =
169
+ useState<HTMLElement | null>(null);
170
+ const [lastFocusableElement, setLastFocusableElement] =
171
+ useState<HTMLElement | null>(null);
172
+
173
+ // Register the content element for RemoveScroll shards
174
+ useEffect(() => {
175
+ if (containerRef.current) {
176
+ setContentElement(containerRef.current);
177
+ }
178
+ return () => {
179
+ setContentElement(null);
180
+ };
181
+ }, [setContentElement]);
182
+
183
+ useEffect(() => {
184
+ const handleKeyDown = (event: KeyboardEvent) => {
185
+ if (event.key === 'Escape') {
186
+ setIsOpen(false);
187
+ }
188
+ if (event.key === 'Tab') {
189
+ if (!firstFocusableElement || !lastFocusableElement) return;
190
+
191
+ if (event.shiftKey) {
192
+ if (document.activeElement === firstFocusableElement) {
193
+ event.preventDefault();
194
+ lastFocusableElement.focus();
195
+ }
196
+ } else {
197
+ if (document.activeElement === lastFocusableElement) {
198
+ event.preventDefault();
199
+ firstFocusableElement.focus();
200
+ }
201
+ }
202
+ }
203
+ };
204
+
205
+ document.addEventListener('keydown', handleKeyDown);
206
+
207
+ return () => {
208
+ document.removeEventListener('keydown', handleKeyDown);
209
+ };
210
+ }, [setIsOpen, firstFocusableElement, lastFocusableElement]);
211
+
212
+ useEffect(() => {
213
+ if (isOpen) {
214
+ // Note: overflow-hidden is now handled by RemoveScroll in MorphingDialogContainer
215
+ const focusableElements = containerRef.current?.querySelectorAll(
216
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
217
+ );
218
+ if (focusableElements && focusableElements.length > 0) {
219
+ setFirstFocusableElement(focusableElements[0] as HTMLElement);
220
+ setLastFocusableElement(
221
+ focusableElements[focusableElements.length - 1] as HTMLElement
222
+ );
223
+ (focusableElements[0] as HTMLElement).focus();
224
+ }
225
+ } else {
226
+ triggerRef.current?.focus();
227
+ }
228
+ }, [isOpen, triggerRef]);
229
+
230
+ useClickOutside(containerRef, () => {
231
+ if (isOpen) {
232
+ setIsOpen(false);
233
+ }
234
+ });
235
+
236
+ return (
237
+ <motion.div
238
+ ref={containerRef}
239
+ layoutId={`dialog-${uniqueId}`}
240
+ className={className}
241
+ style={style}
242
+ role='dialog'
243
+ aria-modal='true'
244
+ aria-labelledby={`motion-ui-morphing-dialog-title-${uniqueId}`}
245
+ aria-describedby={`motion-ui-morphing-dialog-description-${uniqueId}`}
246
+ >
247
+ {children}
248
+ </motion.div>
249
+ );
250
+ }
251
+
252
+ export type MorphingDialogContainerProps = {
253
+ children: React.ReactNode;
254
+ className?: string;
255
+ style?: React.CSSProperties;
256
+ };
257
+
258
+ function MorphingDialogContainer({ children }: MorphingDialogContainerProps) {
259
+ const { isOpen, uniqueId, contentElement } = useMorphingDialog();
260
+ const [mounted, setMounted] = useState(false);
261
+
262
+ useEffect(() => {
263
+ setMounted(true);
264
+ return () => setMounted(false);
265
+ }, []);
266
+
267
+ if (!mounted) return null;
268
+
269
+ // Build shards array - only include contentElement if it's available
270
+ const shards = contentElement ? [contentElement] : [];
271
+
272
+ return createPortal(
273
+ <AnimatePresence initial={false} mode='sync'>
274
+ {isOpen && (
275
+ <RemoveScroll shards={shards} noIsolation>
276
+ <>
277
+ <motion.div
278
+ key={`backdrop-${uniqueId}`}
279
+ className='fixed inset-0 z-[60] h-full w-full bg-black/20 backdrop-blur-xs'
280
+ initial={{ opacity: 0 }}
281
+ animate={{ opacity: 1 }}
282
+ exit={{ opacity: 0 }}
283
+ />
284
+ <div className='fixed inset-0 z-[60] flex items-center justify-center'>
285
+ {children}
286
+ </div>
287
+ </>
288
+ </RemoveScroll>
289
+ )}
290
+ </AnimatePresence>,
291
+ document.body
292
+ );
293
+ }
294
+
295
+ export type MorphingDialogTitleProps = {
296
+ children: React.ReactNode;
297
+ className?: string;
298
+ style?: React.CSSProperties;
299
+ };
300
+
301
+ function MorphingDialogTitle({
302
+ children,
303
+ className,
304
+ style,
305
+ }: MorphingDialogTitleProps) {
306
+ const { uniqueId } = useMorphingDialog();
307
+
308
+ return (
309
+ <motion.div
310
+ layoutId={`dialog-title-container-${uniqueId}`}
311
+ className={className}
312
+ style={style}
313
+ layout
314
+ >
315
+ {children}
316
+ </motion.div>
317
+ );
318
+ }
319
+
320
+ export type MorphingDialogSubtitleProps = {
321
+ children: React.ReactNode;
322
+ className?: string;
323
+ style?: React.CSSProperties;
324
+ };
325
+
326
+ function MorphingDialogSubtitle({
327
+ children,
328
+ className,
329
+ style,
330
+ }: MorphingDialogSubtitleProps) {
331
+ const { uniqueId } = useMorphingDialog();
332
+
333
+ return (
334
+ <motion.div
335
+ layoutId={`dialog-subtitle-container-${uniqueId}`}
336
+ className={className}
337
+ style={style}
338
+ >
339
+ {children}
340
+ </motion.div>
341
+ );
342
+ }
343
+
344
+ export type MorphingDialogDescriptionProps = {
345
+ children: React.ReactNode;
346
+ className?: string;
347
+ disableLayoutAnimation?: boolean;
348
+ variants?: {
349
+ initial: Variant;
350
+ animate: Variant;
351
+ exit: Variant;
352
+ };
353
+ };
354
+
355
+ function MorphingDialogDescription({
356
+ children,
357
+ className,
358
+ variants,
359
+ disableLayoutAnimation,
360
+ }: MorphingDialogDescriptionProps) {
361
+ const { uniqueId } = useMorphingDialog();
362
+
363
+ return (
364
+ <motion.div
365
+ key={`dialog-description-${uniqueId}`}
366
+ layoutId={
367
+ disableLayoutAnimation
368
+ ? undefined
369
+ : `dialog-description-content-${uniqueId}`
370
+ }
371
+ variants={variants}
372
+ className={className}
373
+ initial='initial'
374
+ animate='animate'
375
+ exit='exit'
376
+ id={`dialog-description-${uniqueId}`}
377
+ >
378
+ {children}
379
+ </motion.div>
380
+ );
381
+ }
382
+
383
+ export type MorphingDialogImageProps = {
384
+ src: string;
385
+ alt: string;
386
+ className?: string;
387
+ style?: React.CSSProperties;
388
+ };
389
+
390
+ function MorphingDialogImage({
391
+ src,
392
+ alt,
393
+ className,
394
+ style,
395
+ }: MorphingDialogImageProps) {
396
+ const { uniqueId } = useMorphingDialog();
397
+
398
+ return (
399
+ <motion.img
400
+ src={src}
401
+ alt={alt}
402
+ className={cn(className)}
403
+ layoutId={`dialog-img-${uniqueId}`}
404
+ style={style}
405
+ />
406
+ );
407
+ }
408
+
409
+ export type MorphingDialogCloseProps = {
410
+ children?: React.ReactNode;
411
+ className?: string;
412
+ variants?: {
413
+ initial: Variant;
414
+ animate: Variant;
415
+ exit: Variant;
416
+ };
417
+ };
418
+
419
+ function MorphingDialogClose({
420
+ children,
421
+ className,
422
+ variants,
423
+ }: MorphingDialogCloseProps) {
424
+ const { setIsOpen, uniqueId } = useMorphingDialog();
425
+
426
+ const handleClose = useCallback(() => {
427
+ setIsOpen(false);
428
+ }, [setIsOpen]);
429
+
430
+ return (
431
+ <motion.button
432
+ onClick={handleClose}
433
+ type='button'
434
+ aria-label='Close dialog'
435
+ key={`dialog-close-${uniqueId}`}
436
+ className={cn('absolute top-6 right-6', className)}
437
+ initial='initial'
438
+ animate='animate'
439
+ exit='exit'
440
+ variants={variants}
441
+ >
442
+ {children || <XIcon size={24} />}
443
+ </motion.button>
444
+ );
445
+ }
446
+
447
+ export {
448
+ MorphingDialog,
449
+ MorphingDialogTrigger,
450
+ MorphingDialogContainer,
451
+ MorphingDialogContent,
452
+ MorphingDialogClose,
453
+ MorphingDialogTitle,
454
+ MorphingDialogSubtitle,
455
+ MorphingDialogDescription,
456
+ MorphingDialogImage,
457
+ };
@@ -0,0 +1,33 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as PopoverPrimitive from "@radix-ui/react-popover"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Popover = PopoverPrimitive.Root
9
+
10
+ const PopoverTrigger = PopoverPrimitive.Trigger
11
+
12
+ const PopoverAnchor = PopoverPrimitive.Anchor
13
+
14
+ const PopoverContent = React.forwardRef<
15
+ React.ElementRef<typeof PopoverPrimitive.Content>,
16
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
17
+ >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
18
+ <PopoverPrimitive.Portal>
19
+ <PopoverPrimitive.Content
20
+ ref={ref}
21
+ align={align}
22
+ sideOffset={sideOffset}
23
+ className={cn(
24
+ "z-50 w-72 rounded-md border bg-zinc-900 border-zinc-800 p-4 text-zinc-100 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]",
25
+ className
26
+ )}
27
+ {...props}
28
+ />
29
+ </PopoverPrimitive.Portal>
30
+ ))
31
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName
32
+
33
+ export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
@@ -0,0 +1,28 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as ProgressPrimitive from "@radix-ui/react-progress"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Progress = React.forwardRef<
9
+ React.ElementRef<typeof ProgressPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
11
+ >(({ className, value, ...props }, ref) => (
12
+ <ProgressPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
16
+ className
17
+ )}
18
+ {...props}
19
+ >
20
+ <ProgressPrimitive.Indicator
21
+ className="h-full w-full flex-1 bg-primary transition-all"
22
+ style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
23
+ />
24
+ </ProgressPrimitive.Root>
25
+ ))
26
+ Progress.displayName = ProgressPrimitive.Root.displayName
27
+
28
+ export { Progress }
@@ -0,0 +1,48 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const ScrollArea = React.forwardRef<
9
+ React.ElementRef<typeof ScrollAreaPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
11
+ >(({ className, children, ...props }, ref) => (
12
+ <ScrollAreaPrimitive.Root
13
+ ref={ref}
14
+ className={cn("relative overflow-hidden", className)}
15
+ {...props}
16
+ >
17
+ <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
18
+ {children}
19
+ </ScrollAreaPrimitive.Viewport>
20
+ <ScrollBar />
21
+ <ScrollAreaPrimitive.Corner />
22
+ </ScrollAreaPrimitive.Root>
23
+ ))
24
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25
+
26
+ const ScrollBar = React.forwardRef<
27
+ React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
28
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
29
+ >(({ className, orientation = "vertical", ...props }, ref) => (
30
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
31
+ ref={ref}
32
+ orientation={orientation}
33
+ className={cn(
34
+ "flex touch-none select-none transition-colors",
35
+ orientation === "vertical" &&
36
+ "h-full w-2.5 border-l border-l-transparent p-[1px]",
37
+ orientation === "horizontal" &&
38
+ "h-2.5 flex-col border-t border-t-transparent p-[1px]",
39
+ className
40
+ )}
41
+ {...props}
42
+ >
43
+ <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
44
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
45
+ ))
46
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47
+
48
+ export { ScrollArea, ScrollBar }