wip
This commit is contained in:
commit
d8f2407ddf
44 changed files with 10377 additions and 0 deletions
178
src/components/MaterialsEditor.tsx
Normal file
178
src/components/MaterialsEditor.tsx
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
import { Plus, Trash2, ArrowRight, ArrowDown } from 'lucide-react';
|
||||
import type { Project, Room, FlooringSettings, SkirtingSettings } from '../lib/types';
|
||||
import { NumberField } from './Fields';
|
||||
|
||||
interface Props {
|
||||
project: Project;
|
||||
room: Room;
|
||||
changeProject: (fn: (p: Project) => Project) => void;
|
||||
changeRoom: (fn: (r: Room) => Room) => void;
|
||||
}
|
||||
|
||||
const kerfHelp =
|
||||
'Width of material removed by the saw blade. A 3 mm blade turns a 1,000 mm board into 600 mm + 397 mm after one cut.';
|
||||
|
||||
export function MaterialsEditor({ project, room, changeProject, changeRoom }: Props) {
|
||||
const f = project.flooring;
|
||||
const s = project.skirting;
|
||||
const floor = (key: keyof FlooringSettings, value: number | string) =>
|
||||
changeProject((p) => ({ ...p, flooring: { ...p.flooring, [key]: value } }));
|
||||
const skirt = (key: keyof SkirtingSettings, value: number | number[]) =>
|
||||
changeProject((p) => ({ ...p, skirting: { ...p.skirting, [key]: value } }));
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="editor-heading">
|
||||
<h2>Materials</h2>
|
||||
<span>Shared across rooms · mm</span>
|
||||
</div>
|
||||
<section className="material-section flooring" aria-labelledby="flooring-materials">
|
||||
<h3 id="flooring-materials" className="material-heading">
|
||||
Floorboards
|
||||
</h3>
|
||||
<div className="field-grid">
|
||||
<NumberField
|
||||
label="Board length"
|
||||
value={f.length}
|
||||
min={1}
|
||||
onChange={(n) => floor('length', n)}
|
||||
/>
|
||||
<NumberField
|
||||
label="Board width"
|
||||
value={f.width}
|
||||
min={1}
|
||||
onChange={(n) => floor('width', n)}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<span>Laying pattern</span>
|
||||
<div className="segmented" role="group" aria-label="Laying pattern">
|
||||
<button
|
||||
className={f.pattern === 'brick' ? 'active' : ''}
|
||||
aria-pressed={f.pattern === 'brick'}
|
||||
onClick={() => floor('pattern', 'brick')}
|
||||
>
|
||||
Brick
|
||||
</button>
|
||||
<button
|
||||
className={f.pattern === 'random' ? 'active' : ''}
|
||||
aria-pressed={f.pattern === 'random'}
|
||||
onClick={() => floor('pattern', 'random')}
|
||||
>
|
||||
Random
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<span>Board direction · {room.name}</span>
|
||||
<div className="segmented" role="group" aria-label="Board direction">
|
||||
<button
|
||||
className={room.direction === 0 ? 'active' : ''}
|
||||
aria-pressed={room.direction === 0}
|
||||
onClick={() => changeRoom((r) => ({ ...r, direction: 0 }))}
|
||||
>
|
||||
<ArrowRight size={16} aria-hidden="true" /> Along first wall
|
||||
</button>
|
||||
<button
|
||||
className={room.direction === 90 ? 'active' : ''}
|
||||
aria-pressed={room.direction === 90}
|
||||
onClick={() => changeRoom((r) => ({ ...r, direction: 90 }))}
|
||||
>
|
||||
<ArrowDown size={16} aria-hidden="true" /> Across first wall
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field-grid">
|
||||
<NumberField label="Wall spacer" value={f.gap} onChange={(n) => floor('gap', n)} />
|
||||
<NumberField
|
||||
label="Saw kerf"
|
||||
value={f.kerf}
|
||||
onChange={(n) => floor('kerf', n)}
|
||||
tooltip={kerfHelp}
|
||||
/>
|
||||
<NumberField
|
||||
label="Minimum joint offset"
|
||||
value={f.minOffset}
|
||||
onChange={(n) => floor('minOffset', n)}
|
||||
/>
|
||||
<NumberField
|
||||
label="Maximum joint offset"
|
||||
value={f.maxOffset}
|
||||
onChange={(n) => floor('maxOffset', n)}
|
||||
/>
|
||||
<NumberField
|
||||
label="Shortest end piece"
|
||||
value={f.minLength}
|
||||
min={1}
|
||||
onChange={(n) => floor('minLength', n)}
|
||||
/>
|
||||
<NumberField
|
||||
label="Narrowest edge row"
|
||||
value={f.minWidth}
|
||||
min={1}
|
||||
onChange={(n) => floor('minWidth', n)}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<section className="material-section skirting" aria-labelledby="skirting-materials">
|
||||
<h3 id="skirting-materials" className="material-heading">
|
||||
Skirting boards
|
||||
</h3>
|
||||
{s.stockLengths.map((length, i) => (
|
||||
<div className="stock-input" key={i}>
|
||||
<NumberField
|
||||
label={`Stock length ${i + 1}`}
|
||||
value={length}
|
||||
min={1}
|
||||
onChange={(n) =>
|
||||
skirt(
|
||||
'stockLengths',
|
||||
s.stockLengths.map((v, j) => (i === j ? n : v)),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<button
|
||||
className="icon-button"
|
||||
aria-label={`Remove stock length ${i + 1}`}
|
||||
disabled={s.stockLengths.length === 1}
|
||||
onClick={() =>
|
||||
skirt(
|
||||
'stockLengths',
|
||||
s.stockLengths.filter((_, j) => i !== j),
|
||||
)
|
||||
}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
className="text-button"
|
||||
onClick={() => skirt('stockLengths', [...s.stockLengths, 3600])}
|
||||
disabled={s.stockLengths.length >= 10}
|
||||
>
|
||||
<Plus size={16} /> Add a stock length
|
||||
</button>
|
||||
<div className="field-grid">
|
||||
<NumberField
|
||||
label="Skirting thickness"
|
||||
value={s.thickness}
|
||||
onChange={(n) => skirt('thickness', n)}
|
||||
/>
|
||||
<NumberField
|
||||
label="Minimum piece"
|
||||
value={s.minLength}
|
||||
min={1}
|
||||
onChange={(n) => skirt('minLength', n)}
|
||||
/>
|
||||
<NumberField
|
||||
label="Skirting saw kerf"
|
||||
value={s.kerf}
|
||||
onChange={(n) => skirt('kerf', n)}
|
||||
tooltip={kerfHelp}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue