@pascal-app/editor 0.4.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/package.json +62 -0
- package/src/components/editor/custom-camera-controls.tsx +387 -0
- package/src/components/editor/editor-layout-v2.tsx +220 -0
- package/src/components/editor/export-manager.tsx +78 -0
- package/src/components/editor/first-person-controls.tsx +249 -0
- package/src/components/editor/floating-action-menu.tsx +231 -0
- package/src/components/editor/floorplan-panel.tsx +9609 -0
- package/src/components/editor/grid.tsx +161 -0
- package/src/components/editor/index.tsx +928 -0
- package/src/components/editor/node-action-menu.tsx +66 -0
- package/src/components/editor/preset-thumbnail-generator.tsx +125 -0
- package/src/components/editor/selection-manager.tsx +897 -0
- package/src/components/editor/site-edge-labels.tsx +90 -0
- package/src/components/editor/thumbnail-generator.tsx +166 -0
- package/src/components/editor/wall-measurement-label.tsx +258 -0
- package/src/components/feedback-dialog.tsx +265 -0
- package/src/components/pascal-radio.tsx +280 -0
- package/src/components/preview-button.tsx +16 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +77 -0
- package/src/components/systems/roof/roof-edit-system.tsx +69 -0
- package/src/components/systems/stair/stair-edit-system.tsx +69 -0
- package/src/components/systems/zone/zone-label-editor-system.tsx +320 -0
- package/src/components/systems/zone/zone-system.tsx +87 -0
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +42 -0
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +47 -0
- package/src/components/tools/ceiling/ceiling-tool.tsx +465 -0
- package/src/components/tools/door/door-math.ts +110 -0
- package/src/components/tools/door/door-tool.tsx +293 -0
- package/src/components/tools/door/move-door-tool.tsx +373 -0
- package/src/components/tools/item/item-tool.tsx +26 -0
- package/src/components/tools/item/move-tool.tsx +90 -0
- package/src/components/tools/item/placement-math.ts +85 -0
- package/src/components/tools/item/placement-strategies.ts +556 -0
- package/src/components/tools/item/placement-types.ts +117 -0
- package/src/components/tools/item/use-draft-node.ts +227 -0
- package/src/components/tools/item/use-placement-coordinator.tsx +877 -0
- package/src/components/tools/roof/move-roof-tool.tsx +288 -0
- package/src/components/tools/roof/roof-tool.tsx +318 -0
- package/src/components/tools/select/box-select-tool.tsx +626 -0
- package/src/components/tools/shared/cursor-sphere.tsx +119 -0
- package/src/components/tools/shared/polygon-editor.tsx +361 -0
- package/src/components/tools/site/site-boundary-editor.tsx +42 -0
- package/src/components/tools/slab/slab-boundary-editor.tsx +42 -0
- package/src/components/tools/slab/slab-hole-editor.tsx +47 -0
- package/src/components/tools/slab/slab-tool.tsx +322 -0
- package/src/components/tools/stair/stair-defaults.ts +7 -0
- package/src/components/tools/stair/stair-tool.tsx +194 -0
- package/src/components/tools/tool-manager.tsx +120 -0
- package/src/components/tools/wall/wall-drafting.ts +140 -0
- package/src/components/tools/wall/wall-tool.tsx +210 -0
- package/src/components/tools/window/move-window-tool.tsx +410 -0
- package/src/components/tools/window/window-math.ts +117 -0
- package/src/components/tools/window/window-tool.tsx +303 -0
- package/src/components/tools/zone/zone-boundary-editor.tsx +39 -0
- package/src/components/tools/zone/zone-tool.tsx +364 -0
- package/src/components/ui/action-menu/action-button.tsx +59 -0
- package/src/components/ui/action-menu/camera-actions.tsx +74 -0
- package/src/components/ui/action-menu/control-modes.tsx +240 -0
- package/src/components/ui/action-menu/furnish-tools.tsx +102 -0
- package/src/components/ui/action-menu/index.tsx +152 -0
- package/src/components/ui/action-menu/structure-tools.tsx +100 -0
- package/src/components/ui/action-menu/view-toggles.tsx +397 -0
- package/src/components/ui/command-palette/editor-commands.tsx +396 -0
- package/src/components/ui/command-palette/index.tsx +730 -0
- package/src/components/ui/controls/action-button.tsx +33 -0
- package/src/components/ui/controls/material-picker.tsx +194 -0
- package/src/components/ui/controls/metric-control.tsx +262 -0
- package/src/components/ui/controls/panel-section.tsx +65 -0
- package/src/components/ui/controls/segmented-control.tsx +45 -0
- package/src/components/ui/controls/slider-control.tsx +245 -0
- package/src/components/ui/controls/toggle-control.tsx +38 -0
- package/src/components/ui/floating-level-selector.tsx +355 -0
- package/src/components/ui/helpers/ceiling-helper.tsx +20 -0
- package/src/components/ui/helpers/helper-manager.tsx +33 -0
- package/src/components/ui/helpers/item-helper.tsx +40 -0
- package/src/components/ui/helpers/roof-helper.tsx +16 -0
- package/src/components/ui/helpers/slab-helper.tsx +20 -0
- package/src/components/ui/helpers/wall-helper.tsx +20 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +1580 -0
- package/src/components/ui/item-catalog/item-catalog.tsx +219 -0
- package/src/components/ui/panels/ceiling-panel.tsx +230 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +356 -0
- package/src/components/ui/panels/door-panel.tsx +600 -0
- package/src/components/ui/panels/item-panel.tsx +306 -0
- package/src/components/ui/panels/panel-manager.tsx +59 -0
- package/src/components/ui/panels/panel-wrapper.tsx +80 -0
- package/src/components/ui/panels/presets/presets-popover.tsx +511 -0
- package/src/components/ui/panels/reference-panel.tsx +177 -0
- package/src/components/ui/panels/roof-panel.tsx +262 -0
- package/src/components/ui/panels/roof-segment-panel.tsx +326 -0
- package/src/components/ui/panels/slab-panel.tsx +228 -0
- package/src/components/ui/panels/stair-panel.tsx +304 -0
- package/src/components/ui/panels/stair-segment-panel.tsx +339 -0
- package/src/components/ui/panels/wall-panel.tsx +123 -0
- package/src/components/ui/panels/window-panel.tsx +441 -0
- package/src/components/ui/primitives/button.tsx +69 -0
- package/src/components/ui/primitives/card.tsx +75 -0
- package/src/components/ui/primitives/color-dot.tsx +61 -0
- package/src/components/ui/primitives/context-menu.tsx +227 -0
- package/src/components/ui/primitives/dialog.tsx +129 -0
- package/src/components/ui/primitives/dropdown-menu.tsx +228 -0
- package/src/components/ui/primitives/error-boundary.tsx +52 -0
- package/src/components/ui/primitives/input.tsx +21 -0
- package/src/components/ui/primitives/number-input.tsx +187 -0
- package/src/components/ui/primitives/opacity-control.tsx +79 -0
- package/src/components/ui/primitives/popover.tsx +42 -0
- package/src/components/ui/primitives/separator.tsx +28 -0
- package/src/components/ui/primitives/sheet.tsx +130 -0
- package/src/components/ui/primitives/shortcut-token.tsx +64 -0
- package/src/components/ui/primitives/sidebar.tsx +855 -0
- package/src/components/ui/primitives/skeleton.tsx +13 -0
- package/src/components/ui/primitives/slider.tsx +58 -0
- package/src/components/ui/primitives/switch.tsx +29 -0
- package/src/components/ui/primitives/tooltip.tsx +57 -0
- package/src/components/ui/scene-loader.tsx +40 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +103 -0
- package/src/components/ui/sidebar/icon-rail.tsx +147 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +100 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +438 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +188 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +80 -0
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +126 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +64 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +1543 -0
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +98 -0
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +117 -0
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +65 -0
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +214 -0
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +96 -0
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +216 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +115 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +342 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +271 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +106 -0
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +64 -0
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +87 -0
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +167 -0
- package/src/components/ui/sidebar/tab-bar.tsx +39 -0
- package/src/components/ui/slider-demo.tsx +36 -0
- package/src/components/ui/slider.tsx +81 -0
- package/src/components/ui/viewer-toolbar.tsx +342 -0
- package/src/components/viewer-overlay.tsx +499 -0
- package/src/components/viewer-zone-system.tsx +48 -0
- package/src/contexts/presets-context.tsx +121 -0
- package/src/hooks/use-auto-save.ts +194 -0
- package/src/hooks/use-contextual-tools.ts +52 -0
- package/src/hooks/use-grid-events.ts +106 -0
- package/src/hooks/use-keyboard.ts +214 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/hooks/use-reduced-motion.ts +20 -0
- package/src/index.tsx +33 -0
- package/src/lib/constants.ts +3 -0
- package/src/lib/level-selection.ts +31 -0
- package/src/lib/scene.ts +394 -0
- package/src/lib/sfx/index.ts +2 -0
- package/src/lib/sfx-bus.ts +49 -0
- package/src/lib/sfx-player.ts +60 -0
- package/src/lib/utils.ts +43 -0
- package/src/store/use-audio.tsx +45 -0
- package/src/store/use-command-registry.ts +36 -0
- package/src/store/use-editor.tsx +522 -0
- package/src/store/use-palette-view-registry.ts +45 -0
- package/src/store/use-upload.ts +90 -0
- package/src/three-types.ts +3 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import NumberFlow from '@number-flow/react'
|
|
4
|
+
import { useScene } from '@pascal-app/core'
|
|
5
|
+
import { useCallback, useRef, useState } from 'react'
|
|
6
|
+
|
|
7
|
+
interface NumberInputProps {
|
|
8
|
+
label: string
|
|
9
|
+
value: number
|
|
10
|
+
onChange: (value: number) => void
|
|
11
|
+
min?: number
|
|
12
|
+
max?: number
|
|
13
|
+
precision?: number
|
|
14
|
+
step?: number
|
|
15
|
+
className?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function NumberInput({
|
|
19
|
+
label,
|
|
20
|
+
value,
|
|
21
|
+
onChange,
|
|
22
|
+
min,
|
|
23
|
+
max,
|
|
24
|
+
precision = 2,
|
|
25
|
+
step = 0.1,
|
|
26
|
+
className = '',
|
|
27
|
+
}: NumberInputProps) {
|
|
28
|
+
const [isEditing, setIsEditing] = useState(false)
|
|
29
|
+
const [isDragging, setIsDragging] = useState(false)
|
|
30
|
+
const [inputValue, setInputValue] = useState(value.toFixed(precision))
|
|
31
|
+
const startXRef = useRef(0)
|
|
32
|
+
const startValueRef = useRef(0)
|
|
33
|
+
const labelRef = useRef<HTMLDivElement>(null)
|
|
34
|
+
|
|
35
|
+
const clamp = useCallback(
|
|
36
|
+
(val: number) => {
|
|
37
|
+
if (min !== undefined && val < min) return min
|
|
38
|
+
if (max !== undefined && val > max) return max
|
|
39
|
+
return val
|
|
40
|
+
},
|
|
41
|
+
[min, max],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const handleLabelMouseDown = useCallback(
|
|
45
|
+
(e: React.MouseEvent) => {
|
|
46
|
+
if (isEditing) return
|
|
47
|
+
e.preventDefault()
|
|
48
|
+
setIsDragging(true)
|
|
49
|
+
startXRef.current = e.clientX
|
|
50
|
+
startValueRef.current = value
|
|
51
|
+
|
|
52
|
+
// Pause history tracking during drag
|
|
53
|
+
useScene.temporal.getState().pause()
|
|
54
|
+
|
|
55
|
+
let finalValue = value
|
|
56
|
+
|
|
57
|
+
const handleMouseMove = (moveEvent: MouseEvent) => {
|
|
58
|
+
const deltaX = moveEvent.clientX - startXRef.current
|
|
59
|
+
|
|
60
|
+
// Determine step size based on modifier keys
|
|
61
|
+
let dragStep = step // Default from prop
|
|
62
|
+
if (moveEvent.shiftKey) {
|
|
63
|
+
dragStep = step * 10 // Coarse
|
|
64
|
+
} else if (moveEvent.altKey) {
|
|
65
|
+
dragStep = step * 0.1 // Fine
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const deltaValue = deltaX * dragStep
|
|
69
|
+
const newValue = clamp(startValueRef.current + deltaValue)
|
|
70
|
+
const newFinalValue = Number.parseFloat(newValue.toFixed(precision))
|
|
71
|
+
|
|
72
|
+
// Only call onChange if value actually changed (avoid extra processing on tiny moves)
|
|
73
|
+
if (newFinalValue !== finalValue) {
|
|
74
|
+
finalValue = newFinalValue
|
|
75
|
+
onChange(finalValue)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const handleMouseUp = () => {
|
|
80
|
+
setIsDragging(false)
|
|
81
|
+
document.removeEventListener('mousemove', handleMouseMove)
|
|
82
|
+
document.removeEventListener('mouseup', handleMouseUp)
|
|
83
|
+
|
|
84
|
+
// Reset to initial value while still paused (no history entry)
|
|
85
|
+
// Then resume and apply final value (creates single history entry)
|
|
86
|
+
if (finalValue !== startValueRef.current) {
|
|
87
|
+
onChange(startValueRef.current)
|
|
88
|
+
useScene.temporal.getState().resume()
|
|
89
|
+
onChange(finalValue)
|
|
90
|
+
} else {
|
|
91
|
+
useScene.temporal.getState().resume()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
document.addEventListener('mousemove', handleMouseMove)
|
|
96
|
+
document.addEventListener('mouseup', handleMouseUp)
|
|
97
|
+
},
|
|
98
|
+
[isEditing, value, onChange, clamp, precision, step],
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
const handleValueClick = useCallback(() => {
|
|
102
|
+
setIsEditing(true)
|
|
103
|
+
setInputValue(value.toFixed(precision))
|
|
104
|
+
}, [value, precision])
|
|
105
|
+
|
|
106
|
+
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
107
|
+
setInputValue(e.target.value)
|
|
108
|
+
}, [])
|
|
109
|
+
|
|
110
|
+
const handleInputBlur = useCallback(() => {
|
|
111
|
+
const numValue = Number.parseFloat(inputValue)
|
|
112
|
+
if (!Number.isNaN(numValue)) {
|
|
113
|
+
onChange(clamp(Number.parseFloat(numValue.toFixed(precision))))
|
|
114
|
+
}
|
|
115
|
+
setIsEditing(false)
|
|
116
|
+
}, [inputValue, onChange, clamp, precision])
|
|
117
|
+
|
|
118
|
+
const handleInputKeyDown = useCallback(
|
|
119
|
+
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
120
|
+
if (e.key === 'Enter') {
|
|
121
|
+
const numValue = Number.parseFloat(inputValue)
|
|
122
|
+
if (!Number.isNaN(numValue)) {
|
|
123
|
+
onChange(clamp(Number.parseFloat(numValue.toFixed(precision))))
|
|
124
|
+
}
|
|
125
|
+
setIsEditing(false)
|
|
126
|
+
} else if (e.key === 'Escape') {
|
|
127
|
+
setInputValue(value.toFixed(precision))
|
|
128
|
+
setIsEditing(false)
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
[inputValue, onChange, value, clamp, precision],
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div className={`${className} group/input relative`}>
|
|
136
|
+
<div
|
|
137
|
+
className={`pointer-events-none absolute inset-y-0 left-0 bg-primary/10 transition-all duration-75 dark:bg-primary/20 ${isDragging ? 'opacity-100' : 'opacity-0'}`}
|
|
138
|
+
style={{
|
|
139
|
+
width: `${Math.min(100, Math.max(0, ((value - (min ?? Math.min(0, value))) / ((max ?? Math.max(10, value)) - (min ?? Math.min(0, value)))) * 100))}%`,
|
|
140
|
+
borderTopRightRadius: value >= (max ?? Math.max(10, value)) ? '0.5rem' : '0',
|
|
141
|
+
borderBottomRightRadius: value >= (max ?? Math.max(10, value)) ? '0.5rem' : '0',
|
|
142
|
+
borderTopLeftRadius: '0.5rem',
|
|
143
|
+
borderBottomLeftRadius: '0.5rem',
|
|
144
|
+
}}
|
|
145
|
+
/>
|
|
146
|
+
<div
|
|
147
|
+
className={`relative z-10 flex items-center overflow-hidden rounded-lg border shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] transition-all focus-within:border-primary focus-within:ring-1 focus-within:ring-primary ${isDragging ? 'border-neutral-300 bg-transparent ring-1 ring-neutral-200/60 dark:border-border dark:ring-border/50' : 'border-neutral-200/60 bg-white hover:border-neutral-300 dark:border-border/50 dark:bg-accent/30 dark:hover:border-border/80'}`}
|
|
148
|
+
>
|
|
149
|
+
<div
|
|
150
|
+
className={`z-10 select-none truncate py-1.5 pr-1 pl-2 font-barlow font-medium text-muted-foreground text-xs ${
|
|
151
|
+
isDragging
|
|
152
|
+
? 'cursor-ew-resize text-foreground'
|
|
153
|
+
: 'hover:cursor-ew-resize hover:text-foreground'
|
|
154
|
+
} transition-colors`}
|
|
155
|
+
onMouseDown={handleLabelMouseDown}
|
|
156
|
+
ref={labelRef}
|
|
157
|
+
>
|
|
158
|
+
{label}
|
|
159
|
+
</div>
|
|
160
|
+
{isEditing ? (
|
|
161
|
+
<input
|
|
162
|
+
autoFocus
|
|
163
|
+
className="z-10 min-w-0 flex-1 bg-transparent px-2 py-1.5 text-right font-medium font-mono text-foreground text-sm outline-none placeholder:text-muted-foreground/50"
|
|
164
|
+
onBlur={handleInputBlur}
|
|
165
|
+
onChange={handleInputChange}
|
|
166
|
+
onKeyDown={handleInputKeyDown}
|
|
167
|
+
size={1}
|
|
168
|
+
type="text"
|
|
169
|
+
value={inputValue}
|
|
170
|
+
/>
|
|
171
|
+
) : (
|
|
172
|
+
<div
|
|
173
|
+
className={
|
|
174
|
+
'z-10 min-w-0 flex-1 cursor-text truncate px-2 py-1.5 text-right font-medium font-mono text-foreground text-sm tabular-nums tracking-tight transition-colors hover:bg-black/5 dark:hover:bg-white/5'
|
|
175
|
+
}
|
|
176
|
+
onClick={handleValueClick}
|
|
177
|
+
>
|
|
178
|
+
<NumberFlow
|
|
179
|
+
format={{ minimumFractionDigits: precision, maximumFractionDigits: precision }}
|
|
180
|
+
value={Number(value.toFixed(precision))}
|
|
181
|
+
/>
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Eye, EyeOff } from 'lucide-react'
|
|
4
|
+
import { useState } from 'react'
|
|
5
|
+
import { Button } from '../../../components/ui/primitives/button'
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../../../components/ui/primitives/popover'
|
|
7
|
+
import { Slider } from '../../../components/ui/primitives/slider'
|
|
8
|
+
import { cn } from '../../../lib/utils'
|
|
9
|
+
|
|
10
|
+
interface OpacityControlProps {
|
|
11
|
+
visible?: boolean
|
|
12
|
+
opacity?: number
|
|
13
|
+
onVisibilityToggle: () => void
|
|
14
|
+
onOpacityChange: (opacity: number) => void
|
|
15
|
+
className?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function OpacityControl({
|
|
19
|
+
visible = true,
|
|
20
|
+
opacity = 100,
|
|
21
|
+
onVisibilityToggle,
|
|
22
|
+
onOpacityChange,
|
|
23
|
+
className,
|
|
24
|
+
}: OpacityControlProps) {
|
|
25
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
26
|
+
const actualOpacity = opacity ?? 100
|
|
27
|
+
const isHidden = visible === false || actualOpacity === 0
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Popover onOpenChange={setIsOpen} open={isOpen}>
|
|
31
|
+
<div className={cn('flex items-center gap-1', className)}>
|
|
32
|
+
{!isHidden && actualOpacity < 100 && (
|
|
33
|
+
<span className="text-muted-foreground text-xs">{actualOpacity}%</span>
|
|
34
|
+
)}
|
|
35
|
+
<PopoverTrigger asChild>
|
|
36
|
+
<Button
|
|
37
|
+
className={cn(
|
|
38
|
+
'h-5 w-5 p-0 transition-opacity',
|
|
39
|
+
isHidden ? 'opacity-100' : 'opacity-0 group-hover/item:opacity-100',
|
|
40
|
+
)}
|
|
41
|
+
onClick={(e) => {
|
|
42
|
+
e.stopPropagation()
|
|
43
|
+
// If clicking the button (not opening popover), toggle visibility
|
|
44
|
+
if (!isOpen) {
|
|
45
|
+
onVisibilityToggle()
|
|
46
|
+
}
|
|
47
|
+
}}
|
|
48
|
+
size="sm"
|
|
49
|
+
variant="ghost"
|
|
50
|
+
>
|
|
51
|
+
{isHidden ? <EyeOff className="h-3 w-3" /> : <Eye className="h-3 w-3" />}
|
|
52
|
+
</Button>
|
|
53
|
+
</PopoverTrigger>
|
|
54
|
+
<PopoverContent
|
|
55
|
+
align="end"
|
|
56
|
+
className="w-48 p-3"
|
|
57
|
+
onClick={(e) => e.stopPropagation()}
|
|
58
|
+
side="right"
|
|
59
|
+
>
|
|
60
|
+
<div className="space-y-2">
|
|
61
|
+
<div className="flex items-center justify-between">
|
|
62
|
+
<span className="font-medium text-sm">Opacity</span>
|
|
63
|
+
<span className="text-muted-foreground text-xs">{actualOpacity}%</span>
|
|
64
|
+
</div>
|
|
65
|
+
<Slider
|
|
66
|
+
max={100}
|
|
67
|
+
min={0}
|
|
68
|
+
onValueChange={(values: number[]) => {
|
|
69
|
+
if (values[0] !== undefined) onOpacityChange(values[0])
|
|
70
|
+
}}
|
|
71
|
+
step={1}
|
|
72
|
+
value={[actualOpacity]}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
</PopoverContent>
|
|
76
|
+
</div>
|
|
77
|
+
</Popover>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover'
|
|
4
|
+
import type * as React from 'react'
|
|
5
|
+
|
|
6
|
+
import { cn } from '../../../lib/utils'
|
|
7
|
+
|
|
8
|
+
function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
|
9
|
+
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
13
|
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function PopoverContent({
|
|
17
|
+
className,
|
|
18
|
+
align = 'center',
|
|
19
|
+
sideOffset = 4,
|
|
20
|
+
...props
|
|
21
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
|
22
|
+
return (
|
|
23
|
+
<PopoverPrimitive.Portal>
|
|
24
|
+
<PopoverPrimitive.Content
|
|
25
|
+
align={align}
|
|
26
|
+
className={cn(
|
|
27
|
+
'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 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in',
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
data-slot="popover-content"
|
|
31
|
+
sideOffset={sideOffset}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
</PopoverPrimitive.Portal>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
|
39
|
+
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as SeparatorPrimitive from '@radix-ui/react-separator'
|
|
4
|
+
import type * as React from 'react'
|
|
5
|
+
|
|
6
|
+
import { cn } from '../../../lib/utils'
|
|
7
|
+
|
|
8
|
+
function Separator({
|
|
9
|
+
className,
|
|
10
|
+
orientation = 'horizontal',
|
|
11
|
+
decorative = true,
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
|
14
|
+
return (
|
|
15
|
+
<SeparatorPrimitive.Root
|
|
16
|
+
className={cn(
|
|
17
|
+
'shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=vertical]:h-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px',
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
data-slot="separator"
|
|
21
|
+
decorative={decorative}
|
|
22
|
+
orientation={orientation}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { Separator }
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as SheetPrimitive from '@radix-ui/react-dialog'
|
|
4
|
+
import { XIcon } from 'lucide-react'
|
|
5
|
+
import type * as React from 'react'
|
|
6
|
+
|
|
7
|
+
import { cn } from '../../../lib/utils'
|
|
8
|
+
|
|
9
|
+
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
|
10
|
+
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
|
14
|
+
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
|
18
|
+
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
|
22
|
+
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function SheetOverlay({
|
|
26
|
+
className,
|
|
27
|
+
...props
|
|
28
|
+
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
|
29
|
+
return (
|
|
30
|
+
<SheetPrimitive.Overlay
|
|
31
|
+
className={cn(
|
|
32
|
+
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=open]:animate-in',
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
data-slot="sheet-overlay"
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function SheetContent({
|
|
42
|
+
className,
|
|
43
|
+
children,
|
|
44
|
+
side = 'right',
|
|
45
|
+
...props
|
|
46
|
+
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
|
47
|
+
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
48
|
+
}) {
|
|
49
|
+
return (
|
|
50
|
+
<SheetPortal>
|
|
51
|
+
<SheetOverlay />
|
|
52
|
+
<SheetPrimitive.Content
|
|
53
|
+
className={cn(
|
|
54
|
+
'fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=open]:animate-in data-[state=closed]:duration-300 data-[state=open]:duration-500',
|
|
55
|
+
side === 'right' &&
|
|
56
|
+
'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',
|
|
57
|
+
side === 'left' &&
|
|
58
|
+
'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
|
|
59
|
+
side === 'top' &&
|
|
60
|
+
'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b',
|
|
61
|
+
side === 'bottom' &&
|
|
62
|
+
'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t',
|
|
63
|
+
className,
|
|
64
|
+
)}
|
|
65
|
+
data-slot="sheet-content"
|
|
66
|
+
{...props}
|
|
67
|
+
>
|
|
68
|
+
{children}
|
|
69
|
+
<SheetPrimitive.Close className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
|
70
|
+
<XIcon className="size-4" />
|
|
71
|
+
<span className="sr-only">Close</span>
|
|
72
|
+
</SheetPrimitive.Close>
|
|
73
|
+
</SheetPrimitive.Content>
|
|
74
|
+
</SheetPortal>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
79
|
+
return (
|
|
80
|
+
<div
|
|
81
|
+
className={cn('flex flex-col gap-1.5 p-4', className)}
|
|
82
|
+
data-slot="sheet-header"
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
|
89
|
+
return (
|
|
90
|
+
<div
|
|
91
|
+
className={cn('mt-auto flex flex-col gap-2 p-4', className)}
|
|
92
|
+
data-slot="sheet-footer"
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
|
99
|
+
return (
|
|
100
|
+
<SheetPrimitive.Title
|
|
101
|
+
className={cn('font-semibold text-foreground', className)}
|
|
102
|
+
data-slot="sheet-title"
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function SheetDescription({
|
|
109
|
+
className,
|
|
110
|
+
...props
|
|
111
|
+
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
|
112
|
+
return (
|
|
113
|
+
<SheetPrimitive.Description
|
|
114
|
+
className={cn('text-muted-foreground text-sm', className)}
|
|
115
|
+
data-slot="sheet-description"
|
|
116
|
+
{...props}
|
|
117
|
+
/>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
Sheet,
|
|
123
|
+
SheetTrigger,
|
|
124
|
+
SheetClose,
|
|
125
|
+
SheetContent,
|
|
126
|
+
SheetHeader,
|
|
127
|
+
SheetFooter,
|
|
128
|
+
SheetTitle,
|
|
129
|
+
SheetDescription,
|
|
130
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Icon } from '@iconify/react'
|
|
2
|
+
import type * as React from 'react'
|
|
3
|
+
|
|
4
|
+
import { cn } from '../../../lib/utils'
|
|
5
|
+
|
|
6
|
+
const MOUSE_SHORTCUTS = {
|
|
7
|
+
Click: {
|
|
8
|
+
icon: 'ph:mouse-left-click-fill',
|
|
9
|
+
label: 'Left click',
|
|
10
|
+
},
|
|
11
|
+
'Left click': {
|
|
12
|
+
icon: 'ph:mouse-left-click-fill',
|
|
13
|
+
label: 'Left click',
|
|
14
|
+
},
|
|
15
|
+
'Middle click': {
|
|
16
|
+
icon: 'qlementine-icons:mouse-middle-button-16',
|
|
17
|
+
label: 'Middle click',
|
|
18
|
+
},
|
|
19
|
+
'Right click': {
|
|
20
|
+
icon: 'ph:mouse-right-click-fill',
|
|
21
|
+
label: 'Right click',
|
|
22
|
+
},
|
|
23
|
+
} as const
|
|
24
|
+
|
|
25
|
+
type ShortcutTokenProps = React.ComponentProps<'kbd'> & {
|
|
26
|
+
value: string
|
|
27
|
+
displayValue?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ShortcutToken({ className, displayValue, value, ...props }: ShortcutTokenProps) {
|
|
31
|
+
const mouseShortcut =
|
|
32
|
+
value in MOUSE_SHORTCUTS ? MOUSE_SHORTCUTS[value as keyof typeof MOUSE_SHORTCUTS] : null
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<kbd
|
|
36
|
+
aria-label={mouseShortcut?.label ?? displayValue ?? value}
|
|
37
|
+
className={cn(
|
|
38
|
+
'inline-flex h-6 items-center rounded border border-border bg-muted px-2 font-medium font-mono text-[11px] text-muted-foreground',
|
|
39
|
+
mouseShortcut && 'justify-center px-1.5',
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
title={mouseShortcut?.label ?? value}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
{mouseShortcut ? (
|
|
46
|
+
<>
|
|
47
|
+
<Icon
|
|
48
|
+
aria-hidden="true"
|
|
49
|
+
className="shrink-0"
|
|
50
|
+
color="currentColor"
|
|
51
|
+
height={14}
|
|
52
|
+
icon={mouseShortcut.icon}
|
|
53
|
+
width={14}
|
|
54
|
+
/>
|
|
55
|
+
<span className="sr-only">{mouseShortcut.label}</span>
|
|
56
|
+
</>
|
|
57
|
+
) : (
|
|
58
|
+
(displayValue ?? value)
|
|
59
|
+
)}
|
|
60
|
+
</kbd>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { ShortcutToken }
|