Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-05-14 11:28:24 +00:00
parent bb4c82c2cc
commit 0147fa80fa
+102
View File
@@ -0,0 +1,102 @@
import { Maximize, Check } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { useModelStore, SCALE_PRESETS, type ScaleRatio } from '@/stores/useModelStore';
import { toast } from 'sonner';
interface ScaleSelectorProps {
/** "compact" = small icon-style for XR HUD, "default" = labeled button for 2D viewer */
variant?: 'default' | 'compact';
}
export function ScaleSelector({ variant = 'default' }: ScaleSelectorProps) {
const { scaleRatio, setScaleRatio, resetFineTuning } = useModelStore();
const reductions = SCALE_PRESETS.filter(p => p.factor < 1);
const enlargements = SCALE_PRESETS.filter(p => p.factor > 1);
const oneToOne = SCALE_PRESETS.find(p => p.label === '1:1')!;
const apply = (r: ScaleRatio) => {
setScaleRatio(r);
// Reset two-hand scale gesture so it doesn't fight the preset.
if (r.label !== '1:1') resetFineTuning();
toast.success(`Escala ${r.label}`, {
description: 'Medições e ajuste fino mostrados em mm reais.',
});
};
const Trigger = variant === 'compact' ? (
<Button variant="outline" size="sm" className="h-8 gap-1.5 text-[10px] font-mono" title="Escala do modelo">
<Maximize className="h-3.5 w-3.5" />
{scaleRatio.label}
</Button>
) : (
<Button variant={scaleRatio.label !== '1:1' ? 'default' : 'outline'} size="sm" className="gap-2 h-9">
<Maximize className="h-3.5 w-3.5" />
<span className="font-mono text-xs">Escala {scaleRatio.label}</span>
</Button>
);
return (
<Popover>
<PopoverTrigger asChild>{Trigger}</PopoverTrigger>
<PopoverContent className="w-72 space-y-3" side="top" align="start">
<div>
<p className="font-mono text-[10px] uppercase tracking-widest text-muted-foreground mb-1">
Escala atual
</p>
<p className="font-mono text-sm font-bold text-primary">{scaleRatio.label}</p>
<p className="text-[10px] text-muted-foreground mt-1">
Medições, ajuste fino e snap são compensados todos os valores exibidos continuam em mm reais.
</p>
</div>
<div>
<p className="font-mono text-[10px] uppercase tracking-widest text-muted-foreground mb-1.5">
Real
</p>
<PresetButton current={scaleRatio} preset={oneToOne} onSelect={apply} />
</div>
<div>
<p className="font-mono text-[10px] uppercase tracking-widest text-muted-foreground mb-1.5">
Reduzir (modelo menor)
</p>
<div className="grid grid-cols-3 gap-1.5">
{reductions.map(p => (
<PresetButton key={p.label} current={scaleRatio} preset={p} onSelect={apply} />
))}
</div>
</div>
<div>
<p className="font-mono text-[10px] uppercase tracking-widest text-muted-foreground mb-1.5">
Ampliar (modelo maior)
</p>
<div className="grid grid-cols-3 gap-1.5">
{enlargements.map(p => (
<PresetButton key={p.label} current={scaleRatio} preset={p} onSelect={apply} />
))}
</div>
</div>
</PopoverContent>
</Popover>
);
}
function PresetButton({
preset, current, onSelect,
}: { preset: ScaleRatio; current: ScaleRatio; onSelect: (r: ScaleRatio) => void }) {
const active = preset.label === current.label;
return (
<Button
variant={active ? 'default' : 'outline'}
size="sm"
className="h-8 font-mono text-[11px] gap-1"
onClick={() => onSelect(preset)}
>
{active && <Check className="h-3 w-3" />}
{preset.label}
</Button>
);
}