@sybilion/uilib 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/ui/AppHeader/AppHeader.styl.js +1 -1
- package/dist/esm/components/ui/AppHeader/appChromeAnchors.js +3 -1
- package/dist/esm/components/ui/Sidebar/Sidebar.styl.js +1 -1
- package/dist/esm/components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.js +62 -0
- package/dist/esm/components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.styl.js +7 -0
- package/dist/esm/components/ui/WorkspaceAppSwitcher/workspaceApp.types.js +4 -0
- package/dist/esm/components/ui/WorkspaceAppSwitcher/workspaceAppIcons.js +16 -0
- package/dist/esm/components/ui/WorkspaceAppSwitcher/workspaceAppPaths.js +29 -0
- package/dist/esm/components/ui/WorkspaceAppSwitcher/workspaceAppsConstants.js +4 -0
- package/dist/esm/components/ui/WorkspaceAppSwitcher/workspaceAppsLocalStorage.js +84 -0
- package/dist/esm/components/widgets/SybilionAppHeader/SybilionAppHeader.js +18 -0
- package/dist/esm/components/widgets/SybilionAppHeader/SybilionAppHeader.styl.js +7 -0
- package/dist/esm/index.js +8 -1
- package/dist/esm/types/src/components/ui/AppHeader/appChromeAnchors.d.ts +2 -0
- package/dist/esm/types/src/components/ui/AppHeader/index.d.ts +1 -1
- package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.d.ts +12 -0
- package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/index.d.ts +7 -0
- package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/workspaceApp.types.d.ts +19 -0
- package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/workspaceAppIcons.d.ts +9 -0
- package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/workspaceAppPaths.d.ts +3 -0
- package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/workspaceAppsConstants.d.ts +2 -0
- package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/workspaceAppsLocalStorage.d.ts +6 -0
- package/dist/esm/types/src/components/widgets/SybilionAppHeader/SybilionAppHeader.d.ts +14 -0
- package/dist/esm/types/src/components/widgets/SybilionAppHeader/index.d.ts +1 -0
- package/dist/esm/types/src/index.d.ts +2 -0
- package/docs/standalone-apps.md +195 -53
- package/package.json +1 -1
- package/src/components/ui/AppHeader/AppHeader.styl +5 -0
- package/src/components/ui/AppHeader/appChromeAnchors.ts +3 -0
- package/src/components/ui/AppHeader/index.ts +1 -1
- package/src/components/ui/Sidebar/Sidebar.styl +1 -1
- package/src/components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.styl +91 -0
- package/src/components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.styl.d.ts +15 -0
- package/src/components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.tsx +163 -0
- package/src/components/ui/WorkspaceAppSwitcher/index.ts +20 -0
- package/src/components/ui/WorkspaceAppSwitcher/workspaceApp.types.ts +21 -0
- package/src/components/ui/WorkspaceAppSwitcher/workspaceAppIcons.ts +27 -0
- package/src/components/ui/WorkspaceAppSwitcher/workspaceAppPaths.ts +34 -0
- package/src/components/ui/WorkspaceAppSwitcher/workspaceAppsConstants.ts +2 -0
- package/src/components/ui/WorkspaceAppSwitcher/workspaceAppsLocalStorage.ts +95 -0
- package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.styl +53 -0
- package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.styl.d.ts +10 -0
- package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.tsx +74 -0
- package/src/components/widgets/SybilionAppHeader/index.ts +4 -0
- package/src/docs/pages/{StandaloneAppLayoutPage.styl → StandaloneAppLayoutPage/StandaloneAppLayoutPage.styl} +11 -21
- package/src/docs/pages/{StandaloneAppLayoutPage.styl.d.ts → StandaloneAppLayoutPage/StandaloneAppLayoutPage.styl.d.ts} +1 -0
- package/src/docs/pages/StandaloneAppLayoutPage/StandaloneAppLayoutPage.tsx +659 -0
- package/src/docs/registry.ts +2 -1
- package/src/index.ts +2 -0
- package/src/docs/pages/StandaloneAppLayoutPage.tsx +0 -242
- /package/dist/esm/types/src/docs/pages/{StandaloneAppLayoutPage.d.ts → StandaloneAppLayoutPage/StandaloneAppLayoutPage.d.ts} +0 -0
package/docs/standalone-apps.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Greenfield SPA on **your own origin**: `@sybilion/uilib` for layout/UI, `SybilionAuthProvider` + Sybilion API for data—no iframe in the main client.
|
|
4
4
|
|
|
5
|
-
**Agents / humans:**
|
|
5
|
+
**Agents / humans:** `AppShell` + `AppShellMainContent`; spacing via uilib primitives (e.g. `Gap`), not ad hoc gutters. **Each `<Route>` page:** follow **§4 Route page body** (one canonical pattern below); **Discovering** explains how to find other `@sybilion/uilib` exports.
|
|
6
6
|
|
|
7
7
|
**Local-first:** After scaffolding, the user should run **`yarn dev`** (or `npm run dev`) on **localhost** with **no deploy** (e.g. Vercel) required. Commit **`.env.example`**; the user copies it to **`.env`**. In development, the Sybilion API is reached via a **same-origin `/api` proxy** so the browser avoids CORS; production builds still use `VITE_SYBILION_API_BASE_URL` on the client, and the real API must allow **CORS** for your deployed `Origin` unless you terminate API calls on the same host.
|
|
8
8
|
|
|
@@ -15,6 +15,25 @@ yarn add react react-dom react-router-dom @auth0/auth0-react @sybilion/uilib @sy
|
|
|
15
15
|
yarn add -D vite @vitejs/plugin-react
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
**React ecosystem versions (avoid collisions):** declare **`react`**, **`react-dom`**, **`react-router-dom`**, and **`@auth0/auth0-react`** as **direct** dependencies and align them with **`@sybilion/uilib`**, not arbitrary latest majors.
|
|
19
|
+
|
|
20
|
+
1. After installing uilib, open **`node_modules/@sybilion/uilib/package.json`** (or this repo’s root **`package.json`** at the release you pin).
|
|
21
|
+
2. **`peerDependencies`** — satisfy these ranges in your app (`react` / `react-dom`, `react-router-dom`, `@auth0/auth0-react`, and **`vite`** when you use the Vite helpers).
|
|
22
|
+
3. **`devDependencies`** — for **`react`**, **`react-dom`**, and **`react-router-dom`**, prefer **the same versions uilib lists there** (what its docs build and tests run against). Re-read this file whenever you bump **`@sybilion/uilib`** so your app does not drift to another React major while uilib does not.
|
|
23
|
+
|
|
24
|
+
Mismatched or duplicated React (two copies in the bundle) causes **invalid hook call** / subtle runtime bugs — and specifically causes **Radix UI interactive widgets** (dropdowns, dialogs, tooltips) to **silently fail** (context broken across the module boundary). If Yarn/npm hoists a second React, fix upstream ranges or use **`resolutions`** (Yarn) / **`overrides`** (npm) so the tree resolves to **one** `react` / `react-dom` pair matching (3). Also add `dedupe` + `@radix-ui/` alias in `vite.config.ts` (see **Local dev: Vite API proxy**).
|
|
25
|
+
|
|
26
|
+
**Current required versions (as of uilib 1.2.x):** `react` / `react-dom` **`^19`**, `@types/react` / `@types/react-dom` **`^19`**. Add resolutions so nested copies stay aligned:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"resolutions": {
|
|
31
|
+
"@types/react": "^19",
|
|
32
|
+
"@types/react-dom": "^19"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
18
37
|
Import tokens/fonts once (typically `src/main.tsx`):
|
|
19
38
|
|
|
20
39
|
```ts
|
|
@@ -41,12 +60,12 @@ Add a **`dev`** script so the app is runnable immediately after clone. Typical V
|
|
|
41
60
|
|
|
42
61
|
Commit **`.env.example`** at the app root (no secrets). Minimum variables:
|
|
43
62
|
|
|
44
|
-
| Variable
|
|
45
|
-
|
|
|
46
|
-
| `PORT`
|
|
47
|
-
| `VITE_SYBILION_API_BASE_URL` | Real Sybilion API origin (no trailing slash). Used as **proxy target** in dev and as SDK `baseUrl` in production builds.
|
|
48
|
-
| `VITE_AUTH0_DOMAIN`
|
|
49
|
-
| `VITE_AUTH0_CLIENT_ID`
|
|
63
|
+
| Variable | Purpose |
|
|
64
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
65
|
+
| `PORT` | Vite **dev** and **preview** server port. **`PORT=3000`** matches the current **Auth0 test tenant** (`http://localhost:3000` callback / web origins). Override if your tenant uses another port. |
|
|
66
|
+
| `VITE_SYBILION_API_BASE_URL` | Real Sybilion API origin (no trailing slash). Used as **proxy target** in dev and as SDK `baseUrl` in production builds. |
|
|
67
|
+
| `VITE_AUTH0_DOMAIN` | Auth0 domain (§3). |
|
|
68
|
+
| `VITE_AUTH0_CLIENT_ID` | Auth0 SPA client id (§3). |
|
|
50
69
|
|
|
51
70
|
Example **`.env.example`** content for the app root:
|
|
52
71
|
|
|
@@ -99,21 +118,52 @@ Package README: [`@sybilion/sdk`](https://www.npmjs.com/package/@sybilion/sdk)
|
|
|
99
118
|
|
|
100
119
|
## Local dev: Vite API proxy
|
|
101
120
|
|
|
102
|
-
Avoid browser CORS in development by serving the SPA from Vite and proxying **`/api`** to the real API.
|
|
121
|
+
Avoid browser CORS in development by serving the SPA from Vite and proxying **`/api`** to the real API.
|
|
122
|
+
|
|
123
|
+
**Reference `vite.config.ts` (minimal standalone Sybilion SPA):** merge your own `plugins` / options into this shape; keep `resolve.dedupe` and the `@radix-ui/` alias unless you know the dependency tree dedupes React and Radix correctly on its own.
|
|
124
|
+
|
|
125
|
+
- **`sybilionStandaloneViteDev`** (`@sybilion/uilib/vite-standalone-dev`) — reads **`PORT`** (defaults **3000** if unset or invalid) for **`server`** and **`preview`** bind; sets **`proxy['/api']`** → **`VITE_SYBILION_API_BASE_URL`** with `changeOrigin` and `secure: true`. Pass Vite’s **`mode`** so env (including `VITE_*`) and proxy target resolve the same way as `vite` / `vite build`.
|
|
126
|
+
- **`defineConfig(({ mode }) => …)`** — callback form gives `mode` per command (`development` / `production` / …); forward it into `sybilionStandaloneViteDev`.
|
|
127
|
+
- **`plugins: [react()]`** — add other plugins (e.g. SVGR) in the same array.
|
|
128
|
+
- **`resolve.dedupe`** — force one physical copy of `react`, `react-dom`, `react-router`, and `react-router-dom`. If the app and `@sybilion/uilib` resolve different copies, React context and router state break (`Invalid hook call`, blank subtree, Radix menus that never open). `react-router` is listed because it is a shared transitive dependency of the router packages and can duplicate independently.
|
|
129
|
+
- **`resolve.alias` for `@radix-ui/*`** — uilib’s published bundle uses bare `@radix-ui/react-*` imports. Yarn/npm can still place those packages under `node_modules/@sybilion/uilib/node_modules/@radix-ui/…`, so Vite may bundle two Radix trees and split React context across them. The regex alias rewrites every `@radix-ui/…` import to **`./node_modules/@radix-ui/`** (project root). **`path.resolve(… ) + '/'`** is required so subpaths like `@radix-ui/react-dialog` resolve under that folder.
|
|
130
|
+
- **`path`** — Node built-in `node:path`; only used at config evaluation time.
|
|
103
131
|
|
|
104
132
|
```ts
|
|
105
|
-
|
|
106
|
-
import react from '@vitejs/plugin-react';
|
|
133
|
+
// vite.config.ts — reference config for a standalone app using @sybilion/uilib + proxy.
|
|
107
134
|
import { sybilionStandaloneViteDev } from '@sybilion/uilib/vite-standalone-dev';
|
|
135
|
+
import react from '@vitejs/plugin-react';
|
|
136
|
+
import path from 'node:path';
|
|
137
|
+
import { defineConfig } from 'vite';
|
|
108
138
|
|
|
139
|
+
// Pass `mode` into sybilionStandaloneViteDev so proxy + VITE_* match the active Vite command.
|
|
109
140
|
export default defineConfig(({ mode }) => ({
|
|
110
141
|
...sybilionStandaloneViteDev({ mode }),
|
|
111
142
|
plugins: [react()],
|
|
143
|
+
resolve: {
|
|
144
|
+
// One copy of React + Router across app and uilib (avoids invalid hook call / dead Radix UI).
|
|
145
|
+
dedupe: ['react', 'react-dom', 'react-router', 'react-router-dom'],
|
|
146
|
+
alias: [
|
|
147
|
+
{
|
|
148
|
+
find: /^@radix-ui\//,
|
|
149
|
+
// Pin all Radix imports to the app’s root node_modules; trailing slash keeps subpaths working.
|
|
150
|
+
replacement: path.resolve('./node_modules/@radix-ui/') + '/',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
},
|
|
112
154
|
}));
|
|
113
155
|
```
|
|
114
156
|
|
|
115
157
|
Combine with an **empty `baseUrl` in dev** in the SDK module (§2). In **`preview`** builds, keep **`.env`** with **`VITE_SYBILION_API_BASE_URL`** so `vite preview` can still proxy API calls locally.
|
|
116
158
|
|
|
159
|
+
## Discovering `@sybilion/uilib` components (agents)
|
|
160
|
+
|
|
161
|
+
Prefer **`@sybilion/uilib`** over bespoke layout/controls.
|
|
162
|
+
|
|
163
|
+
1. **Barrel:** uilib repo `src/index.ts`, or **`node_modules/@sybilion/uilib/dist/esm/types/index.d.ts`** after install — scan `export * from './components/ui/...'`.
|
|
164
|
+
2. **Page cluster:** `PageScroll`, `AppShell`, `PageHeader`, `PageFooter`, tabs/columns, **`SybilionAppHeader`** (workspace switcher + **`NavUserHeader`**), etc. come from the same package; **what to compose and when** lives only in **§4 Route page body** (not repeated here).
|
|
165
|
+
3. **Examples:** `src/docs/pages/*Page.tsx`; **`PagePage`** (`slug page`) — minimal **`PageContent` + `PageContentSection`**.
|
|
166
|
+
|
|
117
167
|
## Local dev: apps with a Go server
|
|
118
168
|
|
|
119
169
|
Some templates include a **Go** server. This repo does **not** provide Go middleware. The contract: whatever serves the **same origin the browser uses for the SPA** must **reverse-proxy** path prefix **`/api`** (or your SDK `apiPrefix`) to the Sybilion API. Use **`baseUrl: ''`** in dev when those requests are same-origin. Name and document server-side env vars (e.g. API upstream URL) in the Go project.
|
|
@@ -161,6 +211,44 @@ export function AppProviders({ children }: { children: ReactNode }) {
|
|
|
161
211
|
|
|
162
212
|
**Hooks:** `useSybilionAuth()`, `useSybilionApiFetch()` (or `createSybilionApiFetch` / `sybilionApiFetch` helpers).
|
|
163
213
|
|
|
214
|
+
### Loading the user profile for `SybilionAppHeader`
|
|
215
|
+
|
|
216
|
+
`useSybilionAuth()` gives you `isAuthenticated` / `logout` but **not** a user object. Fetch it once with `sybilionSdk.auth.getMe()` after authentication. The response shape is:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
// GET /api/v1/users/me → { data: { user: BackendUser }, message, status }
|
|
220
|
+
// BackendUser includes `picture` (profile image URL from the API). Do not read deprecated `avatar` on the wire.
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Agents:** import **`MeResponse`** (and rely on **`BackendUser`** inside it) from `@sybilion/sdk`. Profile image on the API is **`picture` only**; `NavUserHeader` / `SybilionAppHeader` still expect the prop key **`avatar`**—map with `avatar: u.picture ?? ''`. No `UserProfile` intersections, no `u.avatar` fallback, no manual `res as { data?: … }` once `getMe()` is typed.
|
|
224
|
+
|
|
225
|
+
`NavUserHeader` (used inside `SybilionAppHeader`) accepts `user: { name, email, avatar? }`.
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
import type { MeResponse } from '@sybilion/sdk';
|
|
229
|
+
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (!isAuthenticated) {
|
|
232
|
+
setUser(null);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
sybilionSdk.auth
|
|
236
|
+
.getMe()
|
|
237
|
+
.then((res: MeResponse) => {
|
|
238
|
+
const u = res.data?.user;
|
|
239
|
+
if (u)
|
|
240
|
+
setUser({
|
|
241
|
+
name: u.name,
|
|
242
|
+
email: u.email,
|
|
243
|
+
avatar: u.picture ?? '',
|
|
244
|
+
});
|
|
245
|
+
})
|
|
246
|
+
.catch(() => undefined);
|
|
247
|
+
}, [isAuthenticated]);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Pass `user` (or `null` while loading with `isLoading` on `NavUserHeader`) and `isAuthenticated` to `SybilionAppHeader`.
|
|
251
|
+
|
|
164
252
|
## 4. Layout (AppShell)
|
|
165
253
|
|
|
166
254
|
With §2 `sybilionSdk` and §3 `AppProviders` / `SybilionAuthProvider` defined, compose routing + shell so Auth0 callbacks and JWT-backed hooks wrap the whole UI.
|
|
@@ -206,26 +294,32 @@ export function App() {
|
|
|
206
294
|
|
|
207
295
|
### `AppLayout` (sidebar + main + `children`)
|
|
208
296
|
|
|
209
|
-
`
|
|
297
|
+
`AppSidebar` is a sibling of `AppShellMainContent` inside `AppShell`. The matched route (`<Routes>` from `App.tsx`) renders as `{children}` in the main column.
|
|
298
|
+
|
|
299
|
+
**Header row:** pass **`header={<AppHeaderHost />}`** to **`AppShellMainContent`**, then **`SybilionAppHeader`** as the **first** child before `{children}`. **`SybilionAppHeader`** portals workspace switcher + embedded **`NavUserHeader`** into that shell header (and **`page-header-actions`** so **`PageHeader`** toolbars line up).
|
|
300
|
+
|
|
301
|
+
Props: all **`WorkspaceAppSwitcher`** props plus all **`NavUserHeader`** props (`user`, **`menuItems`** as **`DropdownMenuItem`** rows, **`theme`**, **`onLogout`**, etc.), and optional **`pageHeaderId`**, **`actionsAnchorId`**, **`actionsAnchorClassName`**.
|
|
210
302
|
|
|
211
303
|
```tsx
|
|
212
304
|
import type { ReactNode } from 'react';
|
|
305
|
+
import { useLocation, useNavigate } from 'react-router-dom';
|
|
213
306
|
|
|
214
307
|
import {
|
|
215
308
|
AppHeaderHost,
|
|
216
|
-
AppHeaderPortal,
|
|
217
309
|
AppShell,
|
|
218
310
|
AppShellMainContent,
|
|
219
|
-
|
|
220
|
-
NavUserHeader,
|
|
311
|
+
DropdownMenuItem,
|
|
221
312
|
PageFooter,
|
|
222
313
|
PageScroll,
|
|
223
|
-
|
|
314
|
+
SybilionAppHeader,
|
|
224
315
|
} from '@sybilion/uilib';
|
|
225
316
|
|
|
226
317
|
import { AppSidebar } from './AppSidebar';
|
|
227
318
|
|
|
228
319
|
export function AppLayout({ children }: { children: ReactNode }) {
|
|
320
|
+
const location = useLocation();
|
|
321
|
+
const navigate = useNavigate();
|
|
322
|
+
|
|
229
323
|
return (
|
|
230
324
|
<PageScroll>
|
|
231
325
|
<AppShell>
|
|
@@ -235,16 +329,24 @@ export function AppLayout({ children }: { children: ReactNode }) {
|
|
|
235
329
|
header={<AppHeaderHost />}
|
|
236
330
|
footer={<PageFooter versionLink="/releases" versionLabel="0.0.1" />}
|
|
237
331
|
>
|
|
238
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
332
|
+
<SybilionAppHeader
|
|
333
|
+
pathname={location.pathname}
|
|
334
|
+
onNavigate={href => navigate(href)}
|
|
335
|
+
authenticated={false}
|
|
336
|
+
appsStorageKey="myapp.workspaceApps"
|
|
337
|
+
defaultApps={[]}
|
|
338
|
+
user={{ name: 'Analyst', email: 'you@example.com', avatar: '' }}
|
|
339
|
+
theme="light"
|
|
340
|
+
onThemeToggle={() => undefined}
|
|
341
|
+
onLogout={() => undefined}
|
|
342
|
+
isAuthenticated={false}
|
|
343
|
+
menuItems={
|
|
344
|
+
<>
|
|
345
|
+
<DropdownMenuItem>Account</DropdownMenuItem>
|
|
346
|
+
<DropdownMenuItem>Settings</DropdownMenuItem>
|
|
347
|
+
</>
|
|
348
|
+
}
|
|
349
|
+
/>
|
|
248
350
|
{children}
|
|
249
351
|
</AppShellMainContent>
|
|
250
352
|
</AppShell>
|
|
@@ -253,7 +355,7 @@ export function AppLayout({ children }: { children: ReactNode }) {
|
|
|
253
355
|
}
|
|
254
356
|
```
|
|
255
357
|
|
|
256
|
-
Wire
|
|
358
|
+
Wire **`authenticated`**, **`user`** / **`isAuthenticated`**, **`theme`** / **`onLogout`**, **`menuItems`**, and **`defaultApps`** / **`appsStorageKey`** to real auth and workspace config (`useSybilionAuth`, §3). **`NavUserHeader`** behavior reference: `src/docs/pages/NavUserHeaderPage.tsx` (slug `nav-user-header`). Full shell preview: `src/docs/pages/StandaloneAppLayoutPage` (slug **`standalone-app-layout`**).
|
|
257
359
|
|
|
258
360
|
#### Sidebar (`AppSidebar.tsx`)
|
|
259
361
|
|
|
@@ -326,41 +428,81 @@ export function AppSidebar() {
|
|
|
326
428
|
|
|
327
429
|
Data loading uses §2 `sybilionSdk` directly — for production swap the inline `useEffect` for your data layer (React Query, SWR, context, etc.). `groupBy` accepts `'regions' | 'target_type' | 'categories'`; the widget owns its expand state and notifies on selection via `onDatasetClick`.
|
|
328
430
|
|
|
431
|
+
### Route page body (`PageHeader`, `PageContent`, `PageContentSection`)
|
|
432
|
+
|
|
433
|
+
**Canonical rule for this doc:** the shell (`AppLayout`) owns **global** chrome only. Every **route** (component under `<Routes>`) builds the main column with **`PageHeader` → `PageContent` → `PageContentSection`** (and other uilib primitives inside sections)—not a padded root `<div>`/`<main>` unless nothing fits.
|
|
434
|
+
|
|
435
|
+
Pieces:
|
|
436
|
+
|
|
437
|
+
| Piece | Role |
|
|
438
|
+
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
439
|
+
| **`PageHeader`** | Route title (`h1`), optional **breadcrumbs**, **subheader**, **actions** (toolbar). Renders the shared header chrome (collapsing title on scroll uses `PageContext` from `PageScroll`). |
|
|
440
|
+
| **`PageContent`** | Outer wrapper for the scrollable main column body. Use **`variant="clean"`** when you need the alternate spacing preset. |
|
|
441
|
+
| **`PageContentSection`** | Vertical **section** blocks inside the page (group related UI; stack multiple sections). Accepts normal `div` props (e.g. `className`, `style`; HTML **`title`** is the native tooltip attribute, not a section heading—pass a real heading element as a child if needed). |
|
|
442
|
+
|
|
443
|
+
**Example — `HomePage.tsx` (fragment inside `AppShellMainContent` / `<Routes>`):**
|
|
444
|
+
|
|
445
|
+
```tsx
|
|
446
|
+
import { PageContent, PageContentSection, PageHeader } from '@sybilion/uilib';
|
|
447
|
+
|
|
448
|
+
export function HomePage() {
|
|
449
|
+
return (
|
|
450
|
+
<>
|
|
451
|
+
<PageHeader
|
|
452
|
+
breadcrumbs={[{ label: 'Home', href: '/' }]}
|
|
453
|
+
title="Home"
|
|
454
|
+
subheader="Short route description."
|
|
455
|
+
/>
|
|
456
|
+
<PageContent>
|
|
457
|
+
<PageContentSection>
|
|
458
|
+
{/* tables, cards, charts — use uilib components where they exist */}
|
|
459
|
+
</PageContentSection>
|
|
460
|
+
</PageContent>
|
|
461
|
+
</>
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Set **`breadcrumbSidebarTrigger={false}`** on `PageHeader` only when **no** `SidebarProvider` wraps the tree (rare for standalone apps; default is fine when the sidebar exists).
|
|
467
|
+
|
|
329
468
|
### Full pattern
|
|
330
469
|
|
|
331
|
-
Composition: `PageScroll` → `AppShell` → `AppSidebar` → `AppShellMainContent` with
|
|
470
|
+
Composition: `PageScroll` → `AppShell` → `AppSidebar` → `AppShellMainContent` with **`AppHeaderHost`** + **`SybilionAppHeader`**, `PageFooter`, and the active route as `children`. **`SidebarProvider`** wraps `AppLayout`. **Route main column:** subsection **Route page body** above. Add `Theme` from `@homecode/ui` only when your product uses those primitives alongside uilib.
|
|
332
471
|
|
|
333
472
|
### Greenfield checklist (agents)
|
|
334
473
|
|
|
335
|
-
| Step
|
|
336
|
-
|
|
|
337
|
-
|
|
|
338
|
-
|
|
|
339
|
-
|
|
|
340
|
-
|
|
|
341
|
-
|
|
|
342
|
-
|
|
|
343
|
-
|
|
|
474
|
+
| Step | Deliverable |
|
|
475
|
+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
476
|
+
| React / router | **`react`**, **`react-dom`**, **`react-router-dom`**, **`@auth0/auth0-react`** (and **`vite`** if applicable) aligned with **`@sybilion/uilib`** **`package.json`** — §1 _React ecosystem versions_. |
|
|
477
|
+
| Env files | **`.env.example`** (committed) + **`.env`** locally; **`PORT=3000`** recommended for Auth0 test tenant; **`VITE_*`** as in §1 table. |
|
|
478
|
+
| Scripts | **`package.json`** includes mandatory **`dev`** (e.g. `"vite"`); optional **`build`**, **`preview`**. |
|
|
479
|
+
| Vite proxy | **`vite.config.ts`** spreads **`sybilionStandaloneViteDev({ mode })`**; SDK **`baseUrl`** empty in dev (§2). |
|
|
480
|
+
| Files | `src/libs/sybilion-sdk.ts`, `AppProviders.tsx`, `AppLayout.tsx`, `AppSidebar.tsx`, `App.tsx`, `main.tsx`, route pages under e.g. `src/pages/`. |
|
|
481
|
+
| Pages + UI | **§4 Route page body** (mandatory stack) + **Discovering** (barrel-first; bespoke markup only when no export fits). |
|
|
482
|
+
| Auth0 | SPA callback / logout URLs + allowed web origins → **`http://localhost:<PORT>`** for local dev and deploy URLs (and previews). |
|
|
483
|
+
| API | **Dev:** proxy handles API traffic (no browser CORS to API). **Prod:** Sybilion backend **CORS** → your deploy `Origin`, unless API is same-origin. |
|
|
484
|
+
| Go (if applicable) | Server proxies **`/api`** to Sybilion; SPA dev **`baseUrl`** stays `''` when same-origin. |
|
|
344
485
|
|
|
345
486
|
### Glossary (high-use pieces)
|
|
346
487
|
|
|
347
|
-
| Component / API
|
|
348
|
-
|
|
|
349
|
-
| `PageScroll`
|
|
350
|
-
| `AppShell`
|
|
351
|
-
| `AppShellMainContent`
|
|
352
|
-
| `
|
|
353
|
-
| `
|
|
354
|
-
| `NavUserHeader`
|
|
355
|
-
| `Sidebar`, `SidebarProvider`
|
|
356
|
-
| `AppSidebar`
|
|
357
|
-
| `SidebarDatasetsItemsGrouped`
|
|
358
|
-
| `SidebarTrigger`
|
|
359
|
-
| `PageFooter`
|
|
360
|
-
| `Gap`
|
|
361
|
-
| `
|
|
362
|
-
| `
|
|
363
|
-
| `
|
|
488
|
+
| Component / API | What it is for |
|
|
489
|
+
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
490
|
+
| `PageScroll` | Page-level vertical scroll wrapper; usual outer shell for the app body. |
|
|
491
|
+
| `AppShell` | Layout grid container; `Sidebar` + `AppShellMainContent` as siblings inside it. |
|
|
492
|
+
| `AppShellMainContent` | Main column: `header`, scrollable body (`children`), `footer`. |
|
|
493
|
+
| `SybilionAppHeader` | **Default** standalone top header: **`AppShellMainContent`** `header` = **`AppHeaderHost`**, first body child = **`SybilionAppHeader`** — workspace switcher + embedded **`NavUserHeader`** (props incl. **`menuItems`**); aligns **`PageHeader`** via **`page-header-actions`**. |
|
|
494
|
+
| `WorkspaceAppSwitcher` | Dropdown of workspace apps (`WorkspaceAppEntry[]`); composed inside **`SybilionAppHeader`**. |
|
|
495
|
+
| `NavUserHeader` | Header user menu (avatar, account, theme, logout). |
|
|
496
|
+
| `Sidebar`, `SidebarProvider` | Collapsible rail + context (`@sybilion/uilib`). Wrap `SidebarProvider` above `AppLayout`; render `Sidebar` inside `AppShell` (usually via `AppSidebar`). |
|
|
497
|
+
| `AppSidebar` | App-specific component (`src/AppSidebar.tsx`) composing `Sidebar` + nav links + product widgets. Keeps `AppLayout` generic. |
|
|
498
|
+
| `SidebarDatasetsItemsGrouped` | Dataset list widget for the sidebar: collapsible groups (`regions` / `target_type` / `categories`) with nested rows + selection callback. |
|
|
499
|
+
| `SidebarTrigger` | Toggle sidebar visibility (especially mobile / `offcanvas`). |
|
|
500
|
+
| `PageFooter` | Standard footer; requires `versionLink` + `versionLabel`. |
|
|
501
|
+
| `Gap` | Spacing primitive between flex children. |
|
|
502
|
+
| `PageHeader`, `PageContent`, `PageContentSection`, … | **§4 Route page body** (roles, `breadcrumbSidebarTrigger`, example). Same barrel also has `PageTabs`, `PageColumns`, `SectionHeader`, `PageEmptyCanvas`, … |
|
|
503
|
+
| `SybilionAuthProvider` | Auth0 + Sybilion JWT (§3). |
|
|
504
|
+
| `useSybilionAuth` | User session + login/logout under provider. |
|
|
505
|
+
| `useSybilionApiFetch` | Authenticated `fetch` using stored JWT. |
|
|
364
506
|
|
|
365
507
|
## 5. Data
|
|
366
508
|
|
package/package.json
CHANGED
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
min-height var(--header-height)
|
|
10
10
|
background-color var(--color-background)
|
|
11
11
|
|
|
12
|
+
@media (min-width MOBILE)
|
|
13
|
+
:global([data-slot='sidebar-wrapper'][data-state='collapsed']) &
|
|
14
|
+
padding-left 200px
|
|
15
|
+
max-width 100%
|
|
16
|
+
|
|
12
17
|
.content
|
|
13
18
|
display flex
|
|
14
19
|
align-items center
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
@import '../../../lib/theme.styl'
|
|
2
|
+
|
|
3
|
+
.trigger
|
|
4
|
+
display flex
|
|
5
|
+
align-items center
|
|
6
|
+
gap var(--p-2)
|
|
7
|
+
max-width 320px
|
|
8
|
+
height auto
|
|
9
|
+
padding var(--p-1) !important
|
|
10
|
+
padding-right var(--p-3) !important
|
|
11
|
+
margin-left var(--p-3) !important
|
|
12
|
+
|
|
13
|
+
border-radius 12px
|
|
14
|
+
border none
|
|
15
|
+
background transparent
|
|
16
|
+
cursor pointer
|
|
17
|
+
color inherit
|
|
18
|
+
font inherit
|
|
19
|
+
text-align left
|
|
20
|
+
|
|
21
|
+
&:hover
|
|
22
|
+
background-color var(--muted)
|
|
23
|
+
|
|
24
|
+
.iconTile
|
|
25
|
+
position relative
|
|
26
|
+
display flex
|
|
27
|
+
align-items center
|
|
28
|
+
justify-content center
|
|
29
|
+
flex-shrink 0
|
|
30
|
+
width 40px
|
|
31
|
+
height 40px
|
|
32
|
+
border-radius 10px
|
|
33
|
+
color var(--fg-color)
|
|
34
|
+
|
|
35
|
+
&::before
|
|
36
|
+
&::after
|
|
37
|
+
position absolute
|
|
38
|
+
content ''
|
|
39
|
+
display block
|
|
40
|
+
width 100%
|
|
41
|
+
height 100%
|
|
42
|
+
border-radius inherit
|
|
43
|
+
&::before
|
|
44
|
+
background-color var(--background)
|
|
45
|
+
&::after
|
|
46
|
+
background-color var(--bg-color)
|
|
47
|
+
|
|
48
|
+
.icon
|
|
49
|
+
z-index 1
|
|
50
|
+
width 22px !important
|
|
51
|
+
height 22px !important
|
|
52
|
+
color var(--fg-color) !important
|
|
53
|
+
|
|
54
|
+
.textCol
|
|
55
|
+
display flex
|
|
56
|
+
flex-direction column
|
|
57
|
+
min-width 0
|
|
58
|
+
flex 1
|
|
59
|
+
gap 2px
|
|
60
|
+
|
|
61
|
+
.name
|
|
62
|
+
font-weight 600
|
|
63
|
+
font-size var(--text-sm)
|
|
64
|
+
line-height 1.2
|
|
65
|
+
color var(--foreground)
|
|
66
|
+
white-space nowrap
|
|
67
|
+
overflow hidden
|
|
68
|
+
text-overflow ellipsis
|
|
69
|
+
|
|
70
|
+
.sub
|
|
71
|
+
font-size var(--text-xs)
|
|
72
|
+
line-height 1.2
|
|
73
|
+
color var(--muted-foreground)
|
|
74
|
+
white-space nowrap
|
|
75
|
+
overflow hidden
|
|
76
|
+
text-overflow ellipsis
|
|
77
|
+
|
|
78
|
+
.menuContent
|
|
79
|
+
min-width 280px
|
|
80
|
+
max-width 360px
|
|
81
|
+
|
|
82
|
+
.item
|
|
83
|
+
display flex
|
|
84
|
+
align-items center
|
|
85
|
+
gap var(--p-3)
|
|
86
|
+
padding var(--p-3)
|
|
87
|
+
cursor pointer
|
|
88
|
+
outline none
|
|
89
|
+
|
|
90
|
+
.itemActive
|
|
91
|
+
background-color var(--muted)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// This file is automatically generated.
|
|
2
|
+
// Please do not change this file!
|
|
3
|
+
interface CssExports {
|
|
4
|
+
'icon': string;
|
|
5
|
+
'iconTile': string;
|
|
6
|
+
'item': string;
|
|
7
|
+
'itemActive': string;
|
|
8
|
+
'menuContent': string;
|
|
9
|
+
'name': string;
|
|
10
|
+
'sub': string;
|
|
11
|
+
'textCol': string;
|
|
12
|
+
'trigger': string;
|
|
13
|
+
}
|
|
14
|
+
export const cssExports: CssExports;
|
|
15
|
+
export default cssExports;
|