diff --git a/.design-sync/NOTES.md b/.design-sync/NOTES.md index 34086dff..0bf68137 100644 --- a/.design-sync/NOTES.md +++ b/.design-sync/NOTES.md @@ -214,6 +214,20 @@ web-app shims but are deliberately EXCLUDED from the design project). - **@parcel/watcher** (optional dep of @tailwindcss/cli): its postinstall is denied in pnpm-workspace.yaml `allowBuilds` — not needed for one-shot builds. +### 2026-08-23 second pass — Empty + Kbd (PR #799) + +Kit grew to 20 components. `Empty` and `Kbd` added to `src/design-sync.ts`, +`cfg.componentSrcMap` (subparts nulled), previews authored, conventions +enumeration updated. `Empty` needed `cardMode: "column"` (same `[GRID_OVERFLOW]` +remedy as Accordion/InputGroup/Tabs). Incremental upload: the two component +dirs + `_preview/{Empty,Kbd}.*` + shared closure +(`_ds_bundle.js/.css`, `styles.css`, `README.md`, `_ds_sync.json`, +`_ds_needs_recompile`). Gotcha: running `resync.mjs` WITHOUT `--entry` cleans +`ds-bundle/` then fails resolving the package (expects a self-link in +node_modules) — leaving an empty bundle. Always drive the individual +`package-build/capture/validate` scripts with the explicit +`--entry ./packages/ui/dist/design-sync.js`. + ### Known render warns (2026-08-23 kit sync) - `[TOKENS_MISSING] --accordion-panel-height, --radius, --secondary, --foreground, --tw` — runtime/JS-set vars (accordion height) plus shadcn-generated arbitrary diff --git a/.design-sync/config.json b/.design-sync/config.json index 5c4ae20b..6cecc7aa 100644 --- a/.design-sync/config.json +++ b/.design-sync/config.json @@ -15,8 +15,10 @@ "Combobox": "src/components/ui/combobox.tsx", "Dialog": "src/components/ui/dialog.tsx", "DropdownMenu": "src/components/ui/dropdown-menu.tsx", + "Empty": "src/components/ui/empty.tsx", "Input": "src/components/ui/input.tsx", "InputGroup": "src/components/ui/input-group.tsx", + "Kbd": "src/components/ui/kbd.tsx", "Label": "src/components/ui/label.tsx", "Popover": "src/components/ui/popover.tsx", "Select": "src/components/ui/select.tsx", @@ -64,6 +66,12 @@ "DialogPortal": null, "DialogTitle": null, "DialogTrigger": null, + "EmptyHeader": null, + "EmptyTitle": null, + "EmptyDescription": null, + "EmptyContent": null, + "EmptyMedia": null, + "KbdGroup": null, "DropdownMenuPortal": null, "DropdownMenuTrigger": null, "DropdownMenuContent": null, @@ -113,6 +121,7 @@ }, "overrides": { "Accordion": { "cardMode": "column" }, + "Empty": { "cardMode": "column" }, "InputGroup": { "cardMode": "column" }, "Tabs": { "cardMode": "column" }, "Dialog": { "cardMode": "single", "primaryStory": "InviteTeammate", "viewport": "760x520" }, diff --git a/.design-sync/conventions.md b/.design-sync/conventions.md index a2b757ef..32cac708 100644 --- a/.design-sync/conventions.md +++ b/.design-sync/conventions.md @@ -82,11 +82,14 @@ Cap prose at `--measure` (68ch). ## Components `Accordion` · `AlertDialog` · `Badge` · `Button` · `Checkbox` · `Combobox` -· `Dialog` · `DropdownMenu` · `Input` · `InputGroup` · `Label` · `Popover` -· `Select` · `Switch` · `Table` · `Tabs` · `Textarea` · `Tooltip` +· `Dialog` · `DropdownMenu` · `Empty` · `Input` · `InputGroup` · `Kbd` +· `Label` · `Popover` · `Select` · `Switch` · `Table` · `Tabs` · `Textarea` +· `Tooltip` Compound parts (`DialogTrigger`, `SelectItem`, `TableRow`, -`DropdownMenuItem`, `InputGroupAddon`, …) are all exported flat from the +`DropdownMenuItem`, `InputGroupAddon`, `EmptyHeader`/`EmptyTitle`/ +`EmptyDescription`/`EmptyContent`/`EmptyMedia`, `KbdGroup`, …) are all +exported flat from the same module. Key variant axes: `Button` `variant` (`default | outline | secondary | ghost | destructive | link`) and `size` (`xs | sm | default | lg | icon*`); `Badge` `variant` (same six). The diff --git a/.design-sync/previews/Empty.tsx b/.design-sync/previews/Empty.tsx new file mode 100644 index 00000000..3c44f615 --- /dev/null +++ b/.design-sync/previews/Empty.tsx @@ -0,0 +1,51 @@ +import "./canvas.module.css"; +import { + Button, + Empty, + EmptyContent, + EmptyDescription, + EmptyHeader, + EmptyMedia, + EmptyTitle, +} from "@uploads/ui"; + +export function Default() { + return ( + + + + No screenshots yet + + Capture one with uploads screenshot <url> and it shows up here. + + + + + + + ); +} + +export function TitleOnly() { + return ( + + + No galleries yet + Create one to collect related uploads. + + + ); +} + +export function Inline() { + return ( + + + No tokens yet + Create one above to start. + + + ); +} diff --git a/.design-sync/previews/Kbd.tsx b/.design-sync/previews/Kbd.tsx new file mode 100644 index 00000000..4fe4bb2b --- /dev/null +++ b/.design-sync/previews/Kbd.tsx @@ -0,0 +1,30 @@ +import "./canvas.module.css"; +import { Kbd, KbdGroup } from "@uploads/ui"; + +export function Default() { + return ( + + + K + + ); +} + +export function SingleKey() { + return /; +} + +export function InlineHint() { + return ( + + Filter path + + + K + + + ); +} diff --git a/apps/web/src/components/ScreenshotsByPath.tsx b/apps/web/src/components/ScreenshotsByPath.tsx index 178af93c..502a0e34 100644 --- a/apps/web/src/components/ScreenshotsByPath.tsx +++ b/apps/web/src/components/ScreenshotsByPath.tsx @@ -21,6 +21,8 @@ */ import { Callout, Input, Select } from "@uploads/ui"; import "@uploads/ui/styles.css"; +import { Empty, EmptyContent, EmptyHeader, EmptyTitle } from "@uploads/ui/components/ui/empty"; +import { Kbd } from "@uploads/ui/components/ui/kbd"; import { useEffect, useLayoutEffect, @@ -90,15 +92,41 @@ function EmptyShotsCta({ title }: { title: string }) { } }; return ( -
-

{title}

-
- {EMPTY_CTA_CMD} - -
-
+ + + {title} + + +
+ + {EMPTY_CTA_CMD} + + +
+
+
+ ); +} + +/** + * Compact Empty for inline filter/drill "nothing found" messages — smaller + * than the CTA-style empty state above, since these sit inside already- + * scoped list/grid contexts rather than replacing the whole page. + */ +function InlineEmpty({ title }: { title: ReactNode }) { + return ( + + + {title} + + ); } @@ -429,6 +457,13 @@ function FilterBar({ // useful to offer until the user edits it back into a query. const suggestions = path ? [] : pathSuggestions(catalog, { project, q }); const open = suggestOpen && suggestions.length > 0; + // "Ctrl K" is the deterministic default so the server's render and the + // client's first render agree bit-for-bit (no `navigator` at SSR time) — + // corrected to "⌘K" after mount, once hydration has already reconciled. + const [modKbd, setModKbd] = useState("Ctrl K"); + useEffect(() => { + if (/Mac|iPhone|iPad|iPod/.test(navigator.userAgent)) setModKbd("⌘K"); + }, []); const pick = (picked: string) => { setSuggestOpen(false); @@ -479,7 +514,7 @@ function FilterBar({ + {value === "" && ( + {modKbd} + )} {open && (