UX polish: selection behavior, styling, and site panel improvements (#119)
* Remove .env.local guard; add portless dep
Remove the check that blocked a local .env.local in apps/editor's dev script and simplify the dev startup to source the root .env. Add portless@^0.4.2 to the project dependencies in package.json. Lockfile updated accordingly.
* Improve tool cursors, previews, and contextual tools
Revamp editor tool visuals and behaviour: replace mesh cursors with a new CursorSphere Group (adds ground marker, vertical indicator and optional tool icon tooltip), unify preview colors to #818cf8, add ground-level previews and ghost lines for ceiling and roof tools, and update slab/zone/wall visuals to match. Add automatic build-mode switching and contextual highlighting for structure tools via a new useContextualTools hook, and relax action-menu visibility checks so tool rows are shown even when not in build mode. Minor typing/import adjustments and small opacity/placement tweaks across multiple tool components.
* Keep tools active and refine action-menu styles
Stop auto-deactivating tools in RoofTool and ZoneTool by removing setTool(null) (and setMode('select') in roof). Add hasActiveTool checks and small layout padding to FurnishTools and StructureTools, and update button classes to improve visual emphasis: smoother transitions, stronger active rings/scale, and dimming/grayscale of non-active tools when another tool is active. These changes improve UX by keeping the selected tool active and giving clearer visual feedback in the action menu.
* Improve selection behavior and refresh UI styling
SelectionManager: add global enter/leave/double-click handlers to support cross-phase hovering and auto-switching into structure/furnish on double-click; normalize click handling (explicitly coalesce shiftKey). Remove per-strategy enter/leave listeners.
UI panels & controls: unify and modernize styles across many panels (ceiling, door, item, reference, roof, slab, wall, window) and action buttons — rounded-md, neutral borders, subtle shadows, translucent headers and consistent hover/focus states. Update view toggles icons and toggle styling.
NumberInput: integrate @number-flow/react NumberFlow for formatted value display, add visual drag progress indicator and refine input/label interactions and styling.
Sidebar: replace rename-popover with inline-rename-input and update tree nodes to use inline editing. Add slider-demo and tweak slider implementation. Update package.json accordingly.
* Use Barlow font across editor UI
Add the Google Barlow font and expose it as a CSS variable, update theme font variables in globals.css, and apply the new font across the editor UI. Imports: add Barlow in app/layout.tsx and include its variable on <html>, set body to use font-sans. Apply font-barlow or font-mono classes to headings, labels, numeric readouts and buttons across multiple panels and primitives to standardize typography. Also update button base variant to include font-barlow. Remove the PhaseSwitcher component and its import/usage from the action menu.
* Replace button wrapper with div in LevelItem
Replace the outer <button> around the LevelItem content with a <div> to avoid nesting interactive button elements (and the resulting invalid HTML/interaction issues). Preserves the original classes and onClick/onDoubleClick handlers so behavior remains the same and allows the PopoverTrigger/button children to function correctly.
* Refine site panel list styles and spacing
Update the SitePanel and TreeNode UI to use compact, border-bottom list rows instead of rounded, card-like items. Simplified active/hover states to use bg-accent variants and reduced shadow/ring styles for a cleaner look. Adjusted paddings (pl/px/py) and container layouts (many sections switched to flex-col) and increased tree node indent/paddingRight for consistent alignment. These changes unify spacing and visual hierarchy across levels, buildings, zones and tree nodes.
* Add auto-scroll to selected items and refine icon styles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
94e5f37983
commit
48d5091bb3
@@ -119,16 +119,16 @@ export function CeilingPanel() {
|
||||
return (
|
||||
<div className="pointer-events-auto fixed top-20 right-4 z-50 flex w-72 flex-col overflow-hidden rounded-lg border border-border bg-background/95 shadow-xl backdrop-blur-md">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between gap-2 border-b p-3">
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border/50 p-3 bg-white/50 dark:bg-transparent">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Image src="/icons/ceiling.png" alt="" width={16} height={16} className="shrink-0 object-contain" />
|
||||
<h2 className="font-semibold text-foreground text-sm truncate">
|
||||
<h2 className="font-semibold font-barlow text-foreground text-sm truncate">
|
||||
{node.name || `Ceiling (${area.toFixed(1)}m²)`}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground cursor-pointer"
|
||||
className="shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/10 hover:text-foreground cursor-pointer"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
@@ -140,7 +140,7 @@ export function CeilingPanel() {
|
||||
<div className="space-y-4">
|
||||
{/* Height */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Height
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -162,27 +162,27 @@ export function CeilingPanel() {
|
||||
|
||||
{/* Quick preset buttons */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Presets
|
||||
</label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={() => handleUpdate({ height: 2.4 })}
|
||||
>
|
||||
Low (2.4m)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={() => handleUpdate({ height: 2.5 })}
|
||||
>
|
||||
Standard (2.5m)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={() => handleUpdate({ height: 3.0 })}
|
||||
>
|
||||
High (3m)
|
||||
@@ -192,10 +192,10 @@ export function CeilingPanel() {
|
||||
|
||||
{/* Area info */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Area
|
||||
</label>
|
||||
<div className="rounded border border-border bg-muted/50 px-3 py-2 text-sm">
|
||||
<div className="rounded-lg border border-neutral-200/60 dark:border-border/50 bg-white/50 dark:bg-accent/30 shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-3 py-2 text-sm text-foreground">
|
||||
{area.toFixed(2)} m²
|
||||
</div>
|
||||
</div>
|
||||
@@ -203,13 +203,13 @@ export function CeilingPanel() {
|
||||
{/* Holes */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Holes
|
||||
</label>
|
||||
{editingHole?.nodeId === selectedId ? (
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-1 rounded border border-green-500 bg-green-500/10 px-2 py-1 text-xs text-green-600 hover:bg-green-500/20 cursor-pointer"
|
||||
className="flex items-center gap-1 rounded-md border border-green-500 bg-green-500/10 shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium text-green-600 hover:bg-green-500/20 transition-colors cursor-pointer"
|
||||
onClick={() => setEditingHole(null)}
|
||||
>
|
||||
<span>Done Editing</span>
|
||||
@@ -217,7 +217,7 @@ export function CeilingPanel() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-1 rounded border border-border px-2 py-1 text-xs hover:bg-accent cursor-pointer"
|
||||
className="flex items-center gap-1 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={handleAddHole}
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
@@ -233,10 +233,10 @@ export function CeilingPanel() {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex items-center justify-between rounded border px-3 py-2 ${
|
||||
className={`flex items-center justify-between rounded-lg border px-3 py-2 shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] transition-colors ${
|
||||
isEditing
|
||||
? 'border-green-500 bg-green-500/10'
|
||||
: 'border-border bg-muted/30'
|
||||
? 'border-green-500 bg-green-500/10 ring-1 ring-green-500/20'
|
||||
: 'border-neutral-200/60 dark:border-border/50 bg-white/50 dark:bg-accent/30'
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
|
||||
@@ -138,16 +138,16 @@ export function DoorPanel() {
|
||||
return (
|
||||
<div className="pointer-events-auto fixed top-20 right-4 z-50 flex w-82 flex-col overflow-hidden rounded-lg border border-border bg-background/95 shadow-xl backdrop-blur-md max-h-[calc(100dvh-100px)]">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between gap-2 border-b p-3">
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border/50 p-3 bg-white/50 dark:bg-transparent">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Image src="/icons/door.png" alt="" width={16} height={16} className="shrink-0 object-contain" />
|
||||
<h2 className="font-semibold text-foreground text-sm truncate">
|
||||
<h2 className="font-semibold font-barlow text-foreground text-sm truncate">
|
||||
{node.name || `Door (${node.width}×${node.height}m)`}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground cursor-pointer"
|
||||
className="shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/10 hover:text-foreground cursor-pointer"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
@@ -159,7 +159,7 @@ export function DoorPanel() {
|
||||
|
||||
{/* Position */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Position
|
||||
</label>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
@@ -172,7 +172,7 @@ export function DoorPanel() {
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex items-center justify-center gap-1.5 rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="w-full flex items-center justify-center gap-1.5 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={handleFlip}
|
||||
>
|
||||
<FlipHorizontal2 className="h-3.5 w-3.5" />
|
||||
@@ -182,7 +182,7 @@ export function DoorPanel() {
|
||||
|
||||
{/* Dimensions */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Dimensions
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
@@ -213,7 +213,7 @@ export function DoorPanel() {
|
||||
|
||||
{/* Frame */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Frame
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
@@ -246,7 +246,7 @@ export function DoorPanel() {
|
||||
|
||||
{/* Content Padding */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Content Padding
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
@@ -279,22 +279,22 @@ export function DoorPanel() {
|
||||
|
||||
{/* Swing */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Swing
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="space-y-1">
|
||||
<span className="text-xs text-muted-foreground">Hinges</span>
|
||||
<div className="flex gap-1">
|
||||
<div className="flex gap-1 p-1 bg-accent/50 rounded-lg">
|
||||
{(['left', 'right'] as const).map((side) => (
|
||||
<button
|
||||
key={side}
|
||||
type="button"
|
||||
onClick={() => handleUpdate({ hingesSide: side })}
|
||||
className={`flex-1 rounded border px-2 py-1 text-xs cursor-pointer transition-colors ${
|
||||
className={`flex-1 rounded-md px-2 py-1 text-xs font-medium cursor-pointer transition-all duration-200 ${
|
||||
node.hingesSide === side
|
||||
? 'border-primary bg-primary text-primary-foreground'
|
||||
: 'border-border hover:bg-accent'
|
||||
? 'bg-white dark:bg-background shadow-sm ring-1 ring-black/5 dark:ring-white/10 text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-white/50 dark:hover:bg-accent/50'
|
||||
}`}
|
||||
>
|
||||
{side.charAt(0).toUpperCase() + side.slice(1)}
|
||||
@@ -304,7 +304,7 @@ export function DoorPanel() {
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<span className="text-xs text-muted-foreground">Direction</span>
|
||||
<div className="flex gap-1">
|
||||
<div className="flex gap-1 p-1 bg-accent/50 rounded-lg">
|
||||
{(['inward', 'outward'] as const).map((dir) => (
|
||||
<button
|
||||
key={dir}
|
||||
@@ -327,7 +327,7 @@ export function DoorPanel() {
|
||||
{/* Threshold */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Threshold
|
||||
</label>
|
||||
<Switch
|
||||
@@ -354,7 +354,7 @@ export function DoorPanel() {
|
||||
{/* Handle */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Handle
|
||||
</label>
|
||||
<Switch
|
||||
@@ -379,7 +379,7 @@ export function DoorPanel() {
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<span className="text-xs text-muted-foreground">Side</span>
|
||||
<div className="flex gap-1">
|
||||
<div className="flex gap-1 p-1 bg-accent/50 rounded-lg">
|
||||
{(['left', 'right'] as const).map((side) => (
|
||||
<button
|
||||
key={side}
|
||||
@@ -402,7 +402,7 @@ export function DoorPanel() {
|
||||
|
||||
{/* Hardware */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Hardware
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
@@ -440,7 +440,7 @@ export function DoorPanel() {
|
||||
|
||||
{/* Segments */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Leaf segments (top → bottom)
|
||||
</label>
|
||||
{node.segments.map((seg, i) => {
|
||||
@@ -451,7 +451,7 @@ export function DoorPanel() {
|
||||
<div key={i} className="rounded border border-border p-2 space-y-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-xs text-muted-foreground">Segment {i + 1}</span>
|
||||
<div className="flex gap-1">
|
||||
<div className="flex gap-1 p-1 bg-accent/50 rounded-lg">
|
||||
{(['panel', 'glass', 'empty'] as const).map((t) => (
|
||||
<button
|
||||
key={t}
|
||||
@@ -584,7 +584,7 @@ export function DoorPanel() {
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="flex-1 rounded border border-border px-2 py-1 text-xs hover:bg-accent cursor-pointer"
|
||||
className="flex-1 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={() => {
|
||||
const updated = [
|
||||
...node.segments,
|
||||
@@ -598,7 +598,7 @@ export function DoorPanel() {
|
||||
{node.segments.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
className="flex-1 rounded border border-border px-2 py-1 text-xs hover:bg-accent cursor-pointer"
|
||||
className="flex-1 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={() => {
|
||||
handleUpdate({ segments: node.segments.slice(0, -1) })
|
||||
}}
|
||||
@@ -611,11 +611,11 @@ export function DoorPanel() {
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="border-t p-3">
|
||||
<div className="border-t border-border/50 p-3 bg-white/50 dark:bg-transparent">
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={handleMove}
|
||||
>
|
||||
<Move className="h-3.5 w-3.5" />
|
||||
@@ -623,7 +623,7 @@ export function DoorPanel() {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={handleDuplicate}
|
||||
>
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
@@ -631,7 +631,7 @@ export function DoorPanel() {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
|
||||
@@ -84,7 +84,7 @@ export function ItemPanel() {
|
||||
return (
|
||||
<div className="pointer-events-auto fixed top-20 right-4 z-50 flex w-72 flex-col overflow-hidden rounded-lg border border-border bg-background/95 shadow-xl backdrop-blur-md">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between gap-2 border-b p-3">
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border/50 p-3 bg-white/50 dark:bg-transparent">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Image
|
||||
src={node.asset.thumbnail || '/icons/furniture.png'}
|
||||
@@ -93,13 +93,13 @@ export function ItemPanel() {
|
||||
height={16}
|
||||
className="shrink-0 object-contain"
|
||||
/>
|
||||
<h2 className="font-semibold text-foreground text-sm truncate">
|
||||
<h2 className="font-semibold font-barlow text-foreground text-sm truncate">
|
||||
{node.name || node.asset.name}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground cursor-pointer"
|
||||
className="shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/10 hover:text-foreground cursor-pointer"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
@@ -111,7 +111,7 @@ export function ItemPanel() {
|
||||
<div className="space-y-4">
|
||||
{/* Position */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Position
|
||||
</label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
@@ -144,7 +144,7 @@ export function ItemPanel() {
|
||||
|
||||
{/* Rotation */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Rotation
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -193,7 +193,7 @@ export function ItemPanel() {
|
||||
{/* Scale */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Scale
|
||||
</label>
|
||||
<button
|
||||
@@ -251,10 +251,10 @@ export function ItemPanel() {
|
||||
|
||||
{/* Dimensions (effective, read-only) */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Dimensions
|
||||
</label>
|
||||
<div className="rounded border border-border bg-muted/50 px-3 py-2 text-sm">
|
||||
<div className="rounded-lg border border-neutral-200/60 dark:border-border/50 bg-white/50 dark:bg-accent/30 shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-3 py-2 text-sm font-mono text-foreground">
|
||||
{(() => {
|
||||
const [w, h, d] = getScaledDimensions(node)
|
||||
return `${Math.round(w * 100) / 100}m × ${Math.round(h * 100) / 100}m × ${Math.round(d * 100) / 100}m`
|
||||
@@ -265,11 +265,11 @@ export function ItemPanel() {
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="border-t p-3">
|
||||
<div className="border-t border-border/50 p-3 bg-white/50 dark:bg-transparent">
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={handleMove}
|
||||
>
|
||||
<Move className="h-3.5 w-3.5" />
|
||||
@@ -277,7 +277,7 @@ export function ItemPanel() {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={handleDuplicate}
|
||||
>
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
@@ -285,7 +285,7 @@ export function ItemPanel() {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
|
||||
@@ -37,19 +37,19 @@ export function ReferencePanel() {
|
||||
return (
|
||||
<div className="pointer-events-auto fixed top-20 right-4 z-50 flex w-72 flex-col overflow-hidden rounded-lg border border-border bg-background/95 shadow-xl backdrop-blur-md">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between gap-2 border-b p-3">
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border/50 p-3 bg-white/50 dark:bg-transparent">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
{isScan ? (
|
||||
<Box className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
) : (
|
||||
<Image className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
)}
|
||||
<h2 className="font-semibold text-foreground text-sm truncate">
|
||||
<h2 className="font-semibold font-barlow text-foreground text-sm truncate">
|
||||
{node.name || (isScan ? '3D Scan' : 'Guide Image')}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
className="shrink-0 rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground cursor-pointer"
|
||||
className="shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/10 hover:text-foreground cursor-pointer"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
@@ -61,7 +61,7 @@ export function ReferencePanel() {
|
||||
<div className="space-y-4">
|
||||
{/* Position */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Position
|
||||
</label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
@@ -83,7 +83,7 @@ export function ReferencePanel() {
|
||||
|
||||
{/* Rotation Y */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Rotation
|
||||
</label>
|
||||
<div className="flex items-center gap-1.5">
|
||||
@@ -101,7 +101,7 @@ export function ReferencePanel() {
|
||||
/>
|
||||
<span className="text-muted-foreground text-xs shrink-0">°</span>
|
||||
<button
|
||||
className="shrink-0 rounded border border-border px-1.5 py-0.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="shrink-0 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-1.5 py-1 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={() =>
|
||||
handleUpdate({
|
||||
rotation: [node.rotation[0], node.rotation[1] - Math.PI / 4, node.rotation[2]],
|
||||
@@ -111,7 +111,7 @@ export function ReferencePanel() {
|
||||
−45
|
||||
</button>
|
||||
<button
|
||||
className="shrink-0 rounded border border-border px-1.5 py-0.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="shrink-0 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-1.5 py-1 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={() =>
|
||||
handleUpdate({
|
||||
rotation: [node.rotation[0], node.rotation[1] + Math.PI / 4, node.rotation[2]],
|
||||
@@ -125,7 +125,7 @@ export function ReferencePanel() {
|
||||
|
||||
{/* Scale */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Scale
|
||||
</label>
|
||||
<NumberInput
|
||||
@@ -144,10 +144,10 @@ export function ReferencePanel() {
|
||||
{/* Opacity */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Opacity
|
||||
</label>
|
||||
<span className="text-muted-foreground text-xs">{node.opacity}%</span>
|
||||
<span className="text-muted-foreground font-mono text-xs">{node.opacity}%</span>
|
||||
</div>
|
||||
<input
|
||||
className="w-full cursor-pointer"
|
||||
|
||||
@@ -39,16 +39,16 @@ export function RoofPanel() {
|
||||
return (
|
||||
<div className="pointer-events-auto fixed top-20 right-4 z-50 flex w-72 flex-col overflow-hidden rounded-lg border border-border bg-background/95 shadow-xl backdrop-blur-md">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between gap-2 border-b p-3">
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border/50 p-3 bg-white/50 dark:bg-transparent">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Image src="/icons/roof.png" alt="" width={16} height={16} className="shrink-0 object-contain" />
|
||||
<h2 className="font-semibold text-foreground text-sm truncate">
|
||||
<h2 className="font-semibold font-barlow text-foreground text-sm truncate">
|
||||
{node.name || 'Gable Roof'}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground cursor-pointer"
|
||||
className="shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/10 hover:text-foreground cursor-pointer"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
@@ -60,12 +60,12 @@ export function RoofPanel() {
|
||||
<div className="space-y-4">
|
||||
{/* Length */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Length
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
className="flex-1 rounded border border-input bg-background px-2 py-1 text-foreground text-sm outline-none focus:border-primary"
|
||||
className="flex-1 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1 text-foreground text-sm outline-none focus:ring-1 focus:ring-primary focus:border-primary"
|
||||
min="0.5"
|
||||
onChange={(e) => {
|
||||
const value = Number.parseFloat(e.target.value)
|
||||
@@ -83,12 +83,12 @@ export function RoofPanel() {
|
||||
|
||||
{/* Height */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Height
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
className="flex-1 rounded border border-input bg-background px-2 py-1 text-foreground text-sm outline-none focus:border-primary"
|
||||
className="flex-1 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1 text-foreground text-sm outline-none focus:ring-1 focus:ring-primary focus:border-primary"
|
||||
min="0.1"
|
||||
onChange={(e) => {
|
||||
const value = Number.parseFloat(e.target.value)
|
||||
@@ -107,7 +107,7 @@ export function RoofPanel() {
|
||||
{/* Slope Widths */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Slope Widths
|
||||
</label>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
@@ -119,7 +119,7 @@ export function RoofPanel() {
|
||||
<label className="text-muted-foreground text-xs">Left</label>
|
||||
<div className="flex items-center gap-1">
|
||||
<input
|
||||
className="w-full rounded border border-input bg-background px-2 py-1 text-foreground text-sm outline-none focus:border-primary"
|
||||
className="w-full rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1 text-foreground text-sm outline-none focus:ring-1 focus:ring-primary focus:border-primary"
|
||||
min="0.1"
|
||||
onChange={(e) => {
|
||||
const value = Number.parseFloat(e.target.value)
|
||||
@@ -138,7 +138,7 @@ export function RoofPanel() {
|
||||
<label className="text-muted-foreground text-xs">Right</label>
|
||||
<div className="flex items-center gap-1">
|
||||
<input
|
||||
className="w-full rounded border border-input bg-background px-2 py-1 text-foreground text-sm outline-none focus:border-primary"
|
||||
className="w-full rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1 text-foreground text-sm outline-none focus:ring-1 focus:ring-primary focus:border-primary"
|
||||
min="0.1"
|
||||
onChange={(e) => {
|
||||
const value = Number.parseFloat(e.target.value)
|
||||
@@ -158,12 +158,12 @@ export function RoofPanel() {
|
||||
|
||||
{/* Rotation */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Rotation
|
||||
</label>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<input
|
||||
className="min-w-0 flex-1 rounded border border-input bg-background px-2 py-1 text-foreground text-sm outline-none focus:border-primary"
|
||||
className="min-w-0 flex-1 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1 text-foreground text-sm outline-none focus:ring-1 focus:ring-primary focus:border-primary"
|
||||
onChange={(e) => {
|
||||
const degrees = Number.parseFloat(e.target.value)
|
||||
if (!Number.isNaN(degrees)) {
|
||||
@@ -178,7 +178,7 @@ export function RoofPanel() {
|
||||
<span className="text-muted-foreground text-xs shrink-0">°</span>
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 rounded border border-border px-1.5 py-0.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="shrink-0 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-1.5 py-1 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={() => {
|
||||
const newRotation = node.rotation - Math.PI / 2
|
||||
handleUpdate({ rotation: newRotation })
|
||||
@@ -188,7 +188,7 @@ export function RoofPanel() {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 rounded border border-border px-1.5 py-0.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="shrink-0 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-1.5 py-1 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={() => {
|
||||
const newRotation = node.rotation + Math.PI / 2
|
||||
handleUpdate({ rotation: newRotation })
|
||||
@@ -201,7 +201,7 @@ export function RoofPanel() {
|
||||
|
||||
{/* Position */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Position
|
||||
</label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
@@ -209,7 +209,7 @@ export function RoofPanel() {
|
||||
<div key={i} className="space-y-1">
|
||||
<label className="text-muted-foreground text-xs">{['X', 'Y', 'Z'][i]}</label>
|
||||
<input
|
||||
className="w-full rounded border border-input bg-background px-2 py-1 text-foreground text-sm outline-none focus:border-primary"
|
||||
className="w-full rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1 text-foreground text-sm outline-none focus:ring-1 focus:ring-primary focus:border-primary"
|
||||
onChange={(e) => {
|
||||
const value = Number.parseFloat(e.target.value)
|
||||
if (!Number.isNaN(value)) {
|
||||
|
||||
@@ -119,16 +119,16 @@ export function SlabPanel() {
|
||||
return (
|
||||
<div className="pointer-events-auto fixed top-20 right-4 z-50 flex w-72 flex-col overflow-hidden rounded-lg border border-border bg-background/95 shadow-xl backdrop-blur-md">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between gap-2 border-b p-3">
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border/50 p-3 bg-white/50 dark:bg-transparent">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Image src="/icons/floor.png" alt="" width={16} height={16} className="shrink-0 object-contain" />
|
||||
<h2 className="font-semibold text-foreground text-sm truncate">
|
||||
<h2 className="font-semibold font-barlow text-foreground text-sm truncate">
|
||||
{node.name || `Slab (${area.toFixed(1)}m²)`}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground cursor-pointer"
|
||||
className="shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/10 hover:text-foreground cursor-pointer"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
@@ -140,7 +140,7 @@ export function SlabPanel() {
|
||||
<div className="space-y-4">
|
||||
{/* Elevation */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Elevation
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -162,34 +162,34 @@ export function SlabPanel() {
|
||||
|
||||
{/* Quick preset buttons */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Presets
|
||||
</label>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={() => handleUpdate({ elevation: -0.15 })}
|
||||
>
|
||||
Sunken (-15cm)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={() => handleUpdate({ elevation: 0 })}
|
||||
>
|
||||
Ground (0m)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={() => handleUpdate({ elevation: 0.05 })}
|
||||
>
|
||||
Raised (5cm)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={() => handleUpdate({ elevation: 0.15 })}
|
||||
>
|
||||
Step (15cm)
|
||||
@@ -199,10 +199,10 @@ export function SlabPanel() {
|
||||
|
||||
{/* Area info */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Area
|
||||
</label>
|
||||
<div className="rounded border border-border bg-muted/50 px-3 py-2 text-sm">
|
||||
<div className="rounded-lg border border-neutral-200/60 dark:border-border/50 bg-white/50 dark:bg-accent/30 shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-3 py-2 text-sm text-foreground">
|
||||
{area.toFixed(2)} m²
|
||||
</div>
|
||||
</div>
|
||||
@@ -210,13 +210,13 @@ export function SlabPanel() {
|
||||
{/* Holes */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Holes
|
||||
</label>
|
||||
{editingHole?.nodeId === selectedId ? (
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-1 rounded border border-green-500 bg-green-500/10 px-2 py-1 text-xs text-green-600 hover:bg-green-500/20 cursor-pointer"
|
||||
className="flex items-center gap-1 rounded-md border border-green-500 bg-green-500/10 shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium text-green-600 hover:bg-green-500/20 transition-colors cursor-pointer"
|
||||
onClick={() => setEditingHole(null)}
|
||||
>
|
||||
<span>Done Editing</span>
|
||||
@@ -224,7 +224,7 @@ export function SlabPanel() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-1 rounded border border-border px-2 py-1 text-xs hover:bg-accent cursor-pointer"
|
||||
className="flex items-center gap-1 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={handleAddHole}
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
@@ -240,10 +240,10 @@ export function SlabPanel() {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex items-center justify-between rounded border px-3 py-2 ${
|
||||
className={`flex items-center justify-between rounded-lg border px-3 py-2 shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] transition-colors ${
|
||||
isEditing
|
||||
? 'border-green-500 bg-green-500/10'
|
||||
: 'border-border bg-muted/30'
|
||||
? 'border-green-500 bg-green-500/10 ring-1 ring-green-500/20'
|
||||
: 'border-neutral-200/60 dark:border-border/50 bg-white/50 dark:bg-accent/30'
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
|
||||
@@ -43,16 +43,16 @@ export function WallPanel() {
|
||||
return (
|
||||
<div className="pointer-events-auto fixed top-20 right-4 z-50 flex w-64 flex-col overflow-hidden rounded-lg border border-border bg-background/95 shadow-xl backdrop-blur-md">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between gap-2 border-b p-3">
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border/50 p-3 bg-white/50 dark:bg-transparent">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Image src="/icons/wall.png" alt="" width={16} height={16} className="shrink-0 object-contain" />
|
||||
<h2 className="font-semibold text-foreground text-sm truncate">
|
||||
<h2 className="font-semibold font-barlow text-foreground text-sm truncate">
|
||||
{node.name || `Wall (${length.toFixed(2)}m)`}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground cursor-pointer"
|
||||
className="shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/10 hover:text-foreground cursor-pointer"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
@@ -64,7 +64,7 @@ export function WallPanel() {
|
||||
|
||||
{/* Dimensions */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Dimensions
|
||||
</label>
|
||||
<div className="flex items-center gap-1.5">
|
||||
@@ -95,10 +95,10 @@ export function WallPanel() {
|
||||
|
||||
{/* Info */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Info
|
||||
</label>
|
||||
<div className="rounded border border-border bg-muted/50 px-3 py-2 text-sm">
|
||||
<div className="rounded-lg border border-neutral-200/60 dark:border-border/50 bg-white/50 dark:bg-accent/30 shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-3 py-2 text-sm font-mono text-foreground">
|
||||
Length: {length.toFixed(2)} m
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -127,16 +127,16 @@ export function WindowPanel() {
|
||||
return (
|
||||
<div className="pointer-events-auto fixed top-20 right-4 z-50 flex w-82 flex-col overflow-hidden rounded-lg border border-border bg-background/95 shadow-xl backdrop-blur-md">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between gap-2 border-b p-3">
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border/50 p-3 bg-white/50 dark:bg-transparent">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Image src="/icons/window.png" alt="" width={16} height={16} className="shrink-0 object-contain" />
|
||||
<h2 className="font-semibold text-foreground text-sm truncate">
|
||||
<h2 className="font-semibold font-barlow text-foreground text-sm truncate">
|
||||
{node.name || `Window (${node.width}×${node.height}m)`}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground cursor-pointer"
|
||||
className="shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/10 hover:text-foreground cursor-pointer"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
@@ -148,7 +148,7 @@ export function WindowPanel() {
|
||||
|
||||
{/* Position */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Position
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
@@ -167,7 +167,7 @@ export function WindowPanel() {
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex items-center justify-center gap-1.5 rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="w-full flex items-center justify-center gap-1.5 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={handleFlip}
|
||||
>
|
||||
<FlipHorizontal2 className="h-3.5 w-3.5" />
|
||||
@@ -177,7 +177,7 @@ export function WindowPanel() {
|
||||
|
||||
{/* Dimensions */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Dimensions
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
@@ -208,7 +208,7 @@ export function WindowPanel() {
|
||||
|
||||
{/* Frame */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Frame
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
@@ -241,7 +241,7 @@ export function WindowPanel() {
|
||||
|
||||
{/* Grid */}
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Grid
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
@@ -343,7 +343,7 @@ export function WindowPanel() {
|
||||
{/* Sill */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||
<label className="font-medium font-barlow text-muted-foreground text-xs uppercase tracking-wide">
|
||||
Sill
|
||||
</label>
|
||||
<Switch
|
||||
@@ -383,11 +383,11 @@ export function WindowPanel() {
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="border-t p-3">
|
||||
<div className="border-t border-border/50 p-3 bg-white/50 dark:bg-transparent">
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={handleMove}
|
||||
>
|
||||
<Move className="h-3.5 w-3.5" />
|
||||
@@ -395,7 +395,7 @@ export function WindowPanel() {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={handleDuplicate}
|
||||
>
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
@@ -403,7 +403,7 @@ export function WindowPanel() {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 rounded-md border border-neutral-200/60 dark:border-border/50 bg-white dark:bg-background shadow-[0_1px_2px_0px_rgba(0,0,0,0.05)] px-2 py-1.5 text-xs font-medium font-barlow text-foreground hover:bg-black/5 dark:hover:bg-white/10 transition-colors cursor-pointer"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
|
||||
Reference in New Issue
Block a user