From 3f1e15282a9973e93e822686cab9794212883589 Mon Sep 17 00:00:00 2001 From: Arda Demir Date: Fri, 31 Jul 2026 15:36:10 +0300 Subject: [PATCH 1/5] feat(web): Gallery Theme Builder brand lab (#theme) Add #theme page: language chips, brand color controls, scoped live preview, Copy CSS export, Apply to gallery, draft localStorage. Plan/todos for v1.8; web-only. --- CHANGELOG.md | 9 + apps/web/src/app/globals.css | 57 ++ apps/web/src/components/playground/catalog.ts | 8 + apps/web/src/components/playground/pages.tsx | 12 +- .../components/playground/theme-builder.tsx | 504 ++++++++++++++++++ docs/claude/todos.md | 99 ++-- docs/claude/v1.8-plan.md | 119 +++++ 7 files changed, 740 insertions(+), 68 deletions(-) create mode 100644 apps/web/src/components/playground/theme-builder.tsx create mode 100644 docs/claude/v1.8-plan.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d24d46..8c82f07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ Versioning follows [SemVer](https://semver.org/). ## [Unreleased] +### Added (gallery — Theme Builder) + +- Gallery page **`#theme`** — brand lab on top of Softglass languages +- Live preview (Button, Badge, Alert, Input, Card) with scoped CSS variables +- Brand controls: accent family + success / warning / danger +- **Copy CSS** export for `[data-softglass-theme="…"]` overrides +- Reset · Apply to gallery · draft autosave (`localStorage`) +- Web-only — no package version bump required for this work + ## [1.7.0] — 2026-07-31 Minor release: **DataTable** (sort · selection · virtualization), Component Studio gallery, product list recipe. diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index c36cb85..07ea227 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -313,6 +313,63 @@ body { align-items: center; } +/* Theme Builder (#theme) */ +.sg-theme-builder-grid { + display: grid; + gap: 1rem; + grid-template-columns: 1fr; + align-items: start; +} + +@media (min-width: 900px) { + .sg-theme-builder-grid { + grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); + } +} + +.sg-theme-builder-fields { + display: grid; + gap: 0.75rem; + grid-template-columns: 1fr; +} + +@media (min-width: 520px) { + .sg-theme-builder-fields { + grid-template-columns: 1fr 1fr; + } +} + +.sg-theme-builder-soft { + display: grid; + gap: 0.25rem; + grid-column: 1 / -1; +} + +.sg-theme-builder-soft-code { + font-size: var(--sg-text-xs); + font-family: var(--sg-font-mono, ui-monospace, monospace); + padding: 0.4rem 0.55rem; + border-radius: var(--sg-radius-md); + background: color-mix(in srgb, var(--sg-fg) 5%, transparent); + border: var(--sg-border-width) solid var(--sg-border-subtle); + overflow-x: auto; +} + +.sg-theme-builder-preview { + display: grid; + gap: 0.85rem; + padding: 0.85rem; + border-radius: var(--sg-radius-lg); + border: var(--sg-border-width) solid var(--sg-border-frost); + background: color-mix(in srgb, var(--sg-fg) 3%, transparent); +} + +.sg-theme-builder-export { + max-height: 220px; + overflow: auto; + white-space: pre; +} + .sg-gallery-stat-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); diff --git a/apps/web/src/components/playground/catalog.ts b/apps/web/src/components/playground/catalog.ts index a6b4107..755d51c 100644 --- a/apps/web/src/components/playground/catalog.ts +++ b/apps/web/src/components/playground/catalog.ts @@ -7,6 +7,7 @@ export type GalleryPageId = | "welcome" | "install" | "languages" + | "theme" | "looks" | "essentials" | "forms" @@ -48,6 +49,13 @@ export const GALLERY_PAGES: GalleryPage[] = [ summary: "3 light + 3 dark (Obsidian, Noir, Ember) — same components, different mood.", takeaway: "Theme is data-softglass-theme; brand is CSS variables.", }, + { + id: "theme", + label: "Theme", + title: "Theme Builder", + summary: "Brand lab: recolor accent + semantics on a language, export CSS overrides.", + takeaway: "Language = dialect; brand = --sg-accent* (and success/warning/danger).", + }, { id: "looks", label: "Looks", diff --git a/apps/web/src/components/playground/pages.tsx b/apps/web/src/components/playground/pages.tsx index 3455f76..fc4c0e3 100644 --- a/apps/web/src/components/playground/pages.tsx +++ b/apps/web/src/components/playground/pages.tsx @@ -15,6 +15,7 @@ import { type SoftglassThemeId, } from "@/lib/themes"; import type { GalleryPageId } from "@/components/playground/catalog"; +import { ThemeBuilderPage } from "@/components/playground/theme-builder"; import { Accordion, Alert, @@ -73,6 +74,8 @@ export function GalleryPageBody({ return ; case "languages": return ; + case "theme": + return ; case "looks": return ; case "essentials": @@ -124,7 +127,7 @@ function WelcomePage({ onGo }: { onGo: (id: GalleryPageId) => void }) { { k: "6", v: "Languages" }, { k: String(COMPONENT_DOCS.length), v: "Documented parts" }, { k: "MIT", v: "License" }, - { k: "9", v: "Tour pages" }, + { k: "10", v: "Tour pages" }, ].map((s) => ( @@ -148,9 +151,10 @@ function WelcomePage({ onGo }: { onGo: (id: GalleryPageId) => void }) { [ ["install", "1 · Install"], ["languages", "2 · Languages"], - ["essentials", "3 · Essentials"], - ["app", "4 · App shell"], - ["library", "5 · Library"], + ["theme", "3 · Theme"], + ["essentials", "4 · Essentials"], + ["app", "5 · App shell"], + ["library", "6 · Library"], ] as const ).map(([id, label]) => ( + ))} + + + +
+ + + Brand colors + + Accent drives hover/soft/ring automatically. Semantics are + independent. + + + + setField("accent", v)} + /> + setField("accentHover", v)} + /> + setField("accentFg", v)} + /> + setField("ring", v)} + /> + setField("success", v)} + /> + setField("warning", v)} + /> + setField("danger", v)} + /> +
+ + Soft accent (auto) + + + {brand.accentSoft} + +
+
+ + + + {galleryApplied ? ( + + applied + + ) : null} + +
+ + + + Live preview + + Scoped CSS variables — edit without committing until Apply. + + + +
+
+ + + + +
+
+ + Accent + + + Success + + + Warning + + + Danger + +
+ + Accent and semantics should stay readable on this language’s + surfaces. + + + + + + Glass chrome with brand accent on actions — keep long text on + solid. + + + +
+
+
+
+ + + + Export CSS + + Paste into your app after the Softglass tokens import. Language id:{" "} + {themeId} + + + +
{exportCss}
+
+ + + Draft autosaves in this browser. + +
+
+
+ + ); +} diff --git a/docs/claude/todos.md b/docs/claude/todos.md index c0319a2..8df9b7a 100644 --- a/docs/claude/todos.md +++ b/docs/claude/todos.md @@ -1,7 +1,7 @@ # Softglass — TODO -> **v1.0–v1.7.0:** shipped (npm + tag) · plan: [v1.7-plan.md](./v1.7-plan.md) -> Arşiv: [v1.6-plan.md](./v1.6-plan.md) · [v1.5-plan.md](./v1.5-plan.md) +> **v1.0–v1.7.0:** shipped (npm + tag) · **Sıradaki:** 1.8 Theme Builder +> Plan: [v1.8-plan.md](./v1.8-plan.md) · arşiv: [v1.7-plan.md](./v1.7-plan.md) > **Hesap:** gh **ArdaDDemir** · npm **ardaddemir** · work/Feedemy **YASAK**. > **Kural:** Aynı anda tek sprint · otomatik push yok. @@ -11,10 +11,9 @@ | Sürüm | Durum | |-------|--------| -| 1.0.0 – 1.5.0 | **shipped** (npm + tag) | -| **1.6** | Component Studio (gallery; shipped with 1.7.0 packages) | -| **1.7.0** | DataTable + virtualization + Studio + App recipe — **shipped** | -| 1.8+ | Theme builder / marketing / Storybook | +| 1.0.0 – 1.7.0 | **shipped** (npm + tag) | +| **1.8** | Theme Builder — branch `feat/v1.8-theme-builder` · **1.8-0 done** | +| 1.9+ | Marketing polish / visual / token JSON | **Repo:** https://github.com/ArdaDDemir/softglass **npm:** `@softglass/tokens@1.7.0` · `@softglass/ui@1.7.0` @@ -22,80 +21,53 @@ --- -## v1.5 — bitti +## v1.7 — bitti -- [x] 1.5a–c + gallery + noir/ember -- [x] review fixes -- [x] 1.5.0 publish + tag -- [x] PR #7 merge → main +- [x] DataTable + virtualization + Studio + App recipe +- [x] 1.7.0 publish + tag + PR #9 → main --- -## v1.6 — Component Studio (Library) — bitti (main) +## v1.8 — Theme Builder (brand lab) -- [x] Hash `#library` / `#library/` -- [x] ComponentStudio chrome -- [x] 56 playgrounds = all `COMPONENT_DOCS.id` -- [x] CHANGELOG Unreleased -- [x] PR #8 merge → main -- [ ] Manual mobile pass in browser *(nice)* - ---- - -## v1.7 — DataTable (+ virtualization) +> Dil + brand accent lab · canlı preview · export CSS. SaaS / Storybook **YAPMA**. -> Ürün listeleri: sort · selection · sticky header · density · loading/empty · Softglass looks. -> Virtualization: planda karar 1.7-0 · ship **1.7b**. - -### 1.7-0 — Plan + skeleton + Studio *(done)* +### 1.8-0 — Plan + Gallery `#theme` *(done)* - [x] Plan + todos -- [x] `DataTable` + `Table*` compounds -- [x] Tokens CSS -- [x] Studio `#library/datatable` -- [x] Tests: sort · select · empty · loading -- [x] Registry `data-table` - -### 1.7a — Polish *(done)* - -- [x] `Checkbox.indeterminate` -- [x] Select-all mixed state -- [x] `getRowLabel` -- [x] Sort `aria-label` -- [x] Skeleton density -- [x] Docs (API / LIMITATIONS / CHANGELOG) +- [x] Catalog page `#theme` +- [x] Brand color controls (accent family + semantics) +- [x] Live preview (Button, Badge, Alert, Input, Card) +- [x] Copy CSS export + Reset +- [x] Apply to gallery (optional documentElement vars) +- [x] CHANGELOG Unreleased -### 1.7b — Virtualization *(done)* +### 1.8a — Polish *(partial in 1.8-0)* -- [x] `virtualized` API + implementation -- [x] 1k-row Studio showcase -- [x] Sticky + selection regression +- [x] Auto-derive soft/hover/ring from accent +- [x] Persist draft in localStorage +- [ ] Shareable hash/query (optional) -### 1.7c — Gallery App (optional) *(done)* +### 1.8b — Marketing glue (optional) -- [x] Product list recipe demo (PageHeader + DataTable + Pagination) · `#app` +- [ ] Welcome CTA → Theme Builder -### 1.7 YAPMA +### 1.8 YAPMA -- Storybook, theme builder, marketing site -- Excel-grid (edit cell, pivot, freeze N) +- Theme SaaS, Storybook farm, full token surface editor - work hesabı push -### 1.7.0 ship +### 1.8.0 ship -- [x] version bump `@softglass/ui` (+ tokens) → **1.7.0** -- [x] CHANGELOG `[1.7.0]` -- [x] registry build -- [x] npm publish **ardaddemir** only -- [x] tag `v1.7.0` -- [ ] PR merge → main *(open PR; merge when green)* +- [ ] Manual pass `#theme` +- [ ] PR → main (web-only unless packages change) +- [ ] npm only if tokens/ui touched --- -## v1.8+ backlog +## v1.9+ backlog -- [ ] Theme builder -- [ ] Docs marketing site / Storybook +- [ ] Docs marketing site polish - [ ] Visual regression - [ ] Token JSON / Figma bridge - [ ] i18n / RTL @@ -107,9 +79,8 @@ ## Yeni session ```text -Softglass v1.7.0 shipped — DataTable + Component Studio -npm @softglass/tokens@1.7.0 · @softglass/ui@1.7.0 · tag v1.7.0 -Branch feat/v1.7-datatable → PR → main -Next: 1.8+ backlog -work hesabına push yok +Softglass v1.8 Theme Builder — docs/claude/v1.8-plan.md +Branch: feat/v1.8-theme-builder +#theme +web-first; work hesabına push yok ``` diff --git a/docs/claude/v1.8-plan.md b/docs/claude/v1.8-plan.md new file mode 100644 index 0000000..2cde66b --- /dev/null +++ b/docs/claude/v1.8-plan.md @@ -0,0 +1,119 @@ +# Softglass v1.8 — Theme Builder (brand lab) + +> **Durum:** **1.8-0 done**. Next: 1.8a share URL (optional) · 1.8b Welcome CTA · or ship. +> **Checkbox:** [todos.md](./todos.md) +> **Base:** `v1.7.0` shipped (npm + tag). +> **Branch:** `feat/v1.8-theme-builder` +> **Hesap:** gh **ArdaDDemir** · npm **ardaddemir** — work/Feedemy **YASAK**. +> **Kural:** Aynı anda **tek sprint**. SaaS multi-tenant theme product **YAPMA**. +> **Publish:** Web-first; npm only if tokens/ui API changes (unlikely in 1.8-0). + +--- + +## 1) Bugün neredeyiz? + +| | | +|--|--| +| **Shipped** | `v1.7.0` — DataTable, virtualization, Component Studio, App recipe | +| **Languages** | 6 dialects via `data-softglass-theme` | +| **Brand** | Docs say override `--sg-accent*` — **no live lab** | +| **Gallery** | Theme chips switch language only | + +**Boşluk:** Adam dil seçer, marka rengini canlı oynar, CSS snippet kopyalar — Softglass “brand vs language” modelini anlar. + +--- + +## 2) Ürün cümlesi + +> **1.8.0** = Softglass **Theme Builder** (Gallery): dil + brand accent lab, canlı preview, export CSS. Storybook / SaaS builder değil. + +| Release | Cümle | +|---------|--------| +| **1.7.0** | DataTable + Studio *(shipped)* | +| **1.8.0** | Theme Builder brand lab in Gallery | +| **1.9+** | Marketing polish / visual regression / token JSON *(ayrı)* | + +--- + +## 3) Bilinçli YAPMA (1.8) + +| Yapma | Neden | +|-------|--------| +| Theme builder **SaaS** (accounts, save cloud themes) | Ürün şişirir; Softglass kit kalır | +| Full Storybook + Chromatic | Gallery + Studio yeter | +| Her dil için yeni CSS dosyası üretimi (codegen farm) | 1.8 export snippet yeter | +| Tüm token surface’lerini (blur, shadow…) UI’da edit | Scope patlar; brand first | +| npm publish her color tweak | Web-only 1.8-0; ship sonunda karar | + +--- + +## 4) UX (kilitli) + +``` +Gallery → Theme (#theme) + ┌─────────────────────────────────────────────┐ + │ Base language: [aurora][mist]… (apply) │ + │ Brand colors: accent · hover · fg · soft │ + │ success · warning · danger │ + │ [Reset] [Copy CSS] [Apply to gallery] │ + │ │ + │ Live preview (scoped vars): │ + │ Button / Badge / Alert / Input / Card │ + │ │ + │ Export: │ + │ [data-softglass-theme="…"] { --sg-… } │ + └─────────────────────────────────────────────┘ +``` + +**Model (PUBLIC-MODEL ile uyumlu):** +- **Language** = dialect (shared) +- **Brand** = override accent (+ optional semantics) + +--- + +## 5) Sprint haritası + +``` +[ 1.8-0 ] Plan + #theme page + brand controls + preview + copy CSS + ↓ +[ 1.8a ] Soft auto-derive · share query/hash · a11y polish + ↓ +[ 1.8b ] Optional Welcome/marketing CTA → Theme Builder + ↓ +[ 1.8.0 ] Ship: web (gallery). npm only if packages touched. +``` + +--- + +## 6) DoD 1.8-0 + +1. Plan + todos 1.8 checklist +2. Gallery page `#theme` in catalog + hash nav +3. Color controls (accent family + success/warning/danger) +4. Live preview with Softglass components +5. Copy export CSS; Reset to language defaults +6. Optional “Apply to gallery” (documentElement CSS vars) +7. `typecheck` (web) green +8. CHANGELOG Unreleased + +--- + +## 7) Teknik notlar + +| | | +|--|--| +| **Scope** | Prefer **scoped preview** (`style` on container) so builder doesn’t break whole app until Apply | +| **Persistence** | `localStorage` key for brand draft (optional 1.8-0) | +| **Derive** | Soft/hover helpers from accent hex (1.8a polish if not in 1.8-0) | +| **Atoms** | Reuse `ColorInput`, `Button`, `Card`, `Badge`, `Alert`, `Input` | + +--- + +## 8) Yeni session + +```text +Softglass v1.8 Theme Builder — docs/claude/v1.8-plan.md +Branch: feat/v1.8-theme-builder +#theme brand lab +web-first; work hesabına push yok +``` From bb56de92f5aa29a62ff29e6c9f676dbff992e48c Mon Sep 17 00:00:00 2001 From: Arda Demir Date: Fri, 31 Jul 2026 15:38:24 +0300 Subject: [PATCH 2/5] feat(web): Welcome CTA for Theme Builder (1.8b) Hero button and accent-bordered card on Welcome route users to #theme. --- CHANGELOG.md | 1 + apps/web/src/app/globals.css | 8 ++++++ apps/web/src/components/playground/pages.tsx | 26 +++++++++++++++++++- docs/claude/todos.md | 4 +-- docs/claude/v1.8-plan.md | 2 +- 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c82f07..ceda48e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Versioning follows [SemVer](https://semver.org/). - Brand controls: accent family + success / warning / danger - **Copy CSS** export for `[data-softglass-theme="…"]` overrides - Reset · Apply to gallery · draft autosave (`localStorage`) +- Welcome CTA card + hero button → `#theme` - Web-only — no package version bump required for this work ## [1.7.0] — 2026-07-31 diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 07ea227..61b3e0d 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -313,6 +313,14 @@ body { align-items: center; } +/* Welcome → Theme Builder CTA */ +.sg-welcome-theme-cta { + border-color: color-mix(in srgb, var(--sg-accent) 28%, var(--sg-border-frost)); + box-shadow: + inset 0 1px 0 var(--sg-gloss-highlight), + 0 0 0 1px color-mix(in srgb, var(--sg-accent) 12%, transparent); +} + /* Theme Builder (#theme) */ .sg-theme-builder-grid { display: grid; diff --git a/apps/web/src/components/playground/pages.tsx b/apps/web/src/components/playground/pages.tsx index fc4c0e3..8c93504 100644 --- a/apps/web/src/components/playground/pages.tsx +++ b/apps/web/src/components/playground/pages.tsx @@ -112,13 +112,16 @@ function WelcomePage({ onGo }: { onGo: (id: GalleryPageId) => void }) { MIT, shadcn-style ownership. - + + @@ -138,6 +141,27 @@ function WelcomePage({ onGo }: { onGo: (id: GalleryPageId) => void }) { ))} + + + + new · 1.8 + + Theme Builder + + Language is the Softglass dialect. Brand is your accent. Recolor live, + preview components, copy the CSS override — no Storybook, no SaaS. + + + + + + + + How to use this gallery diff --git a/docs/claude/todos.md b/docs/claude/todos.md index 8df9b7a..6065ec2 100644 --- a/docs/claude/todos.md +++ b/docs/claude/todos.md @@ -48,9 +48,9 @@ - [x] Persist draft in localStorage - [ ] Shareable hash/query (optional) -### 1.8b — Marketing glue (optional) +### 1.8b — Marketing glue (optional) *(done)* -- [ ] Welcome CTA → Theme Builder +- [x] Welcome CTA → Theme Builder (hero + card) ### 1.8 YAPMA diff --git a/docs/claude/v1.8-plan.md b/docs/claude/v1.8-plan.md index 2cde66b..2da3e02 100644 --- a/docs/claude/v1.8-plan.md +++ b/docs/claude/v1.8-plan.md @@ -1,6 +1,6 @@ # Softglass v1.8 — Theme Builder (brand lab) -> **Durum:** **1.8-0 done**. Next: 1.8a share URL (optional) · 1.8b Welcome CTA · or ship. +> **Durum:** **1.8-0 + 1.8b done**. Next: 1.8a share URL (optional) · or ship PR. > **Checkbox:** [todos.md](./todos.md) > **Base:** `v1.7.0` shipped (npm + tag). > **Branch:** `feat/v1.8-theme-builder` From fdf7be35f9d2aee8681be4b9ad863d2a1384d364 Mon Sep 17 00:00:00 2001 From: Arda Demir Date: Fri, 31 Jul 2026 15:51:35 +0300 Subject: [PATCH 3/5] fix(web): complete #app as full mini product shell Wire Inventory and Settings inside AppShell with working nav, command palette actions, taller embed height, and demo Export/New/Save feedback. --- CHANGELOG.md | 5 + apps/web/src/components/playground/catalog.ts | 4 +- apps/web/src/components/playground/pages.tsx | 515 ++++++++++++------ packages/tokens/src/base.css | 31 +- 4 files changed, 374 insertions(+), 181 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ceda48e..07a2931 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,11 @@ Versioning follows [SemVer](https://semver.org/). - Welcome CTA card + hero button → `#theme` - Web-only — no package version bump required for this work +### Fixed (gallery — App mini product) + +- **`#app`** is a single live product shell (not loose demos): working nav · inventory inside AppShell · settings · command palette +- Taller embed (`.sg-shell-demo-app`) so DataTable + pagination fit + ## [1.7.0] — 2026-07-31 Minor release: **DataTable** (sort · selection · virtualization), Component Studio gallery, product list recipe. diff --git a/apps/web/src/components/playground/catalog.ts b/apps/web/src/components/playground/catalog.ts index 755d51c..b7b2801 100644 --- a/apps/web/src/components/playground/catalog.ts +++ b/apps/web/src/components/playground/catalog.ts @@ -89,8 +89,8 @@ export const GALLERY_PAGES: GalleryPage[] = [ label: "App", title: "Product shell & patterns", summary: - "Inventory list recipe (PageHeader + DataTable + Pagination), AppShell, settings, command palette.", - takeaway: "How a real product list and chrome are assembled.", + "Full mini product: AppShell nav · inventory DataTable · settings · command palette.", + takeaway: "One shell, working sections — not loose demos stacked.", }, { id: "library", diff --git a/apps/web/src/components/playground/pages.tsx b/apps/web/src/components/playground/pages.tsx index 8c93504..a861ee1 100644 --- a/apps/web/src/components/playground/pages.tsx +++ b/apps/web/src/components/playground/pages.tsx @@ -22,6 +22,7 @@ import { AppShell, AppShellCollapseButton, AppShellMenuButton, + useAppShell, Badge, Button, Card, @@ -54,7 +55,13 @@ import { type DataTableColumn, type DataTableSortState, } from "@softglass/ui"; -import { useEffect, useMemo, useState, type ReactNode } from "react"; +import { + useCallback, + useEffect, + useMemo, + useState, + type ReactNode, +} from "react"; export function GalleryPageBody({ pageId, @@ -724,8 +731,14 @@ const INVENTORY_COLUMNS: DataTableColumn[] = [ }, ]; -/** 1.7c — product list recipe: PageHeader + filter + DataTable + Pagination */ -function ProductListRecipe() { +type AppSection = "inventory" | "settings"; + +/** Inventory main — lives inside AppShell (PageHeader + filter + DataTable + Pagination) */ +function InventoryPanel({ + onToast, +}: { + onToast?: (message: string) => void; +}) { const [query, setQuery] = useState(""); const [page, setPage] = useState(1); const [selectedIds, setSelectedIds] = useState([]); @@ -749,8 +762,7 @@ function ProductListRecipe() { const pageCount = Math.max(1, Math.ceil(filtered.length / pageSize)); const safePage = Math.min(page, pageCount); const pageRows = useMemo( - () => - filtered.slice((safePage - 1) * pageSize, safePage * pageSize), + () => filtered.slice((safePage - 1) * pageSize, safePage * pageSize), [filtered, safePage, pageSize], ); @@ -759,126 +771,287 @@ function ProductListRecipe() { }, [query]); return ( - - - Product list recipe - - How inventory screens compose Softglass:{" "} - PageHeader · filter · DataTable ·{" "} - Pagination. Sort and multi-select stay on the - current page of rows. - - - - - - - - } - /> + <> + + + + + } + /> -
-
- -
- - page {safePage}/{pageCount} - - {selectedIds.length > 0 ? ( - - {selectedIds.length} selected - - ) : null} +
+
+
+ + page {safePage}/{pageCount} + + {selectedIds.length > 0 ? ( + + {selectedIds.length} selected + + ) : null} +
- row.name} - sort={sort} - onSortChange={setSort} - clientSort - emptyTitle="No products match" - emptyDescription="Try another filter or clear the search." - aria-label="Inventory products" - maxHeight={360} + row.name} + sort={sort} + onSortChange={setSort} + clientSort + emptyTitle="No products match" + emptyDescription="Try another filter or clear the search." + aria-label="Inventory products" + maxHeight={280} + /> + +
+ + Showing {(safePage - 1) * pageSize + (pageRows.length ? 1 : 0)}– + {(safePage - 1) * pageSize + pageRows.length} of {filtered.length} + + +
+ + ); +} + +function SettingsPanel({ + name, + onNameChange, + onSave, + saved, +}: { + name: string; + onNameChange: (value: string) => void; + onSave: () => void; + saved: boolean; +}) { + return ( + <> + + + {saved ? "Saved" : "Save"} + + } + look="soft" + > + onNameChange(e.target.value)} + /> + + Changes stay in this session only (gallery demo). + + + + ); +} -
void; +}) { + const { setMobileNavOpen } = useAppShell(); + + function go(next: AppSection) { + onSection(next); + setMobileNavOpen(false); + } + + return ( + <> +
+ + Softglass + + +
+ + { + e.preventDefault(); + go("inventory"); }} > - - Showing {(safePage - 1) * pageSize + (pageRows.length ? 1 : 0)}– - {(safePage - 1) * pageSize + pageRows.length} of {filtered.length} - - -
- - + Inventory + + { + e.preventDefault(); + go("settings"); + }} + > + Settings + + + ); } +/** + * 1.7c + completion — full mini product app: + * AppShell · working nav · inventory DataTable · settings · command palette. + */ function AppPage() { const [collapsed, setCollapsed] = useState(false); + const [section, setSection] = useState("inventory"); const [cmd, setCmd] = useState(false); const [last, setLast] = useState("—"); - const [name, setName] = useState("Studio"); + const [workspaceName, setWorkspaceName] = useState("Softglass Studio"); + const [saved, setSaved] = useState(false); + const [flash, setFlash] = useState(null); - const items = useMemo( + const notify = useCallback((message: string) => { + setFlash(message); + setLast(message); + window.setTimeout(() => setFlash(null), 2200); + }, []); + + const paletteItems = useMemo( () => [ - { id: "home", label: "Go home", group: "Nav", icon: "⌂" }, - { id: "settings", label: "Open settings", group: "Nav", icon: "◎" }, - { id: "theme", label: "Toggle theme", group: "Appearance", icon: "◐" }, + { + id: "inventory", + label: "Go to inventory", + group: "Nav", + icon: "◆", + }, + { + id: "settings", + label: "Open settings", + group: "Nav", + icon: "◎", + }, + { + id: "new", + label: "New product", + group: "Actions", + icon: "+", + }, + { + id: "export", + label: "Export inventory", + group: "Actions", + icon: "↓", + }, ], [], ); + function onPaletteSelect(id: string, label: string) { + setLast(label); + if (id === "inventory") setSection("inventory"); + else if (id === "settings") setSection("settings"); + else if (id === "new") { + setSection("inventory"); + notify("New product (demo)"); + } else if (id === "export") { + setSection("inventory"); + notify("Export all (demo)"); + } + } + return (
- + + One shell, two screens: Inventory (PageHeader · filter · + DataTable · Pagination) and Settings. Use the rail, the + menu on mobile, or the command palette. + - AppShell (live mini) + App shell · live product - Collapse the rail on desktop. On a narrow screen use the menu - button — same nav in a left Sheet. + Collapse the rail on desktop. Narrow viewports use the same nav in a + left Sheet. Section:{" "} + {section === "inventory" ? "Inventory" : "Settings"} @@ -888,106 +1061,96 @@ function AppPage() { variant={collapsed ? "primary" : "secondary"} onClick={() => setCollapsed((v) => !v)} > - {collapsed ? "Expand" : "Collapse"} + {collapsed ? "Expand rail" : "Collapse rail"} - collapsed: {String(collapsed)} + + + + {flash ? ( + + {flash} + + ) : ( + last: {last} + )}
+
- Mini product + {workspaceName} +
+
+ + + Softglass +
- - Softglass - } sidebar={ - <> -
- -
- - - Home - - - Projects - - - Settings - - - + } > - - New - - } - style={{ marginBottom: "0.75rem" }} - /> - - Main content grows when the rail collapses. Pair with the product - list recipe above for full inventory screens. - + {section === "inventory" ? ( + + ) : ( + { + setWorkspaceName(v); + setSaved(false); + }} + onSave={() => { + setSaved(true); + notify(`Saved “${workspaceName}”`); + }} + saved={saved} + /> + )}
- - - - Save - - } - look="soft" - > - setName(e.target.value)} - /> - - - - - Command palette - - Minimal: search + list + keyboard. Wire ⌘K yourself. - - - - - last: {last} setLast(i.label)} + items={paletteItems} + onSelect={(item) => onPaletteSelect(item.id, item.label)} /> + + + Studio deep-dive:{" "} + #library/datatable · brand lab:{" "} + #theme +
); } diff --git a/packages/tokens/src/base.css b/packages/tokens/src/base.css index 377f84c..7016de7 100644 --- a/packages/tokens/src/base.css +++ b/packages/tokens/src/base.css @@ -2790,19 +2790,32 @@ background: var(--sg-surface-solid); } -.sg-shell.sg-shell-demo .sg-shell-sidebar { +/* Full mini-product embed (inventory + settings) — taller than shell-demo */ +.sg-shell.sg-shell-demo-app { + min-height: min(36rem, 78vh); + height: min(40rem, 82vh); + border-radius: var(--sg-radius-lg); + border: var(--sg-border-width) solid var(--sg-border-frost); + overflow: hidden; + background: var(--sg-surface-solid); +} + +.sg-shell.sg-shell-demo .sg-shell-sidebar, +.sg-shell.sg-shell-demo-app .sg-shell-sidebar { height: 100%; position: relative; top: auto; } -.sg-shell.sg-shell-demo .sg-shell-header { +.sg-shell.sg-shell-demo .sg-shell-header, +.sg-shell.sg-shell-demo-app .sg-shell-header { position: relative; top: auto; padding: var(--sg-space-2) var(--sg-space-3); } -.sg-shell.sg-shell-demo .sg-shell-header-inner { +.sg-shell.sg-shell-demo .sg-shell-header-inner, +.sg-shell.sg-shell-demo-app .sg-shell-header-inner { padding: 0.45rem 0.65rem; } @@ -2810,6 +2823,18 @@ padding: var(--sg-space-4); } +.sg-shell.sg-shell-demo-app .sg-shell-main { + min-height: 0; + overflow: auto; +} + +.sg-shell.sg-shell-demo-app .sg-shell-main-inner { + padding: var(--sg-space-4); + display: grid; + gap: 0.85rem; + align-content: start; +} + /* Stronger collapse read: wider gap + rail tint */ .sg-shell-with-sidebar { transition: grid-template-columns var(--sg-duration-normal) var(--sg-ease-out); From 1ea260eb83d00bee851754b785c944facf73f202 Mon Sep 17 00:00:00 2001 From: Arda Demir Date: Fri, 31 Jul 2026 15:57:19 +0300 Subject: [PATCH 4/5] fix(web): gallery App/Theme polish after visual pass Raise shell visibility on #app, contain demo AppShell height, more bottom-pager padding, normalize 8-digit hex brand soft colors for export. --- apps/web/src/app/globals.css | 3 +- apps/web/src/components/playground/pages.tsx | 39 ++++++------ .../components/playground/theme-builder.tsx | 59 ++++++++++++++++--- packages/tokens/src/base.css | 8 ++- 4 files changed, 76 insertions(+), 33 deletions(-) diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 61b3e0d..c5bfe84 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -240,7 +240,8 @@ body { width: 100%; max-width: 52rem; margin: 0 auto; - padding: 1rem 0.85rem 7.5rem; + /* Bottom pager is fixed ~100–120px — leave room so long pages (App/Theme) aren't clipped */ + padding: 1rem 0.85rem 9.5rem; min-width: 0; } diff --git a/apps/web/src/components/playground/pages.tsx b/apps/web/src/components/playground/pages.tsx index a861ee1..f8ec6fb 100644 --- a/apps/web/src/components/playground/pages.tsx +++ b/apps/web/src/components/playground/pages.tsx @@ -1039,19 +1039,25 @@ function AppPage() { return (
- - One shell, two screens: Inventory (PageHeader · filter · - DataTable · Pagination) and Settings. Use the rail, the - menu on mobile, or the command palette. - - - + - App shell · live product +
+ + live product + + + {section === "inventory" ? "Inventory" : "Settings"} + + {flash ? ( + + {flash} + + ) : null} +
+ Mini product · AppShell - Collapse the rail on desktop. Narrow viewports use the same nav in a - left Sheet. Section:{" "} - {section === "inventory" ? "Inventory" : "Settings"} + Working nav · inventory table · settings · command palette. Collapse + the rail or open the menu on a narrow screen.
@@ -1080,13 +1086,6 @@ function AppPage() { - {flash ? ( - - {flash} - - ) : ( - last: {last} - )}
} - sidebar={ - - } + sidebar={} > {section === "inventory" ? ( diff --git a/apps/web/src/components/playground/theme-builder.tsx b/apps/web/src/components/playground/theme-builder.tsx index bd45b1b..2bc5d35 100644 --- a/apps/web/src/components/playground/theme-builder.tsx +++ b/apps/web/src/components/playground/theme-builder.tsx @@ -90,6 +90,24 @@ function softRgba(hex: string, alpha = 0.12): string { return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`; } +/** Normalize computed colors (browser may return 8-digit hex) to rgba/hex for export. */ +function normalizeCssColor(value: string, fallback: string): string { + const v = value.trim(); + if (!v) return fallback; + // #RRGGBBAA → rgba + const hex8 = v.match(/^#([0-9a-fA-F]{8})$/); + if (hex8) { + const h = hex8[1]!; + const r = Number.parseInt(h.slice(0, 2), 16); + const g = Number.parseInt(h.slice(2, 4), 16); + const b = Number.parseInt(h.slice(4, 6), 16); + const a = Number.parseInt(h.slice(6, 8), 16) / 255; + return `rgba(${r}, ${g}, ${b}, ${Math.round(a * 100) / 100})`; + } + if (parseHex(v) || v.startsWith("rgb") || v.startsWith("hsl")) return v; + return fallback; +} + function brandToCssVars(brand: BrandColors): CSSProperties { return { ["--sg-accent" as string]: brand.accent, @@ -127,15 +145,40 @@ function readCssVar(name: string, fallback: string): string { } function readBrandFromDocument(): BrandColors { + const accent = normalizeCssColor( + readCssVar("--sg-accent", DEFAULT_BRAND.accent), + DEFAULT_BRAND.accent, + ); return { - accent: readCssVar("--sg-accent", DEFAULT_BRAND.accent), - accentHover: readCssVar("--sg-accent-hover", DEFAULT_BRAND.accentHover), - accentFg: readCssVar("--sg-accent-fg", DEFAULT_BRAND.accentFg), - accentSoft: readCssVar("--sg-accent-soft", DEFAULT_BRAND.accentSoft), - ring: readCssVar("--sg-ring", DEFAULT_BRAND.ring), - success: readCssVar("--sg-success", DEFAULT_BRAND.success), - warning: readCssVar("--sg-warning", DEFAULT_BRAND.warning), - danger: readCssVar("--sg-danger", DEFAULT_BRAND.danger), + accent, + accentHover: normalizeCssColor( + readCssVar("--sg-accent-hover", DEFAULT_BRAND.accentHover), + DEFAULT_BRAND.accentHover, + ), + accentFg: normalizeCssColor( + readCssVar("--sg-accent-fg", DEFAULT_BRAND.accentFg), + DEFAULT_BRAND.accentFg, + ), + accentSoft: normalizeCssColor( + readCssVar("--sg-accent-soft", DEFAULT_BRAND.accentSoft), + softRgba(accent), + ), + ring: normalizeCssColor( + readCssVar("--sg-ring", DEFAULT_BRAND.ring), + DEFAULT_BRAND.ring, + ), + success: normalizeCssColor( + readCssVar("--sg-success", DEFAULT_BRAND.success), + DEFAULT_BRAND.success, + ), + warning: normalizeCssColor( + readCssVar("--sg-warning", DEFAULT_BRAND.warning), + DEFAULT_BRAND.warning, + ), + danger: normalizeCssColor( + readCssVar("--sg-danger", DEFAULT_BRAND.danger), + DEFAULT_BRAND.danger, + ), }; } diff --git a/packages/tokens/src/base.css b/packages/tokens/src/base.css index 7016de7..5adaaa6 100644 --- a/packages/tokens/src/base.css +++ b/packages/tokens/src/base.css @@ -2790,10 +2790,12 @@ background: var(--sg-surface-solid); } -/* Full mini-product embed (inventory + settings) — taller than shell-demo */ +/* Full mini-product embed (inventory + settings). + Contain layout — override base .sg-shell min-height: 100dvh. */ .sg-shell.sg-shell-demo-app { - min-height: min(36rem, 78vh); - height: min(40rem, 82vh); + min-height: 0; + height: min(34rem, 70vh); + max-height: 70vh; border-radius: var(--sg-radius-lg); border: var(--sg-border-width) solid var(--sg-border-frost); overflow: hidden; From b3da1c7531efe98fa13e2e1ab89d517baa96c25d Mon Sep 17 00:00:00 2001 From: Arda Demir Date: Fri, 31 Jul 2026 16:01:09 +0300 Subject: [PATCH 5/5] fix(web): Theme Builder brand colors no longer overflow Stack brand + preview; use minmax(0,1fr) field grid so ColorInputs fit inside the gallery card instead of clipping hex values. --- apps/web/src/app/globals.css | 57 ++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index c5bfe84..c0bc891 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -322,46 +322,74 @@ body { 0 0 0 1px color-mix(in srgb, var(--sg-accent) 12%, transparent); } -/* Theme Builder (#theme) */ +/* Theme Builder (#theme) + Gallery max-width ~52rem — side-by-side brand+preview made color fields overflow. + Stack sections; fields use a safe 2-col grid only when the card is wide enough. */ .sg-theme-builder-grid { display: grid; gap: 1rem; - grid-template-columns: 1fr; - align-items: start; + grid-template-columns: minmax(0, 1fr); + align-items: stretch; } -@media (min-width: 900px) { - .sg-theme-builder-grid { - grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); - } +.sg-theme-builder-grid > * { + min-width: 0; + max-width: 100%; } +/* Brand colors card body */ .sg-theme-builder-fields { display: grid; - gap: 0.75rem; - grid-template-columns: 1fr; + gap: 0.85rem 0.75rem; + grid-template-columns: minmax(0, 1fr); + width: 100%; + max-width: 100%; + box-sizing: border-box; } -@media (min-width: 520px) { +/* Full-width brand card → two equal columns that cannot overflow */ +@media (min-width: 560px) { .sg-theme-builder-fields { - grid-template-columns: 1fr 1fr; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } } +.sg-theme-builder-fields > * { + min-width: 0; + max-width: 100%; +} + +/* ColorInput field wrappers */ +.sg-theme-builder-fields .sg-field { + min-width: 0; + max-width: 100%; +} + +.sg-theme-builder-fields .sg-color-input { + width: 100%; + max-width: 100%; + box-sizing: border-box; +} + .sg-theme-builder-soft { display: grid; - gap: 0.25rem; + gap: 0.35rem; grid-column: 1 / -1; + min-width: 0; } .sg-theme-builder-soft-code { + display: block; font-size: var(--sg-text-xs); font-family: var(--sg-font-mono, ui-monospace, monospace); - padding: 0.4rem 0.55rem; + padding: 0.45rem 0.6rem; border-radius: var(--sg-radius-md); background: color-mix(in srgb, var(--sg-fg) 5%, transparent); border: var(--sg-border-width) solid var(--sg-border-subtle); overflow-x: auto; + white-space: nowrap; + max-width: 100%; + box-sizing: border-box; } .sg-theme-builder-preview { @@ -371,12 +399,15 @@ body { border-radius: var(--sg-radius-lg); border: var(--sg-border-width) solid var(--sg-border-frost); background: color-mix(in srgb, var(--sg-fg) 3%, transparent); + min-width: 0; } .sg-theme-builder-export { max-height: 220px; overflow: auto; white-space: pre; + max-width: 100%; + box-sizing: border-box; } .sg-gallery-stat-grid {