@moontra/moonui-pro 2.19.0 → 2.20.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.
- package/dist/index.d.ts +3251 -0
- package/dist/index.mjs +2410 -1545
- package/package.json +137 -136
- package/src/__tests__/use-local-storage.test.tsx +2 -2
- package/src/components/advanced-chart/index.tsx +6 -6
- package/src/components/calendar/event-dialog.tsx +1 -1
- package/src/components/calendar/index.tsx +1 -1
- package/src/components/calendar-pro/index.tsx +2 -4
- package/src/components/dashboard/demo.tsx +2 -2
- package/src/components/dashboard/widgets/activity-feed.tsx +1 -1
- package/src/components/dashboard/widgets/metric-card.tsx +1 -1
- package/src/components/enhanced/button.tsx +13 -13
- package/src/components/file-upload/file-upload.test.tsx +20 -19
- package/src/components/form-wizard/form-wizard-progress.tsx +7 -7
- package/src/components/gesture-drawer/index.tsx +551 -0
- package/src/components/github-stars/hooks.ts +1 -1
- package/src/components/github-stars/index.tsx +1 -1
- package/src/components/github-stars/types.ts +1 -0
- package/src/components/health-check/index.tsx +2 -2
- package/src/components/hover-card-3d/index.tsx +437 -74
- package/src/components/index.ts +10 -1
- package/src/components/lazy-component/index.tsx +4 -2
- package/src/components/license-error/index.tsx +29 -0
- package/src/components/memory-efficient-data/index.tsx +1 -1
- package/src/components/pinch-zoom/index.tsx +438 -42
- package/src/components/rich-text-editor/index.tsx +12 -12
- package/src/components/timeline/index.tsx +2 -2
- package/src/components/ui/aspect-ratio.tsx +186 -22
- package/src/components/ui/button.tsx +47 -50
- package/src/components/ui/card.tsx +98 -30
- package/src/components/ui/gesture-drawer.tsx +11 -0
- package/src/components/ui/index.ts +17 -5
- package/src/components/ui/lightbox.tsx +606 -0
- package/src/components/ui/media-gallery.tsx +612 -0
- package/src/components/ui/select.tsx +134 -35
- package/src/components/ui/toggle.tsx +78 -15
- package/src/components/virtual-list/index.tsx +7 -7
- package/src/index.ts +4 -4
- package/src/lib/component-metadata.ts +18 -0
- package/src/lib/paddle.ts +17 -0
- package/src/patterns/login-form/index.tsx +1 -1
- package/src/patterns/login-form/types.ts +6 -6
- package/src/styles/index.css +14 -4
- package/src/types/next-auth.d.ts +21 -0
- package/src/use-local-storage.tsx +3 -3
- package/src/use-scroll-animation.ts +3 -5
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
{/*
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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-
|
|
311
|
-
active && "bg-
|
|
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-
|
|
771
|
-
'[&_table_th]:border [&_table_th]:border-
|
|
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-
|
|
774
|
-
'[&_pre]:bg-
|
|
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-
|
|
1056
|
+
<div className={cn("border rounded-lg overflow-hidden bg-background", className)}>
|
|
1057
1057
|
{/* Toolbar */}
|
|
1058
1058
|
<TooltipProvider>
|
|
1059
|
-
<div className="border-b bg-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
}
|