diff --git a/web/package-lock.json b/web/package-lock.json index 2b9e8dd..25905b8 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,6 +8,7 @@ "name": "silo-web", "version": "0.0.0", "dependencies": { + "lucide-react": "^0.564.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.0.0" @@ -1499,6 +1500,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.564.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.564.0.tgz", + "integrity": "sha512-JJ8GVTQqFwuliifD48U6+h7DXEHdkhJ/E87kksGByII3qHxtPciVb8T8woQONHBQgHVOl7rSMrrip3SeVNy7Fg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/web/package.json b/web/package.json index b30bec5..ad2910d 100644 --- a/web/package.json +++ b/web/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "lucide-react": "^0.564.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.0.0" diff --git a/web/src/components/AppShell.tsx b/web/src/components/AppShell.tsx index 92d291b..843bc68 100644 --- a/web/src/components/AppShell.tsx +++ b/web/src/components/AppShell.tsx @@ -113,9 +113,9 @@ export function AppShell() { onClick={toggleDensity} title={`Switch to ${density === "comfortable" ? "compact" : "comfortable"} view`} style={{ - padding: "0.2rem 0.5rem", - fontSize: "0.7rem", - borderRadius: "0.3rem", + padding: "0.25rem 0.5rem", + fontSize: "var(--font-sm)", + borderRadius: "0.375rem", cursor: "pointer", border: "1px solid var(--ctp-surface1)", background: "var(--ctp-surface0)", @@ -130,7 +130,7 @@ export function AppShell() { onClick={logout} style={{ padding: "0.35rem 0.75rem", - fontSize: "0.8rem", + fontSize: "var(--font-table)", borderRadius: "0.4rem", cursor: "pointer", border: "none", diff --git a/web/src/components/ContextMenu.tsx b/web/src/components/ContextMenu.tsx index 13e4e6c..1c53131 100644 --- a/web/src/components/ContextMenu.tsx +++ b/web/src/components/ContextMenu.tsx @@ -1,4 +1,5 @@ -import { useEffect, useRef } from 'react'; +import { useEffect, useRef } from "react"; +import { Check } from "lucide-react"; export interface ContextMenuItem { label: string; @@ -24,76 +25,95 @@ export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) { if (ref.current && !ref.current.contains(e.target as Node)) onClose(); }; const handleKey = (e: KeyboardEvent) => { - if (e.key === 'Escape') onClose(); + if (e.key === "Escape") onClose(); }; const handleScroll = () => onClose(); - document.addEventListener('mousedown', handleClick); - document.addEventListener('keydown', handleKey); - window.addEventListener('scroll', handleScroll, true); + document.addEventListener("mousedown", handleClick); + document.addEventListener("keydown", handleKey); + window.addEventListener("scroll", handleScroll, true); return () => { - document.removeEventListener('mousedown', handleClick); - document.removeEventListener('keydown', handleKey); - window.removeEventListener('scroll', handleScroll, true); + document.removeEventListener("mousedown", handleClick); + document.removeEventListener("keydown", handleKey); + window.removeEventListener("scroll", handleScroll, true); }; }, [onClose]); // Clamp position to viewport const style: React.CSSProperties = { - position: 'fixed', + position: "fixed", left: Math.min(x, window.innerWidth - 220), top: Math.min(y, window.innerHeight - items.length * 32 - 16), zIndex: 9999, - backgroundColor: 'var(--ctp-surface0)', - border: '1px solid var(--ctp-surface1)', - borderRadius: '0.5rem', - padding: '0.25rem 0', + backgroundColor: "var(--ctp-surface0)", + border: "1px solid var(--ctp-surface1)", + borderRadius: "0.5rem", + padding: "0.25rem 0", minWidth: 200, - boxShadow: '0 4px 12px rgba(0,0,0,0.4)', + boxShadow: "0 4px 12px rgba(0,0,0,0.4)", }; return (
{items.map((item, i) => item.divider ? ( -
+
) : ( ))} @@ -166,30 +181,30 @@ export function TagInput({ value, onChange, placeholder, searchFn }: TagInputPro placeholder={value.length === 0 ? placeholder : undefined} style={{ flex: 1, - minWidth: '4rem', - border: 'none', - outline: 'none', - background: 'transparent', - color: 'var(--ctp-text)', - fontSize: '0.85rem', - padding: '0.1rem 0', + minWidth: "4rem", + border: "none", + outline: "none", + background: "transparent", + color: "var(--ctp-text)", + fontSize: "var(--font-body)", + padding: "0.15rem 0", }} />
{open && results.length > 0 && (
{results.map((opt, i) => ( @@ -201,15 +216,15 @@ export function TagInput({ value, onChange, placeholder, searchFn }: TagInputPro }} onMouseEnter={() => setHighlighted(i)} style={{ - padding: '0.25rem 0.5rem', - height: '28px', - display: 'flex', - alignItems: 'center', - fontSize: '0.8rem', - cursor: 'pointer', - color: 'var(--ctp-text)', + padding: "0.25rem 0.5rem", + height: "28px", + display: "flex", + alignItems: "center", + fontSize: "var(--font-table)", + cursor: "pointer", + color: "var(--ctp-text)", backgroundColor: - i === highlighted ? 'var(--ctp-surface1)' : 'transparent', + i === highlighted ? "var(--ctp-surface1)" : "transparent", }} > {opt.label} diff --git a/web/src/components/audit/AuditDetailPanel.tsx b/web/src/components/audit/AuditDetailPanel.tsx index ba5819a..6e013c6 100644 --- a/web/src/components/audit/AuditDetailPanel.tsx +++ b/web/src/components/audit/AuditDetailPanel.tsx @@ -210,7 +210,7 @@ export function AuditDetailPanel({ fontFamily: "'JetBrains Mono', monospace", color: "var(--ctp-peach)", fontWeight: 600, - fontSize: "1rem", + fontSize: "var(--font-body)", }} > {audit.part_number} @@ -263,7 +263,7 @@ export function AuditDetailPanel({ style={{ padding: "0.5rem 1rem", color: "var(--ctp-red)", - fontSize: "0.8rem", + fontSize: "var(--font-table)", }} > {error} @@ -274,7 +274,7 @@ export function AuditDetailPanel({
* @@ -456,7 +456,7 @@ function FieldRow({
diff --git a/web/src/components/audit/AuditTable.tsx b/web/src/components/audit/AuditTable.tsx index c71e095..fe92e72 100644 --- a/web/src/components/audit/AuditTable.tsx +++ b/web/src/components/audit/AuditTable.tsx @@ -55,7 +55,7 @@ export function AuditTable({ style={{ width: "100%", borderCollapse: "collapse", - fontSize: "0.8rem", + fontSize: "var(--font-table)", }} > diff --git a/web/src/components/items/BOMTab.tsx b/web/src/components/items/BOMTab.tsx index 0d1e64a..fc1090c 100644 --- a/web/src/components/items/BOMTab.tsx +++ b/web/src/components/items/BOMTab.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, useCallback } from "react"; +import { Plus, Download } from "lucide-react"; import { get, post, put, del } from "../../api/client"; import type { BOMEntry } from "../../api/types"; @@ -117,11 +118,11 @@ export function BOMTab({ partNumber, isEditor }: BOMTabProps) { }; const inputStyle: React.CSSProperties = { - padding: "0.2rem 0.4rem", - fontSize: "0.8rem", + padding: "0.25rem 0.4rem", + fontSize: "var(--font-table)", backgroundColor: "var(--ctp-base)", border: "1px solid var(--ctp-surface1)", - borderRadius: "0.3rem", + borderRadius: "0.375rem", color: "var(--ctp-text)", width: "100%", }; @@ -225,7 +226,9 @@ export function BOMTab({ partNumber, isEditor }: BOMTabProps) { marginBottom: "0.5rem", }} > - + {entries.length} entries @@ -233,9 +236,14 @@ export function BOMTab({ partNumber, isEditor }: BOMTabProps) { onClick={() => { window.location.href = `/api/items/${encodeURIComponent(partNumber)}/bom/export.csv`; }} - style={toolBtnStyle} + style={{ + ...toolBtnStyle, + display: "inline-flex", + alignItems: "center", + gap: "0.35rem", + }} > - Export CSV + Export CSV {isEditor && ( )}
@@ -254,9 +267,9 @@ export function BOMTab({ partNumber, isEditor }: BOMTabProps) { {isEditor && assemblyCount > 0 && (
@@ -403,12 +416,12 @@ export function BOMTab({ partNumber, isEditor }: BOMTabProps) { } const thStyle: React.CSSProperties = { - padding: "0.3rem 0.5rem", + padding: "0.25rem 0.5rem", textAlign: "left", borderBottom: "1px solid var(--ctp-surface1)", color: "var(--ctp-overlay1)", fontWeight: 600, - fontSize: "0.7rem", + fontSize: "var(--font-sm)", textTransform: "uppercase", letterSpacing: "0.05em", whiteSpace: "nowrap", @@ -438,12 +451,12 @@ const actionBtnStyle: React.CSSProperties = { cursor: "pointer", fontSize: "0.75rem", fontWeight: 500, - padding: "0.1rem 0.3rem", + padding: "0.15rem 0.25rem", borderRadius: "0.375rem", }; const saveBtnStyle: React.CSSProperties = { - padding: "0.2rem 0.4rem", + padding: "0.25rem 0.4rem", fontSize: "0.75rem", fontWeight: 500, border: "none", @@ -455,9 +468,9 @@ const saveBtnStyle: React.CSSProperties = { }; const sourceBadgeBase: React.CSSProperties = { - padding: "0.1rem 0.4rem", + padding: "0.15rem 0.4rem", borderRadius: "1rem", - fontSize: "0.7rem", + fontSize: "var(--font-sm)", fontWeight: 500, }; @@ -474,7 +487,7 @@ const manualBadge: React.CSSProperties = { }; const cancelBtnStyle: React.CSSProperties = { - padding: "0.2rem 0.4rem", + padding: "0.25rem 0.4rem", fontSize: "0.75rem", fontWeight: 500, border: "none", diff --git a/web/src/components/items/CategoryPicker.tsx b/web/src/components/items/CategoryPicker.tsx index 4b64592..826021d 100644 --- a/web/src/components/items/CategoryPicker.tsx +++ b/web/src/components/items/CategoryPicker.tsx @@ -95,7 +95,7 @@ export function CategoryPicker({ } }} style={{ - padding: "0.2rem 0.5rem", + padding: "0.25rem 0.5rem", fontSize: "0.75rem", fontWeight: 500, border: "none", @@ -134,7 +134,7 @@ export function CategoryPicker({ style={{ width: "100%", padding: "0.4rem 0.5rem", - fontSize: "0.8rem", + fontSize: "var(--font-table)", border: "none", borderBottom: "1px solid var(--ctp-surface1)", backgroundColor: "var(--ctp-mantle)", @@ -152,7 +152,7 @@ export function CategoryPicker({ padding: "0.75rem", textAlign: "center", color: "var(--ctp-subtext0)", - fontSize: "0.8rem", + fontSize: "var(--font-table)", }} > Select a domain to see categories @@ -163,7 +163,7 @@ export function CategoryPicker({ padding: "0.75rem", textAlign: "center", color: "var(--ctp-subtext0)", - fontSize: "0.8rem", + fontSize: "var(--font-table)", }} > No categories found @@ -180,9 +180,9 @@ export function CategoryPicker({ display: "flex", alignItems: "center", gap: "0.5rem", - padding: "0.3rem 0.5rem", + padding: "0.25rem 0.5rem", cursor: "pointer", - fontSize: "0.8rem", + fontSize: "var(--font-table)", backgroundColor: isSelected ? "rgba(203,166,247,0.12)" : "transparent", @@ -228,7 +228,7 @@ export function CategoryPicker({ {value && categories[value] && (
New Item @@ -400,13 +400,19 @@ export function CreateItemPane({ onCreated, onCancel }: CreateItemPaneProps) { /> ) : thumbnailFile?.uploadStatus === "uploading" ? ( Uploading... {thumbnailFile.uploadProgress}% ) : ( Click to upload @@ -571,7 +577,7 @@ function SectionHeader({ children }: { children: React.ReactNode }) { >
@@ -647,13 +653,13 @@ function FormGroup({ children: React.ReactNode; }) { return ( -
+