diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2530d25..6e58be9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -182,6 +182,7 @@ export default function App() { custom_colors: { border: data.border_color, border_style: data.border_style, + border_width: data.border_width, background: data.background_color, text_color: data.text_color, text_position: data.text_position, @@ -198,6 +199,7 @@ export default function App() { const handleUpdateGroupRect = useCallback((data: GroupRectFormData) => { if (!editingGroupRectId) return + snapshotHistory() const existing = nodes.find((n) => n.id === editingGroupRectId) updateNode(editingGroupRectId, { label: data.label, @@ -205,6 +207,7 @@ export default function App() { ...existing?.data.custom_colors, border: data.border_color, border_style: data.border_style, + border_width: data.border_width, background: data.background_color, text_color: data.text_color, text_position: data.text_position, @@ -214,7 +217,7 @@ export default function App() { }) setNodeZIndex(editingGroupRectId, data.z_order - 10) setEditingGroupRectId(null) - }, [editingGroupRectId, nodes, updateNode, setNodeZIndex, setEditingGroupRectId]) + }, [editingGroupRectId, nodes, updateNode, setNodeZIndex, setEditingGroupRectId, snapshotHistory]) const handleDeleteGroupRect = useCallback(() => { if (!editingGroupRectId) return @@ -436,7 +439,7 @@ export default function App() { open={addGroupRectOpen} onClose={() => setAddGroupRectOpen(false)} onSubmit={handleAddGroupRect} - title="Add Rectangle" + title="Add Zone" /> {/* key forces re-mount when editing a different rect */} @@ -457,11 +460,12 @@ export default function App() { text_position: rc.text_position ?? 'top-left', border_color: rc.border ?? '#00d4ff', border_style: rc.border_style ?? 'solid', + border_width: rc.border_width ?? 2, background_color: rc.background ?? '#00d4ff0d', z_order: rc.z_order ?? 1, } })()} - title="Edit Rectangle" + title="Edit Zone" /> {/* key forces re-mount on open so useState captures current theme as original */} diff --git a/frontend/src/components/canvas/nodes/GroupRectNode.tsx b/frontend/src/components/canvas/nodes/GroupRectNode.tsx index bd6bfe9..45f2879 100644 --- a/frontend/src/components/canvas/nodes/GroupRectNode.tsx +++ b/frontend/src/components/canvas/nodes/GroupRectNode.tsx @@ -32,6 +32,7 @@ export function GroupRectNode({ id, data, selected }: NodeProps>) const rc = data.custom_colors ?? {} const borderColor = rc.border ?? '#00d4ff' const borderStyle = rc.border_style ?? 'solid' + const borderWidth = rc.border_width ?? 2 const backgroundColor = rc.background ?? 'rgba(0,212,255,0.05)' const textColor = rc.text_color ?? '#e6edf3' const fontFamily = FONT_FAMILIES[rc.font ?? 'inter'] ?? FONT_FAMILIES.inter @@ -62,7 +63,7 @@ export function GroupRectNode({ id, data, selected }: NodeProps>) justifyContent: posStyle.justifyContent, padding: 12, background: backgroundColor, - border: `${selected ? 2 : 1}px ${selected ? 'solid' : borderStyle} ${selected ? '#00d4ff' : borderColor}`, + border: `${selected ? borderWidth + 1 : borderWidth}px ${selected ? 'solid' : borderStyle} ${selected ? '#00d4ff' : borderColor}`, borderRadius: 10, fontFamily, color: textColor, diff --git a/frontend/src/components/modals/GroupRectModal.tsx b/frontend/src/components/modals/GroupRectModal.tsx index ad1b490..b437b67 100644 --- a/frontend/src/components/modals/GroupRectModal.tsx +++ b/frontend/src/components/modals/GroupRectModal.tsx @@ -15,6 +15,7 @@ export interface GroupRectFormData { text_position: TextPosition border_color: string border_style: BorderStyle + border_width: number background_color: string z_order: number } @@ -27,6 +28,14 @@ const BORDER_STYLES: { value: BorderStyle; label: string; preview: string }[] = { value: 'none', label: 'None', preview: ' ' }, ] +const BORDER_WIDTHS: { value: number; label: string }[] = [ + { value: 1, label: '1px' }, + { value: 2, label: '2px' }, + { value: 3, label: '3px' }, + { value: 4, label: '4px' }, + { value: 5, label: '5px' }, +] + const DEFAULT_FORM: GroupRectFormData = { label: '', font: 'inter', @@ -34,6 +43,7 @@ const DEFAULT_FORM: GroupRectFormData = { text_position: 'top-left', border_color: '#00d4ff', border_style: 'solid', + border_width: 2, background_color: '#00d4ff0d', z_order: 1, } @@ -65,7 +75,7 @@ interface GroupRectModalProps { title?: string } -export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, title = 'Add Rectangle' }: GroupRectModalProps) { +export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, title = 'Add Zone' }: GroupRectModalProps) { const [form, setForm] = useState({ ...DEFAULT_FORM, ...initial }) const set = (key: K, value: GroupRectFormData[K]) => @@ -196,6 +206,31 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit + {/* Border width */} +
+ +
+ {BORDER_WIDTHS.map(({ value, label }) => { + const isSelected = form.border_width === value + return ( + + ) + })} +
+
+ {/* Z-order */}
@@ -230,7 +265,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit Cancel
diff --git a/frontend/src/components/modals/__tests__/GroupRectModal.test.tsx b/frontend/src/components/modals/__tests__/GroupRectModal.test.tsx index 896b752..40f1528 100644 --- a/frontend/src/components/modals/__tests__/GroupRectModal.test.tsx +++ b/frontend/src/components/modals/__tests__/GroupRectModal.test.tsx @@ -13,14 +13,15 @@ describe('GroupRectModal', () => { it('renders form fields when open', () => { render() expect(screen.getByPlaceholderText('Zone name…')).toBeDefined() - expect(screen.getByText('Add Rectangle')).toBeDefined() + expect(screen.getByText('Add Zone')).toBeDefined() expect(screen.getByText('Text Position')).toBeDefined() + expect(screen.getByText('Border Width')).toBeDefined() expect(screen.getByText('Z-Order (1 = furthest back)')).toBeDefined() }) - it('renders Edit Rectangle title when provided', () => { - render() - expect(screen.getByText('Edit Rectangle')).toBeDefined() + it('renders Edit Zone title when provided', () => { + render() + expect(screen.getByText('Edit Zone')).toBeDefined() }) it('calls onSubmit with form data on submit', () => { @@ -123,6 +124,46 @@ describe('GroupRectModal', () => { expect(submitted.border_style).toBe('dotted') }) + it('renders Border Width section with 5 options', () => { + render() + expect(screen.getByText('Border Width')).toBeDefined() + expect(screen.getByText('1px')).toBeDefined() + expect(screen.getByText('3px')).toBeDefined() + expect(screen.getByText('5px')).toBeDefined() + }) + + it('defaults border_width to 2', () => { + const onSubmit = vi.fn() + render() + fireEvent.click(screen.getByText('Add')) + const submitted = onSubmit.mock.calls[0][0] as GroupRectFormData + expect(submitted.border_width).toBe(2) + }) + + it('selects border width on click', () => { + const onSubmit = vi.fn() + render() + fireEvent.click(screen.getByText('4px')) + fireEvent.click(screen.getByText('Add')) + const submitted = onSubmit.mock.calls[0][0] as GroupRectFormData + expect(submitted.border_width).toBe(4) + }) + + it('pre-fills border_width from initial prop', () => { + const onSubmit = vi.fn() + render( + + ) + fireEvent.click(screen.getByText('Add')) + const submitted = onSubmit.mock.calls[0][0] as GroupRectFormData + expect(submitted.border_width).toBe(5) + }) + it('toggles border style — clicking selected style deselects back to solid', () => { const onSubmit = vi.fn() render() diff --git a/frontend/src/components/panels/Sidebar.tsx b/frontend/src/components/panels/Sidebar.tsx index 90690c3..3105bdb 100644 --- a/frontend/src/components/panels/Sidebar.tsx +++ b/frontend/src/components/panels/Sidebar.tsx @@ -124,7 +124,7 @@ export function Sidebar({ onAddNode, onAddGroupRect, onScan, onSave, onNodeAppro {/* Actions */}
- + {!STANDALONE && } { load() }) + useEffect(() => { load() }, [load]) const handleIgnore = async (id: string) => { try {