feat(chrome)!: v0.4.0 — AdminShell + headers as /chrome sub-export

New `./chrome` entrypoint exporting `<AdminShell>` and the header
components (Search, SearchHistory, SearchOptions, Messages, BrowseApps,
HeaderCustomers, HeaderContacts, LogoutButton). Refactored from the
Chronos-style AdminShell that gscCRM was vendoring byte-for-byte —
header/footer/sidebar are now a single shared surface across apps.

Explicit props contract (no site-informations.json, no internal data
sources): `menus`, `apps`, `user`, `brand` are required; `features.*`
flags gate every section (search/browseApps/messages/notifications/
subbar*/pageHeader*/activityPanel/chat/footer); `slots.*` lets apps
inject content; `labels` overrides the next-intl "chrome" namespace.

Locale-aware navigation: chrome calls useLocale() and prepends
/{locale} to internal menu URLs, leaving externals (http(s)://…) and
the "#" sentinel alone. Breadcrumbs and the path-derived page title
strip the leading locale segment so they read "Contacts" not
"En › Contacts". Necessary for `localePrefix: 'always'` consumers like
gscCRM.

Phosphor 2.x icons: `normalizeIconClass` prepends the base `ph` class
(compound selectors `.ph.ph-house:before` require both). All hardcoded
`<i className="ph-…">` sites switched to `ph ph-…`.

`next-intl` and `next-auth` moved to peerDependencies (with devDep
copies for the kit's own typecheck/build). Consumers must symlink their
installed copies into the kit's node_modules at build time — otherwise
useTranslations()/useSession() bind to a separate React context and
next-intl throws Error(void 0) on render.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude
2026-05-12 11:24:16 +02:00
parent 387e10b2fb
commit 440f815df7
18 changed files with 2146 additions and 14 deletions

150
README.md
View File

@@ -29,7 +29,10 @@ your app
```ts
import "@gsc/web-kit/css"; // CSS bundle (layout-3 + JetBrains Mono)
// Chrome
// Chrome — unified header/footer/sidebar shell
import { AdminShell } from "@gsc/web-kit/chrome";
// Lower-level layout primitives
import { AppLayout } from "@gsc/web-kit/layout";
import { useShell } from "@gsc/web-kit/shell";
import { fetchShellConfig } from "@gsc/web-kit/shell/server";
@@ -81,8 +84,149 @@ The `/api` sub-export is reserved for a future HTTP client helper; it currently
| 2 | layout · auth · shell — usable end-to-end with shell-api | **done** |
| 3 | data · forms — curated re-exports from limitless + validation | **done (v0.3.0)** |
| 4 | feedback · navigation · utils — curated re-exports from limitless | **done (v0.3.0)** |
| 4a | api · HTTP client helper (Bearer injection, 401 → signInRedirect) | planned |
| 5 | Roll out to gscCRM / gscChronos / gscAdmin / gscPortal | planned |
| 5 | chrome · AdminShell + headers + LogoutButton + nav migrations | **done (v0.4.0)** |
| 5a | Roll out chrome to gscCRM / gscChronos / gscAdmin / gscPortal | in progress |
| 6 | api · HTTP client helper (Bearer injection, 401 → signInRedirect) | planned |
---
## Chrome (`/chrome`) — v0.4.0
Unified `AdminShell` providing navbar, subbar, page-header, sidebar, footer,
optional chat overlay and activity panel. Every app receives the same UI
chrome and toggles features it doesn't use via `features` props.
### Adopting chrome in a new app
1. **Add the dep** (already a `file:` resolve to this kit) and import:
```tsx
// app/[locale]/layout.tsx
import { AdminShell } from "@gsc/web-kit/chrome";
import "@gsc/web-kit/css";
```
2. **Apply nav migrations** to your app's database:
```bash
# In your app's migrations directory, copy the two kit-canonical files verbatim
cp node_modules/@gsc/web-kit/migrations/nav-schema.up.sql apps/<your-app>/migrations/00X_nav_schema.up.sql
cp node_modules/@gsc/web-kit/migrations/nav-apps-seed.up.sql apps/<your-app>/migrations/00Y_nav_apps_seed.up.sql
# Then copy the menu-items template once and adapt to your app's menu
cp node_modules/@gsc/web-kit/migrations/nav-menu-items-template.sql apps/<your-app>/migrations/00Z_nav_menu_items.up.sql
# edit 00Z_… to replace example rows with your app's sidebar/topbar entries
psql "$DATABASE_URL" -f apps/<your-app>/migrations/00X_nav_schema.up.sql
psql "$DATABASE_URL" -f apps/<your-app>/migrations/00Y_nav_apps_seed.up.sql
psql "$DATABASE_URL" -f apps/<your-app>/migrations/00Z_nav_menu_items.up.sql
```
Re-copy `nav-schema` + `nav-apps-seed` on every kit upgrade. The
menu-items file is yours after the first copy — the kit never touches it
again.
3. **Add Prisma** to read the data:
```prisma
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
schemas = ["nav"]
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["multiSchema"]
}
```
Add `"postinstall": "prisma generate"` to package.json. Copy `prisma/`
into the Docker image *before* `npm install` so the generate step sees it.
4. **Wire the layout server component**:
```tsx
const [sidebar, topbar, subbar, apps] = await Promise.all([
getMenuItemsByType("sidebar"),
getMenuItemsByType("topbar"),
getMenuItemsByType("subbar"),
getApps(),
]);
return (
<AdminShell
menus={{ sidebar, topbar, subbar }}
apps={apps}
user={{ displayName, email, avatarUrl }}
brand={{
name: "MyApp",
product: "GoSec MyApp",
logoUrl: "https://assets.gosec.cloud/logos/logo.svg",
websiteUrl: "https://gosec.cloud",
supportUrl: "https://support.gosec.cloud/",
docsUrl: "https://support.gosec.cloud/docs",
copyrightStartYear: 2024,
}}
features={{ chat: false, activityPanel: false }}
>
{children}
</AdminShell>
);
```
### Props reference
`<AdminShell>` is the kit's contract — see `src/chrome/types.ts` for the
authoritative TypeScript definition. Summary:
- **Data**: `menus`, `apps`, `user`, `notificationCount?`, `activity?`
- **Brand** (required): `name`, `product`, `logoUrl`, `websiteUrl`,
`supportUrl`, `docsUrl`, `copyrightStartYear` (+ optional `logoSmallUrl`)
- **`features`** (all optional booleans, sensible defaults):
`search`, `searchHistory`, `searchOptions`, `browseApps`, `messages`,
`notifications`, `subbar`, `subbarSupport`, `subbarSettings`,
`pageHeader`, `pageHeaderCustomers`, `pageHeaderContacts`,
`activityPanel`, `chat`, `footer`
- **`slots`** (ReactNode overrides):
`pageTitle`, `pageHeaderExtras`, `subbarExtras`, `activityPanel`,
`navbarExtras`, `footerExtras`
- **Behavior**: `onSignOut?` (default `next-auth signOut`),
`labels?: Partial<ChromeLabels>` (override individual chrome strings)
### i18n
Chrome's own strings come from next-intl namespace `chrome` with English
fallbacks. Add to your app's `common.json`:
```json
{
"chrome": {
"navigation": "Navigation",
"logout": "Logout",
"support": "Support",
...
}
}
```
Menu item labels (`menu.dashboard`, `menu.accounts`, …) live in your app's
existing translation namespace.
### Semver
Public surface = `<AdminShell>` props + exported types + migration files +
chrome CSS class names.
- **Major** — removed/renamed prop, changed prop shape, removed feature
flag, renamed CSS class, changed migration DDL.
- **Minor** — new optional prop, new feature flag (default off), new
slot, new export, new migration file.
- **Patch** — bugfix, internal refactor.
Deprecations land for one minor release with `console.warn` and a
`CHANGELOG.md` note before removal in the next major. Pin
`"@gsc/web-kit": "^X.Y.Z"` to receive minors automatically.
---
## Notes