@moontra/moonui-pro 2.19.0 → 2.20.1

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 (47) hide show
  1. package/dist/index.d.ts +3251 -0
  2. package/dist/index.mjs +2410 -1545
  3. package/package.json +137 -136
  4. package/src/__tests__/use-local-storage.test.tsx +2 -2
  5. package/src/components/advanced-chart/index.tsx +6 -6
  6. package/src/components/calendar/event-dialog.tsx +1 -1
  7. package/src/components/calendar/index.tsx +1 -1
  8. package/src/components/calendar-pro/index.tsx +2 -4
  9. package/src/components/dashboard/demo.tsx +2 -2
  10. package/src/components/dashboard/widgets/activity-feed.tsx +1 -1
  11. package/src/components/dashboard/widgets/metric-card.tsx +1 -1
  12. package/src/components/enhanced/button.tsx +13 -13
  13. package/src/components/file-upload/file-upload.test.tsx +20 -19
  14. package/src/components/form-wizard/form-wizard-progress.tsx +7 -7
  15. package/src/components/gesture-drawer/index.tsx +551 -0
  16. package/src/components/github-stars/hooks.ts +1 -1
  17. package/src/components/github-stars/index.tsx +1 -1
  18. package/src/components/github-stars/types.ts +1 -0
  19. package/src/components/health-check/index.tsx +2 -2
  20. package/src/components/hover-card-3d/index.tsx +437 -74
  21. package/src/components/index.ts +10 -1
  22. package/src/components/lazy-component/index.tsx +4 -2
  23. package/src/components/license-error/index.tsx +29 -0
  24. package/src/components/memory-efficient-data/index.tsx +1 -1
  25. package/src/components/pinch-zoom/index.tsx +438 -42
  26. package/src/components/rich-text-editor/index.tsx +12 -12
  27. package/src/components/timeline/index.tsx +2 -2
  28. package/src/components/ui/aspect-ratio.tsx +186 -22
  29. package/src/components/ui/button.tsx +47 -50
  30. package/src/components/ui/card.tsx +98 -30
  31. package/src/components/ui/gesture-drawer.tsx +11 -0
  32. package/src/components/ui/index.ts +17 -5
  33. package/src/components/ui/lightbox.tsx +606 -0
  34. package/src/components/ui/media-gallery.tsx +612 -0
  35. package/src/components/ui/select.tsx +134 -35
  36. package/src/components/ui/toggle.tsx +78 -15
  37. package/src/components/virtual-list/index.tsx +7 -7
  38. package/src/index.ts +4 -4
  39. package/src/lib/component-metadata.ts +18 -0
  40. package/src/lib/paddle.ts +17 -0
  41. package/src/patterns/login-form/index.tsx +1 -1
  42. package/src/patterns/login-form/types.ts +6 -6
  43. package/src/styles/index.css +14 -4
  44. package/src/types/next-auth.d.ts +21 -0
  45. package/src/use-local-storage.tsx +3 -3
  46. package/src/use-scroll-animation.ts +3 -5
  47. package/src/components/ui/hover-card-3d.tsx +0 -472
@@ -1,11 +1,11 @@
1
1
  "use client"
2
2
 
3
- import React, { useRef, useState, useCallback } from "react"
4
- import { motion, useMotionValue, useTransform, animate } from "framer-motion"
3
+ import React, { useRef, useState, useCallback, useEffect } from "react"
4
+ import { motion, useMotionValue, useTransform, animate, AnimatePresence } from "framer-motion"
5
5
  import { cn } from "../../lib/utils"
6
6
  import { Card, CardContent } from "../ui/card"
7
7
  import { Button } from "../ui/button"
8
- import { Lock, Sparkles } from "lucide-react"
8
+ import { Lock, Sparkles, ZoomIn, ZoomOut, Maximize2, RotateCw, Minimize2 } from "lucide-react"
9
9
  import { useSubscription } from "../../hooks/use-subscription"
10
10
 
11
11
  interface PinchZoomProps {
@@ -16,6 +16,14 @@ interface PinchZoomProps {
16
16
  className?: string
17
17
  contentClassName?: string
18
18
  onZoomChange?: (zoom: number) => void
19
+ showControls?: boolean
20
+ showIndicator?: boolean
21
+ doubleTapBehavior?: 'zoom' | 'reset'
22
+ wheelScaling?: boolean
23
+ dragEnabled?: boolean
24
+ zoomStep?: number
25
+ smoothZoom?: boolean
26
+ boundaryConstraints?: boolean
19
27
  }
20
28
 
21
29
  const PinchZoomInternal = React.forwardRef<HTMLDivElement, PinchZoomProps>(
@@ -27,10 +35,23 @@ const PinchZoomInternal = React.forwardRef<HTMLDivElement, PinchZoomProps>(
27
35
  className,
28
36
  contentClassName,
29
37
  onZoomChange,
38
+ showControls = true,
39
+ showIndicator = true,
40
+ doubleTapBehavior = 'zoom',
41
+ wheelScaling = true,
42
+ dragEnabled = true,
43
+ zoomStep = 0.5,
44
+ smoothZoom = true,
45
+ boundaryConstraints = true,
30
46
  ...props
31
47
  }, ref) => {
32
48
  const containerRef = useRef<HTMLDivElement>(null)
49
+ const contentRef = useRef<HTMLDivElement>(null)
33
50
  const [isDragging, setIsDragging] = useState(false)
51
+ const [showZoomIndicator, setShowZoomIndicator] = useState(false)
52
+ const [lastTap, setLastTap] = useState(0)
53
+ const [isHovered, setIsHovered] = useState(false)
54
+ const [isFullscreen, setIsFullscreen] = useState(false)
34
55
 
35
56
  const scale = useMotionValue(initialZoom)
36
57
  const x = useMotionValue(0)
@@ -40,22 +61,135 @@ const PinchZoomInternal = React.forwardRef<HTMLDivElement, PinchZoomProps>(
40
61
  return Math.min(Math.max(value, minZoom), maxZoom)
41
62
  })
42
63
 
64
+ // Zoom yüzdesi için transform
65
+ const zoomPercentage = useTransform(scale, (value) => {
66
+ return Math.round(value * 100)
67
+ })
68
+
69
+ // Sınırları hesapla
70
+ const calculateConstraints = useCallback(() => {
71
+ if (!containerRef.current || !contentRef.current || !boundaryConstraints) return { left: 0, right: 0, top: 0, bottom: 0 }
72
+
73
+ const containerRect = containerRef.current.getBoundingClientRect()
74
+ const currentScale = scale.get()
75
+
76
+ const scaledWidth = containerRect.width * currentScale
77
+ const scaledHeight = containerRect.height * currentScale
78
+
79
+ const maxX = Math.max(0, (scaledWidth - containerRect.width) / 2)
80
+ const maxY = Math.max(0, (scaledHeight - containerRect.height) / 2)
81
+
82
+ return {
83
+ left: -maxX,
84
+ right: maxX,
85
+ top: -maxY,
86
+ bottom: maxY
87
+ }
88
+ }, [scale, boundaryConstraints])
89
+
90
+ // Browser zoom'unu engelle (sadece Ctrl/Cmd basılıyken)
91
+ useEffect(() => {
92
+ if (!containerRef.current || !wheelScaling) return
93
+
94
+ const container = containerRef.current
95
+ const handleWheelPrevent = (e: WheelEvent) => {
96
+ // Sadece component üzerinde ve Ctrl/Cmd basılıyken preventDefault yap
97
+ if (isHovered && wheelScaling && (e.ctrlKey || e.metaKey)) {
98
+ e.preventDefault()
99
+ }
100
+ }
101
+
102
+ // Passive: false ile event listener ekle
103
+ container.addEventListener('wheel', handleWheelPrevent, { passive: false })
104
+
105
+ return () => {
106
+ container.removeEventListener('wheel', handleWheelPrevent)
107
+ }
108
+ }, [isHovered, wheelScaling])
109
+
43
110
  const handleWheel = useCallback((event: React.WheelEvent) => {
111
+ if (!wheelScaling) return
112
+
113
+ // Ctrl (veya Mac'te Cmd) basılı değilse zoom yapma
114
+ if (!event.ctrlKey && !event.metaKey) return
115
+
44
116
  event.preventDefault()
45
117
 
46
118
  const delta = -event.deltaY / 1000
47
119
  const currentScale = scale.get()
48
120
  const newScale = Math.min(Math.max(currentScale + delta, minZoom), maxZoom)
49
121
 
50
- animate(scale, newScale, { duration: 0.2 })
122
+ // Zoom noktasını hesapla (mouse pozisyonuna göre)
123
+ if (containerRef.current) {
124
+ const rect = containerRef.current.getBoundingClientRect()
125
+ const centerX = (event.clientX - rect.left) / rect.width - 0.5
126
+ const centerY = (event.clientY - rect.top) / rect.height - 0.5
127
+
128
+ const scaleDiff = newScale - currentScale
129
+ const currentX = x.get()
130
+ const currentY = y.get()
131
+
132
+ // Zoom noktasına göre pozisyonu ayarla
133
+ const newX = currentX - centerX * rect.width * scaleDiff
134
+ const newY = currentY - centerY * rect.height * scaleDiff
135
+
136
+ if (smoothZoom) {
137
+ animate(x, newX, { duration: 0.2 })
138
+ animate(y, newY, { duration: 0.2 })
139
+ } else {
140
+ x.set(newX)
141
+ y.set(newY)
142
+ }
143
+ }
144
+
145
+ if (smoothZoom) {
146
+ animate(scale, newScale, { duration: 0.2 })
147
+ } else {
148
+ scale.set(newScale)
149
+ }
150
+
51
151
  onZoomChange?.(newScale)
52
- }, [scale, minZoom, maxZoom, onZoomChange])
152
+
153
+ // Zoom göstergesini göster
154
+ if (showIndicator) {
155
+ setShowZoomIndicator(true)
156
+ setTimeout(() => setShowZoomIndicator(false), 1500)
157
+ }
158
+ }, [scale, x, y, minZoom, maxZoom, onZoomChange, wheelScaling, smoothZoom, showIndicator])
53
159
 
54
160
  const handleDoubleClick = useCallback((event: React.MouseEvent) => {
55
161
  event.preventDefault()
56
162
 
57
163
  const currentScale = scale.get()
58
- const newScale = currentScale > 1 ? 1 : 2
164
+ let newScale: number
165
+
166
+ if (doubleTapBehavior === 'reset') {
167
+ newScale = initialZoom
168
+ } else {
169
+ // Zoom to point functionality
170
+ newScale = currentScale > 1 ? 1 : 2
171
+
172
+ if (newScale > 1 && containerRef.current) {
173
+ const rect = containerRef.current.getBoundingClientRect()
174
+ const centerX = (event.clientX - rect.left) / rect.width - 0.5
175
+ const centerY = (event.clientY - rect.top) / rect.height - 0.5
176
+
177
+ // Tıklanan noktaya zoom yap
178
+ const targetX = -centerX * rect.width * (newScale - 1)
179
+ const targetY = -centerY * rect.height * (newScale - 1)
180
+
181
+ animate(x, targetX, {
182
+ duration: 0.3,
183
+ type: "spring",
184
+ stiffness: 300
185
+ })
186
+ animate(y, targetY, {
187
+ duration: 0.3,
188
+ type: "spring",
189
+ stiffness: 300
190
+ })
191
+ }
192
+ }
59
193
 
60
194
  animate(scale, Math.min(Math.max(newScale, minZoom), maxZoom), {
61
195
  duration: 0.3,
@@ -63,65 +197,327 @@ const PinchZoomInternal = React.forwardRef<HTMLDivElement, PinchZoomProps>(
63
197
  stiffness: 300
64
198
  })
65
199
 
66
- if (newScale === 1) {
200
+ if (newScale === 1 || doubleTapBehavior === 'reset') {
67
201
  animate(x, 0, { duration: 0.3 })
68
202
  animate(y, 0, { duration: 0.3 })
69
203
  }
70
204
 
71
205
  onZoomChange?.(newScale)
72
- }, [scale, x, y, minZoom, maxZoom, onZoomChange])
206
+
207
+ if (showIndicator) {
208
+ setShowZoomIndicator(true)
209
+ setTimeout(() => setShowZoomIndicator(false), 1500)
210
+ }
211
+ }, [scale, x, y, minZoom, maxZoom, initialZoom, onZoomChange, doubleTapBehavior, showIndicator])
212
+
213
+ // Touch gestures için çift dokunma
214
+ const handleTouchEnd = useCallback((event: React.TouchEvent) => {
215
+ const now = Date.now()
216
+ const timeSinceLastTap = now - lastTap
217
+
218
+ if (timeSinceLastTap < 300 && timeSinceLastTap > 0) {
219
+ // Çift dokunma algılandı
220
+ const touch = event.changedTouches[0]
221
+ const fakeMouseEvent = {
222
+ clientX: touch.clientX,
223
+ clientY: touch.clientY,
224
+ preventDefault: () => {}
225
+ } as React.MouseEvent
226
+
227
+ handleDoubleClick(fakeMouseEvent)
228
+ }
229
+
230
+ setLastTap(now)
231
+ }, [lastTap, handleDoubleClick])
73
232
 
74
233
  const resetZoom = useCallback(() => {
75
- animate(scale, initialZoom, { duration: 0.3 })
76
- animate(x, 0, { duration: 0.3 })
77
- animate(y, 0, { duration: 0.3 })
234
+ animate(scale, initialZoom, {
235
+ duration: 0.3,
236
+ type: "spring",
237
+ stiffness: 300
238
+ })
239
+ animate(x, 0, {
240
+ duration: 0.3,
241
+ type: "spring",
242
+ stiffness: 300
243
+ })
244
+ animate(y, 0, {
245
+ duration: 0.3,
246
+ type: "spring",
247
+ stiffness: 300
248
+ })
78
249
  onZoomChange?.(initialZoom)
79
250
  }, [scale, x, y, initialZoom, onZoomChange])
80
251
 
252
+ // Programmatik zoom kontrolleri
253
+ const zoomIn = useCallback(() => {
254
+ const currentScale = scale.get()
255
+ const newScale = Math.min(currentScale + zoomStep, maxZoom)
256
+
257
+ animate(scale, newScale, {
258
+ duration: 0.3,
259
+ type: "spring",
260
+ stiffness: 300
261
+ })
262
+
263
+ onZoomChange?.(newScale)
264
+
265
+ if (showIndicator) {
266
+ setShowZoomIndicator(true)
267
+ setTimeout(() => setShowZoomIndicator(false), 1500)
268
+ }
269
+ }, [scale, maxZoom, zoomStep, onZoomChange, showIndicator])
270
+
271
+ const zoomOut = useCallback(() => {
272
+ const currentScale = scale.get()
273
+ const newScale = Math.max(currentScale - zoomStep, minZoom)
274
+
275
+ animate(scale, newScale, {
276
+ duration: 0.3,
277
+ type: "spring",
278
+ stiffness: 300
279
+ })
280
+
281
+ // Zoom out yapınca sınırları kontrol et
282
+ if (newScale < scale.get()) {
283
+ const constraints = calculateConstraints()
284
+ const currentX = x.get()
285
+ const currentY = y.get()
286
+
287
+ if (currentX < constraints.left || currentX > constraints.right) {
288
+ animate(x, 0, { duration: 0.3 })
289
+ }
290
+ if (currentY < constraints.top || currentY > constraints.bottom) {
291
+ animate(y, 0, { duration: 0.3 })
292
+ }
293
+ }
294
+
295
+ onZoomChange?.(newScale)
296
+
297
+ if (showIndicator) {
298
+ setShowZoomIndicator(true)
299
+ setTimeout(() => setShowZoomIndicator(false), 1500)
300
+ }
301
+ }, [scale, minZoom, zoomStep, x, y, calculateConstraints, onZoomChange, showIndicator])
302
+
303
+ const fitToScreen = useCallback(() => {
304
+ resetZoom()
305
+ }, [resetZoom])
306
+
307
+ // Fullscreen toggle
308
+ const toggleFullscreen = useCallback(() => {
309
+ if (!containerRef.current) return
310
+
311
+ if (!document.fullscreenElement) {
312
+ // Fullscreen'e gir
313
+ const element = containerRef.current.parentElement || containerRef.current
314
+ element.requestFullscreen().then(() => {
315
+ setIsFullscreen(true)
316
+ }).catch((err) => {
317
+ console.error("Fullscreen error:", err)
318
+ })
319
+ } else {
320
+ // Fullscreen'den çık
321
+ document.exitFullscreen().then(() => {
322
+ setIsFullscreen(false)
323
+ }).catch((err) => {
324
+ console.error("Exit fullscreen error:", err)
325
+ })
326
+ }
327
+ }, [])
328
+
329
+ // Fullscreen değişikliklerini dinle
330
+ useEffect(() => {
331
+ const handleFullscreenChange = () => {
332
+ setIsFullscreen(!!document.fullscreenElement)
333
+ }
334
+
335
+ document.addEventListener('fullscreenchange', handleFullscreenChange)
336
+ document.addEventListener('webkitfullscreenchange', handleFullscreenChange)
337
+ document.addEventListener('mozfullscreenchange', handleFullscreenChange)
338
+ document.addEventListener('MSFullscreenChange', handleFullscreenChange)
339
+
340
+ return () => {
341
+ document.removeEventListener('fullscreenchange', handleFullscreenChange)
342
+ document.removeEventListener('webkitfullscreenchange', handleFullscreenChange)
343
+ document.removeEventListener('mozfullscreenchange', handleFullscreenChange)
344
+ document.removeEventListener('MSFullscreenChange', handleFullscreenChange)
345
+ }
346
+ }, [])
347
+
348
+ // Keyboard zoom shortcuts (Ctrl/Cmd + ve -)
349
+ useEffect(() => {
350
+ if (!isHovered || !wheelScaling) return
351
+
352
+ const handleKeyDown = (e: KeyboardEvent) => {
353
+ if (!e.ctrlKey && !e.metaKey) return
354
+
355
+ if (e.key === '+' || e.key === '=') {
356
+ e.preventDefault()
357
+ zoomIn()
358
+ } else if (e.key === '-' || e.key === '_') {
359
+ e.preventDefault()
360
+ zoomOut()
361
+ } else if (e.key === '0') {
362
+ e.preventDefault()
363
+ resetZoom()
364
+ }
365
+ }
366
+
367
+ document.addEventListener('keydown', handleKeyDown)
368
+
369
+ return () => {
370
+ document.removeEventListener('keydown', handleKeyDown)
371
+ }
372
+ }, [isHovered, wheelScaling, zoomIn, zoomOut, resetZoom])
373
+
374
+ // Drag sınırlarını güncelle
375
+ useEffect(() => {
376
+ const unsubscribe = scale.onChange(() => {
377
+ const constraints = calculateConstraints()
378
+ const currentX = x.get()
379
+ const currentY = y.get()
380
+
381
+ // Sınırlar dışında mı kontrol et
382
+ if (boundaryConstraints) {
383
+ if (currentX < constraints.left || currentX > constraints.right) {
384
+ animate(x, Math.max(constraints.left, Math.min(constraints.right, currentX)), { duration: 0.2 })
385
+ }
386
+ if (currentY < constraints.top || currentY > constraints.bottom) {
387
+ animate(y, Math.max(constraints.top, Math.min(constraints.bottom, currentY)), { duration: 0.2 })
388
+ }
389
+ }
390
+ })
391
+
392
+ return unsubscribe
393
+ }, [scale, x, y, calculateConstraints, boundaryConstraints])
394
+
81
395
  return (
82
396
  <div
83
397
  ref={ref}
84
398
  className={cn(
85
399
  "relative overflow-hidden touch-none select-none",
86
- "cursor-grab active:cursor-grabbing",
400
+ dragEnabled && "cursor-grab active:cursor-grabbing",
401
+ isFullscreen && "fixed inset-0 z-50 bg-background",
87
402
  className
88
403
  )}
89
404
  onWheel={handleWheel}
90
405
  onDoubleClick={handleDoubleClick}
406
+ onTouchEnd={handleTouchEnd}
407
+ onMouseEnter={() => setIsHovered(true)}
408
+ onMouseLeave={() => setIsHovered(false)}
91
409
  {...props}
92
410
  >
93
- <motion.div
94
- ref={containerRef}
95
- drag
96
- dragElastic={0}
97
- dragMomentum={false}
98
- onDragStart={() => setIsDragging(true)}
99
- onDragEnd={() => setIsDragging(false)}
100
- style={{
101
- scale: constrainedScale,
102
- x,
103
- y,
104
- }}
105
- className={cn(
106
- "w-full h-full flex items-center justify-center",
107
- isDragging && "cursor-grabbing",
108
- contentClassName
109
- )}
110
- >
111
- {children}
112
- </motion.div>
411
+ <div ref={containerRef} className="w-full h-full">
412
+ <motion.div
413
+ ref={contentRef}
414
+ drag={dragEnabled}
415
+ dragElastic={0.1}
416
+ dragMomentum={false}
417
+ dragConstraints={boundaryConstraints ? calculateConstraints() : false}
418
+ onDragStart={() => setIsDragging(true)}
419
+ onDragEnd={() => setIsDragging(false)}
420
+ style={{
421
+ scale: constrainedScale,
422
+ x,
423
+ y,
424
+ }}
425
+ className={cn(
426
+ "w-full h-full flex items-center justify-center",
427
+ isDragging && "cursor-grabbing",
428
+ contentClassName
429
+ )}
430
+ >
431
+ {children}
432
+ </motion.div>
433
+ </div>
113
434
 
114
- {/* Reset Button */}
115
- <button
116
- onClick={resetZoom}
117
- className={cn(
118
- "absolute bottom-4 right-4 px-3 py-1 bg-background/80 backdrop-blur-sm",
119
- "border rounded-md text-sm hover:bg-background/90 transition-colors",
120
- "opacity-0 hover:opacity-100 focus:opacity-100"
435
+ {/* Zoom Indicator */}
436
+ <AnimatePresence>
437
+ {showIndicator && showZoomIndicator && (
438
+ <motion.div
439
+ initial={{ opacity: 0, scale: 0.8 }}
440
+ animate={{ opacity: 1, scale: 1 }}
441
+ exit={{ opacity: 0, scale: 0.8 }}
442
+ transition={{ duration: 0.2 }}
443
+ className="absolute top-4 left-1/2 -translate-x-1/2 px-3 py-1.5 bg-black/80 dark:bg-white/80 text-white dark:text-black rounded-full text-sm font-medium pointer-events-none"
444
+ >
445
+ {zoomPercentage.get()}%
446
+ </motion.div>
121
447
  )}
122
- >
123
- Reset
124
- </button>
448
+ </AnimatePresence>
449
+
450
+ {/* Control Buttons */}
451
+ {showControls && (
452
+ <div className="absolute bottom-4 right-4 flex gap-2">
453
+ <button
454
+ onClick={zoomOut}
455
+ disabled={scale.get() <= minZoom}
456
+ className={cn(
457
+ "p-2 bg-background/80 backdrop-blur-sm border rounded-md",
458
+ "hover:bg-background/90 transition-all duration-200",
459
+ "disabled:opacity-50 disabled:cursor-not-allowed",
460
+ "shadow-sm hover:shadow-md"
461
+ )}
462
+ aria-label="Zoom out"
463
+ >
464
+ <ZoomOut className="w-4 h-4" />
465
+ </button>
466
+ <button
467
+ onClick={zoomIn}
468
+ disabled={scale.get() >= maxZoom}
469
+ className={cn(
470
+ "p-2 bg-background/80 backdrop-blur-sm border rounded-md",
471
+ "hover:bg-background/90 transition-all duration-200",
472
+ "disabled:opacity-50 disabled:cursor-not-allowed",
473
+ "shadow-sm hover:shadow-md"
474
+ )}
475
+ aria-label="Zoom in"
476
+ >
477
+ <ZoomIn className="w-4 h-4" />
478
+ </button>
479
+ <button
480
+ onClick={resetZoom}
481
+ className={cn(
482
+ "p-2 bg-background/80 backdrop-blur-sm border rounded-md",
483
+ "hover:bg-background/90 transition-all duration-200",
484
+ "shadow-sm hover:shadow-md"
485
+ )}
486
+ aria-label="Reset zoom"
487
+ >
488
+ <RotateCw className="w-4 h-4" />
489
+ </button>
490
+ <button
491
+ onClick={toggleFullscreen}
492
+ className={cn(
493
+ "p-2 bg-background/80 backdrop-blur-sm border rounded-md",
494
+ "hover:bg-background/90 transition-all duration-200",
495
+ "shadow-sm hover:shadow-md"
496
+ )}
497
+ aria-label={isFullscreen ? "Exit fullscreen" : "Enter fullscreen"}
498
+ >
499
+ {isFullscreen ? (
500
+ <Minimize2 className="w-4 h-4" />
501
+ ) : (
502
+ <Maximize2 className="w-4 h-4" />
503
+ )}
504
+ </button>
505
+ </div>
506
+ )}
507
+
508
+ {/* Zoom Level Info */}
509
+ {showControls && (
510
+ <div className="absolute bottom-4 left-4 px-3 py-1.5 bg-background/80 backdrop-blur-sm border rounded-md text-sm">
511
+ <div className="flex items-center gap-2">
512
+ <span>{Math.round(scale.get() * 100)}%</span>
513
+ {wheelScaling && !showZoomIndicator && (
514
+ <span className="text-xs text-muted-foreground">
515
+ (Ctrl+Scroll to zoom)
516
+ </span>
517
+ )}
518
+ </div>
519
+ </div>
520
+ )}
125
521
  </div>
126
522
  )
127
523
  }
@@ -307,8 +307,8 @@ const ToolbarButton = ({
307
307
  onClick={onClick}
308
308
  disabled={disabled}
309
309
  className={cn(
310
- "p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors",
311
- active && "bg-gray-100 dark:bg-gray-800",
310
+ "p-2 rounded hover:bg-accent transition-colors",
311
+ active && "bg-accent",
312
312
  disabled && "opacity-50 cursor-not-allowed"
313
313
  )}
314
314
  >
@@ -767,11 +767,11 @@ export function RichTextEditor({
767
767
  '[&_li]:mb-1',
768
768
  // Table styles
769
769
  '[&_table]:border-collapse-collapse [&_table]:w-full [&_table]:mb-4',
770
- '[&_table_td]:border [&_table_td]:border-gray-300 [&_table_td]:p-2',
771
- '[&_table_th]:border [&_table_th]:border-gray-300 [&_table_th]:p-2 [&_table_th]:bg-gray-50 [&_table_th]:font-semibold',
770
+ '[&_table_td]:border [&_table_td]:border-border [&_table_td]:p-2',
771
+ '[&_table_th]:border [&_table_th]:border-border [&_table_th]:p-2 [&_table_th]:bg-muted [&_table_th]:font-semibold',
772
772
  // Other styles
773
- '[&_blockquote]:border-l-4 [&_blockquote]:border-gray-300 [&_blockquote]:pl-4 [&_blockquote]:italic',
774
- '[&_pre]:bg-gray-100 [&_pre]:dark:bg-gray-800 [&_pre]:p-4 [&_pre]:rounded [&_pre]:overflow-x-auto'
773
+ '[&_blockquote]:border-l-4 [&_blockquote]:border-muted-foreground/30 [&_blockquote]:pl-4 [&_blockquote]:italic',
774
+ '[&_pre]:bg-muted [&_pre]:p-4 [&_pre]:rounded [&_pre]:overflow-x-auto'
775
775
  ),
776
776
  },
777
777
  },
@@ -1053,10 +1053,10 @@ export function RichTextEditor({
1053
1053
  }
1054
1054
 
1055
1055
  return (
1056
- <div className={cn("border rounded-lg overflow-hidden bg-white dark:bg-gray-950", className)}>
1056
+ <div className={cn("border rounded-lg overflow-hidden bg-background", className)}>
1057
1057
  {/* Toolbar */}
1058
1058
  <TooltipProvider>
1059
- <div className="border-b bg-gray-50 dark:bg-gray-900 p-3">
1059
+ <div className="border-b bg-muted/50 p-3">
1060
1060
  <div className="flex items-center flex-wrap gap-1">
1061
1061
  {/* Format Group */}
1062
1062
  {(features.bold || features.italic || features.underline || features.strike || features.code) && (
@@ -1469,7 +1469,7 @@ export function RichTextEditor({
1469
1469
  type="checkbox"
1470
1470
  id="headerRow"
1471
1471
  defaultChecked
1472
- className="rounded border-gray-300"
1472
+ className="rounded border-input"
1473
1473
  />
1474
1474
  <Label htmlFor="headerRow" className="text-sm font-normal">
1475
1475
  Include header row
@@ -2046,7 +2046,7 @@ export function RichTextEditor({
2046
2046
  {/* Preview */}
2047
2047
  <div className="grid gap-3">
2048
2048
  <Label>Preview</Label>
2049
- <div className="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
2049
+ <div className="p-4 bg-muted rounded-lg">
2050
2050
  <table
2051
2051
  className="w-full text-sm"
2052
2052
  style={{
@@ -2220,7 +2220,7 @@ export function RichTextEditor({
2220
2220
  <textarea
2221
2221
  value={sourceContent}
2222
2222
  onChange={(e) => setSourceContent(e.target.value)}
2223
- className="w-full h-full p-4 font-mono text-sm resize-none focus:outline-none bg-gray-50 dark:bg-gray-900"
2223
+ className="w-full h-full p-4 font-mono text-sm resize-none focus:outline-none bg-muted"
2224
2224
  placeholder="HTML source code..."
2225
2225
  />
2226
2226
  ) : (
@@ -2277,7 +2277,7 @@ export function RichTextEditor({
2277
2277
  </div>
2278
2278
 
2279
2279
  {/* Statistics Bar */}
2280
- <div className="border-t bg-gray-50 dark:bg-gray-900 px-4 py-2">
2280
+ <div className="border-t bg-muted/50 px-4 py-2">
2281
2281
  <div className="flex items-center justify-between text-xs text-muted-foreground">
2282
2282
  <div className="flex items-center gap-6">
2283
2283
  <div className="flex items-center gap-1">
@@ -207,7 +207,7 @@ const DEFAULT_COLORS: Record<TimelineEventType, string> = {
207
207
  warning: 'bg-yellow-500 border-yellow-500',
208
208
  error: 'bg-red-500 border-red-500',
209
209
  info: 'bg-blue-500 border-blue-500',
210
- pending: 'bg-gray-400 border-gray-400',
210
+ pending: 'bg-muted-foreground/40 border-muted-foreground/40',
211
211
  milestone: 'bg-purple-500 border-purple-500',
212
212
  custom: 'bg-slate-500 border-slate-500'
213
213
  }
@@ -227,7 +227,7 @@ const TEXT_COLORS: Record<TimelineEventType, string> = {
227
227
  warning: 'text-yellow-700 dark:text-yellow-400',
228
228
  error: 'text-red-700 dark:text-red-400',
229
229
  info: 'text-blue-700 dark:text-blue-400',
230
- pending: 'text-gray-700 dark:text-gray-400',
230
+ pending: 'text-muted-foreground',
231
231
  milestone: 'text-purple-700 dark:text-purple-400',
232
232
  custom: 'text-slate-700 dark:text-slate-400'
233
233
  }