@sybilion/uilib 1.2.0 → 1.2.3
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.js +3 -3
- package/dist/esm/components/ui/Image/Image.styl.js +1 -1
- package/dist/esm/components/ui/NavUserHeader/NavUserHeader.js +28 -0
- package/dist/esm/components/ui/NavUserHeader/NavUserHeader.styl.js +7 -0
- package/dist/esm/components/ui/Page/AppShell/AppShell.styl.js +1 -1
- package/dist/esm/components/ui/Page/PageScroll/PageScroll.js +4 -4
- package/dist/esm/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.js +9 -9
- package/dist/esm/index.js +2 -1
- package/dist/esm/sybilion-auth/SybilionAuthProvider.js +30 -7
- package/dist/esm/sybilion-auth/exchangeSybilionToken.js +6 -2
- package/dist/esm/types/src/components/ui/AppHeader/AppHeader.d.ts +2 -1
- package/dist/esm/types/src/components/ui/NavUserHeader/NavUserHeader.d.ts +2 -0
- package/dist/esm/types/src/components/ui/NavUserHeader/NavUserHeader.types.d.ts +25 -0
- package/dist/esm/types/src/components/ui/NavUserHeader/index.d.ts +2 -0
- package/dist/esm/types/src/components/ui/Page/PageScroll/PageScroll.d.ts +2 -1
- package/dist/esm/types/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.d.ts +3 -1
- package/dist/esm/types/src/docs/pages/NavUserHeaderPage.d.ts +1 -0
- package/dist/esm/types/src/docs/pages/StandaloneAppLayoutPage.d.ts +1 -0
- package/dist/esm/types/src/index.d.ts +1 -0
- package/dist/esm/types/src/sybilion-auth/SybilionAuthProvider.d.ts +5 -2
- package/dist/esm/types/src/sybilion-auth/exchangeSybilionToken.d.ts +3 -1
- package/dist/esm/types/src/sybilion-auth/index.d.ts +1 -1
- package/docs/standalone-apps.md +266 -27
- package/package.json +6 -1
- package/src/components/ui/AppHeader/AppHeader.tsx +7 -3
- package/src/components/ui/Image/Image.styl +1 -0
- package/src/components/ui/NavUserHeader/NavUserHeader.styl +125 -0
- package/src/components/ui/NavUserHeader/NavUserHeader.styl.d.ts +28 -0
- package/src/components/ui/NavUserHeader/NavUserHeader.tsx +148 -0
- package/src/components/ui/NavUserHeader/NavUserHeader.types.ts +27 -0
- package/src/components/ui/NavUserHeader/avatar.svg +4 -0
- package/src/components/ui/NavUserHeader/index.ts +5 -0
- package/src/components/ui/Page/AppShell/AppShell.styl +1 -0
- package/src/components/ui/Page/PageScroll/PageScroll.tsx +9 -2
- package/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.tsx +9 -0
- package/src/docs/pages/NavUserHeaderPage.tsx +89 -0
- package/src/docs/pages/StandaloneAppLayoutPage.styl +46 -0
- package/src/docs/pages/StandaloneAppLayoutPage.styl.d.ts +8 -0
- package/src/docs/pages/StandaloneAppLayoutPage.tsx +242 -0
- package/src/docs/pages/SybilionAuthProviderPage.tsx +5 -2
- package/src/docs/registry.ts +12 -0
- package/src/index.ts +1 -0
- package/src/sybilion-auth/SybilionAuthProvider.tsx +33 -11
- package/src/sybilion-auth/exchangeSybilionToken.ts +5 -1
- package/src/sybilion-auth/index.ts +1 -0
package/docs/standalone-apps.md
CHANGED
|
@@ -1,63 +1,302 @@
|
|
|
1
1
|
# Standalone Sybilion apps (@sybilion/uilib)
|
|
2
2
|
|
|
3
|
-
Greenfield SPA on **your own origin**: `@sybilion/uilib` for layout/UI,
|
|
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:** use `AppShell` + `AppShellMainContent`; stick to uilib spacing primitives (e.g. `Gap`) instead of ad hoc root horizontal gutters.
|
|
6
6
|
|
|
7
7
|
## 1. Dependencies and global CSS
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
yarn add react react-dom react-router-dom @auth0/auth0-react @sybilion/uilib
|
|
10
|
+
yarn add react react-dom react-router-dom @auth0/auth0-react @sybilion/uilib @sybilion/sdk
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Import tokens/fonts once:
|
|
13
|
+
Import tokens/fonts once (typically `src/main.tsx`):
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
16
|
import '@sybilion/uilib/standalone-global.css';
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Mount the tree with `ReactDOM.createRoot` → `App` (wrap with `StrictMode` if you want).
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
## 2. SDK (`@sybilion/sdk`)
|
|
22
|
+
|
|
23
|
+
Typed HTTP client for the Sybilion API. Env vars depend on bundler; for Vite, only `import.meta.env.VITE_*` reaches the client.
|
|
24
|
+
|
|
25
|
+
Create **one** instance at module scope (e.g. `src/libs/sybilion-sdk.ts`) so React never reconstructs the client (`useMemo` not needed), and reuse it for auth + data:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createSybilionSDK } from '@sybilion/sdk';
|
|
29
|
+
|
|
30
|
+
export const sybilionJwtStorageKey = 'sybilion.standalone.jwt';
|
|
31
|
+
|
|
32
|
+
export const sybilionSdk = createSybilionSDK({
|
|
33
|
+
baseUrl: import.meta.env.VITE_SYBILION_API_BASE_URL as string,
|
|
34
|
+
apiPrefix: '/api',
|
|
35
|
+
getToken: () =>
|
|
36
|
+
typeof localStorage !== 'undefined'
|
|
37
|
+
? (localStorage.getItem(sybilionJwtStorageKey) ?? undefined)
|
|
38
|
+
: undefined,
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Options:** `baseUrl` — API origin only (no trailing slash). `apiPrefix` — default `'/api'` so calls go to `{baseUrl}/api/v1/...`. `getToken` — must read the same key you pass as `sybilionTokenStorageKey` on `SybilionAuthProvider` (§3).
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { sybilionSdk } from './libs/sybilion-sdk';
|
|
46
|
+
|
|
47
|
+
await sybilionSdk.raw.datasets.getById(datasetId);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- `sybilionSdk.auth` — `loginWithAuth0Identity`, `getMe`, `updateMe` (Auth0 bootstrap + user profile).
|
|
51
|
+
- `sybilionSdk.raw` — thin `GET`/`POST`/… wrappers for `/v1/...` paths (e.g. `raw.analyses.driversMapOnce`; parsed JSON, no app-specific shaping).
|
|
52
|
+
- `sybilionSdk.resources` — higher-level helpers (datasets, drivers, subscriptions) on top of `raw`.
|
|
53
|
+
|
|
54
|
+
Package README: [`@sybilion/sdk`](https://www.npmjs.com/package/@sybilion/sdk) — monorepo: [`../../sdk/README.md`](../../sdk/README.md).
|
|
22
55
|
|
|
23
56
|
## 3. Auth (`SybilionAuthProvider`)
|
|
24
57
|
|
|
25
58
|
Use inside `BrowserRouter` if redirects hit a callback route.
|
|
26
59
|
|
|
27
|
-
|
|
60
|
+
Wire the SDK module from §2 — no second `createSybilionSDK` here:
|
|
28
61
|
|
|
29
62
|
```tsx
|
|
63
|
+
import type { ReactNode } from 'react';
|
|
64
|
+
|
|
30
65
|
import { SybilionAuthProvider } from '@sybilion/uilib';
|
|
31
66
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
import { sybilionJwtStorageKey, sybilionSdk } from './libs/sybilion-sdk';
|
|
68
|
+
|
|
69
|
+
export function AppProviders({ children }: { children: ReactNode }) {
|
|
70
|
+
const auth0Domain = import.meta.env.VITE_AUTH0_DOMAIN as string;
|
|
71
|
+
const auth0ClientId = import.meta.env.VITE_AUTH0_CLIENT_ID as string;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<SybilionAuthProvider
|
|
75
|
+
sdk={sybilionSdk}
|
|
76
|
+
sybilionTokenStorageKey={sybilionJwtStorageKey}
|
|
77
|
+
auth0Domain={auth0Domain}
|
|
78
|
+
auth0ClientId={auth0ClientId}
|
|
79
|
+
redirectUri={window.location.origin}
|
|
80
|
+
>
|
|
81
|
+
{children}
|
|
82
|
+
</SybilionAuthProvider>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
44
85
|
```
|
|
45
86
|
|
|
46
|
-
**Flow:** Auth0 SPA → `
|
|
87
|
+
**Flow:** Auth0 SPA → `sybilionSdk.auth.loginWithAuth0Identity(<Auth0 AT>)` → Sybilion JWT (`data.token` / `token`) persisted → same `sybilionSdk` + `getToken` attach Bearer on requests; `useSybilionApiFetch()` uses `getSybilionApiOriginFromSdk(sdk)` for URLs (paths like `/api/v1/...`).
|
|
47
88
|
|
|
48
89
|
**Defaults** for `authorizationParams` match sybilion-client (Management audience + `openid profile email offline_access …`); override if your Auth0 SPA needs a Resource Server audience (backend confirms).
|
|
49
90
|
|
|
50
|
-
| Layer | Configure
|
|
51
|
-
| ---------------- |
|
|
52
|
-
| **Auth0** | Callback, logout, and web origins → your URLs (+ previews).
|
|
53
|
-
| **Sybilion API** | CORS → your deploy `Origin`.
|
|
54
|
-
| **App** | `
|
|
91
|
+
| Layer | Configure |
|
|
92
|
+
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
93
|
+
| **Auth0** | Callback, logout, and web origins → your URLs (+ previews). |
|
|
94
|
+
| **Sybilion API** | CORS → your deploy `Origin`. |
|
|
95
|
+
| **App** | §2 SDK module (`baseUrl`, `apiPrefix`, `getToken`); pass `sdk` + matching `sybilionTokenStorageKey`; Auth0 `domain` / `clientId`; redirect usually `window.location.origin`. |
|
|
55
96
|
|
|
56
97
|
**Hooks:** `useSybilionAuth()`, `useSybilionApiFetch()` (or `createSybilionApiFetch` / `sybilionApiFetch` helpers).
|
|
57
98
|
|
|
58
|
-
## 4.
|
|
99
|
+
## 4. Layout (AppShell)
|
|
100
|
+
|
|
101
|
+
With §2 `sybilionSdk` and §3 `AppProviders` / `SybilionAuthProvider` defined, compose routing + shell so Auth0 callbacks and JWT-backed hooks wrap the whole UI.
|
|
102
|
+
|
|
103
|
+
### Root wiring (`App.tsx`)
|
|
104
|
+
|
|
105
|
+
Order outside → in: `BrowserRouter` (callbacks) → `AppProviders` (§3) → `SidebarProvider` (sidebar width / open state for `Sidebar` primitives) → `AppLayout` → `Routes`.
|
|
106
|
+
|
|
107
|
+
`AppLayout` renders the persistent chrome (sidebar, header, footer) and the active route renders inside its main column via `children`. Add `HomePage` / `DatasetsPage` as your own page components; register an Auth0 callback route here if `SybilionAuthProvider` uses a dedicated path.
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
|
111
|
+
|
|
112
|
+
import { SidebarProvider } from '@sybilion/uilib';
|
|
113
|
+
|
|
114
|
+
import { AppLayout } from './AppLayout';
|
|
115
|
+
import { AppProviders } from './AppProviders';
|
|
116
|
+
import { DatasetsPage } from './pages/DatasetsPage';
|
|
117
|
+
import { HomePage } from './pages/HomePage';
|
|
118
|
+
|
|
119
|
+
export function App() {
|
|
120
|
+
return (
|
|
121
|
+
<BrowserRouter>
|
|
122
|
+
<AppProviders>
|
|
123
|
+
<SidebarProvider
|
|
124
|
+
sidebarWidthStorageKey="myapp.sidebarWidthPx"
|
|
125
|
+
persistSidebarWidthWithoutConsent
|
|
126
|
+
>
|
|
127
|
+
<AppLayout>
|
|
128
|
+
<Routes>
|
|
129
|
+
<Route path="/" element={<HomePage />} />
|
|
130
|
+
<Route path="/datasets" element={<DatasetsPage />} />
|
|
131
|
+
</Routes>
|
|
132
|
+
</AppLayout>
|
|
133
|
+
</SidebarProvider>
|
|
134
|
+
</AppProviders>
|
|
135
|
+
</BrowserRouter>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`SidebarProvider`: pass only the props you need. `sidebarWidthStorageKey` namespaces width in localStorage. For production apps with cookie-based consent, drop `persistSidebarWidthWithoutConsent` and pass `userId` so width persistence follows your consent rules.
|
|
141
|
+
|
|
142
|
+
### `AppLayout` (sidebar + main + `children`)
|
|
143
|
+
|
|
144
|
+
`AppHeaderHost` is the header anchor; put `SidebarTrigger` (collapses / opens rail), `NavUserHeader`, etc. inside `AppHeaderPortal`. `AppSidebar` (next subsection) is a sibling of `AppShellMainContent` inside `AppShell`. The matched route (the `<Routes>` subtree from `App.tsx`) arrives as `children` and renders inside the main column.
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
import type { ReactNode } from 'react';
|
|
148
|
+
|
|
149
|
+
import {
|
|
150
|
+
AppHeaderHost,
|
|
151
|
+
AppHeaderPortal,
|
|
152
|
+
AppShell,
|
|
153
|
+
AppShellMainContent,
|
|
154
|
+
Gap,
|
|
155
|
+
NavUserHeader,
|
|
156
|
+
PageFooter,
|
|
157
|
+
PageScroll,
|
|
158
|
+
SidebarTrigger,
|
|
159
|
+
} from '@sybilion/uilib';
|
|
160
|
+
|
|
161
|
+
import { AppSidebar } from './AppSidebar';
|
|
162
|
+
|
|
163
|
+
export function AppLayout({ children }: { children: ReactNode }) {
|
|
164
|
+
return (
|
|
165
|
+
<PageScroll>
|
|
166
|
+
<AppShell>
|
|
167
|
+
<AppSidebar />
|
|
168
|
+
|
|
169
|
+
<AppShellMainContent
|
|
170
|
+
header={<AppHeaderHost />}
|
|
171
|
+
footer={<PageFooter versionLink="/releases" versionLabel="0.0.1" />}
|
|
172
|
+
>
|
|
173
|
+
<AppHeaderPortal>
|
|
174
|
+
<SidebarTrigger />
|
|
175
|
+
<Gap />
|
|
176
|
+
<NavUserHeader
|
|
177
|
+
theme="light"
|
|
178
|
+
onThemeToggle={() => undefined}
|
|
179
|
+
onLogout={() => undefined}
|
|
180
|
+
isAuthenticated={false}
|
|
181
|
+
/>
|
|
182
|
+
</AppHeaderPortal>
|
|
183
|
+
{children}
|
|
184
|
+
</AppShellMainContent>
|
|
185
|
+
</AppShell>
|
|
186
|
+
</PageScroll>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Wire `NavUserHeader` to real auth (`useSybilionAuth`, theme context, etc.; §3). Demo in repo: `src/docs/pages/NavUserHeaderPage.tsx` (slug `nav-user-header`).
|
|
192
|
+
|
|
193
|
+
#### Sidebar (`AppSidebar.tsx`)
|
|
194
|
+
|
|
195
|
+
App-specific sidebar component — keeps the navigation surface out of `AppLayout` so the shell stays generic. Compose your nav from `@sybilion/uilib` primitives (`Sidebar` + `SidebarContent` + `SidebarGroup` + `SidebarMenu*`) and product widgets like `SidebarDatasetsItemsGrouped` (collapsible groups + nested rows for datasets — see demo `src/docs/pages/SidebarDatasetsItemsGroupedPage.tsx`, slug `sidebar-datasets-items-grouped`).
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
import { useEffect, useState } from 'react';
|
|
199
|
+
import { NavLink, useNavigate } from 'react-router-dom';
|
|
200
|
+
|
|
201
|
+
import {
|
|
202
|
+
Sidebar,
|
|
203
|
+
SidebarContent,
|
|
204
|
+
SidebarDatasetsItemsGrouped,
|
|
205
|
+
type SidebarDatasetsItemsGroupedDataset,
|
|
206
|
+
SidebarGroup,
|
|
207
|
+
SidebarMenu,
|
|
208
|
+
SidebarMenuButton,
|
|
209
|
+
SidebarMenuItem,
|
|
210
|
+
} from '@sybilion/uilib';
|
|
211
|
+
|
|
212
|
+
import { sybilionSdk } from './libs/sybilion-sdk';
|
|
213
|
+
|
|
214
|
+
export function AppSidebar() {
|
|
215
|
+
const navigate = useNavigate();
|
|
216
|
+
const [datasets, setDatasets] = useState<
|
|
217
|
+
SidebarDatasetsItemsGroupedDataset[]
|
|
218
|
+
>([]);
|
|
219
|
+
const [selectedDatasetId, setSelectedDatasetId] = useState<number>();
|
|
220
|
+
|
|
221
|
+
useEffect(() => {
|
|
222
|
+
sybilionSdk.raw.datasetsIndex(1, 50).then(res => {
|
|
223
|
+
setDatasets(res?.data?.datasets ?? []);
|
|
224
|
+
});
|
|
225
|
+
}, []);
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<Sidebar variant="inset" collapsible="offcanvas">
|
|
229
|
+
<SidebarContent>
|
|
230
|
+
<SidebarGroup>
|
|
231
|
+
<SidebarMenu>
|
|
232
|
+
<SidebarMenuItem>
|
|
233
|
+
<SidebarMenuButton asChild>
|
|
234
|
+
<NavLink to="/" end>
|
|
235
|
+
Home
|
|
236
|
+
</NavLink>
|
|
237
|
+
</SidebarMenuButton>
|
|
238
|
+
</SidebarMenuItem>
|
|
239
|
+
<SidebarMenuItem>
|
|
240
|
+
<SidebarMenuButton asChild>
|
|
241
|
+
<NavLink to="/datasets">Datasets</NavLink>
|
|
242
|
+
</SidebarMenuButton>
|
|
243
|
+
</SidebarMenuItem>
|
|
244
|
+
</SidebarMenu>
|
|
245
|
+
</SidebarGroup>
|
|
246
|
+
|
|
247
|
+
<SidebarDatasetsItemsGrouped
|
|
248
|
+
groupBy="regions"
|
|
249
|
+
datasets={datasets}
|
|
250
|
+
selectedDatasetId={selectedDatasetId}
|
|
251
|
+
onDatasetClick={id => {
|
|
252
|
+
setSelectedDatasetId(id);
|
|
253
|
+
navigate(`/datasets/${id}`);
|
|
254
|
+
}}
|
|
255
|
+
/>
|
|
256
|
+
</SidebarContent>
|
|
257
|
+
</Sidebar>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
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`.
|
|
263
|
+
|
|
264
|
+
### Full pattern
|
|
265
|
+
|
|
266
|
+
Composition: `PageScroll` → `AppShell` → `AppSidebar` → `AppShellMainContent` with `AppHeaderHost`, `PageFooter`, and the active route as `children`. `SidebarProvider` wraps `AppLayout`. Add `Theme` from `@homecode/ui` only when your product uses those primitives alongside uilib.
|
|
267
|
+
|
|
268
|
+
### Greenfield checklist (agents)
|
|
269
|
+
|
|
270
|
+
| Step | Deliverable |
|
|
271
|
+
| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
272
|
+
| Env | `VITE_SYBILION_API_BASE_URL`, `VITE_AUTH0_DOMAIN`, `VITE_AUTH0_CLIENT_ID` (names mirror §2–§3). |
|
|
273
|
+
| Files | `src/libs/sybilion-sdk.ts`, `AppProviders.tsx`, `AppLayout.tsx`, `AppSidebar.tsx`, `App.tsx`, `main.tsx`, route `element` pages under e.g. `src/pages/`. |
|
|
274
|
+
| Auth0 | SPA callback / logout URLs + allowed web origins → your deploy URLs (and previews). |
|
|
275
|
+
| API | Sybilion backend CORS → same `Origin` values. |
|
|
276
|
+
|
|
277
|
+
### Glossary (high-use pieces)
|
|
278
|
+
|
|
279
|
+
| Component / API | What it is for |
|
|
280
|
+
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
281
|
+
| `PageScroll` | Page-level vertical scroll wrapper; usual outer shell for the app body. |
|
|
282
|
+
| `AppShell` | Layout grid container; `Sidebar` + `AppShellMainContent` as siblings inside it. |
|
|
283
|
+
| `AppShellMainContent` | Main column: `header`, scrollable body (`children`), `footer`. |
|
|
284
|
+
| `AppHeaderHost` | Top header anchor (DOM id `page-header`); stays empty until `AppHeaderPortal` portals into it. |
|
|
285
|
+
| `AppHeaderPortal` | Portals into `AppHeaderHost` — `SidebarTrigger`, `NavUserHeader`, theme toggle. |
|
|
286
|
+
| `NavUserHeader` | Header user menu (avatar, account, theme, logout). |
|
|
287
|
+
| `Sidebar`, `SidebarProvider` | Collapsible rail + context (`@sybilion/uilib`). Wrap `SidebarProvider` above `AppLayout`; render `Sidebar` inside `AppShell` (usually via `AppSidebar`). |
|
|
288
|
+
| `AppSidebar` | App-specific component (`src/AppSidebar.tsx`) composing `Sidebar` + nav links + product widgets. Keeps `AppLayout` generic. |
|
|
289
|
+
| `SidebarDatasetsItemsGrouped` | Dataset list widget for the sidebar: collapsible groups (`regions` / `target_type` / `categories`) with nested rows + selection callback. |
|
|
290
|
+
| `SidebarTrigger` | Toggle sidebar visibility (especially mobile / `offcanvas`). |
|
|
291
|
+
| `PageFooter` | Standard footer; requires `versionLink` + `versionLabel`. |
|
|
292
|
+
| `Gap` | Spacing primitive between flex children. |
|
|
293
|
+
| `SybilionAuthProvider` | Auth0 + Sybilion JWT (§3). |
|
|
294
|
+
| `useSybilionAuth` | User session + login/logout under provider. |
|
|
295
|
+
| `useSybilionApiFetch` | Authenticated `fetch` using stored JWT. |
|
|
296
|
+
|
|
297
|
+
## 5. Data
|
|
59
298
|
|
|
60
|
-
|
|
299
|
+
Inside `SybilionAuthProvider`, use `useSybilionApiFetch()` for authenticated `fetch`, or import `sybilionSdk` from §2 for `raw` / `resources` — same JWT storage and API origin either way.
|
|
61
300
|
|
|
62
301
|
## Related
|
|
63
302
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sybilion/uilib",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Sybilion Design System — React UI components (Webpack + Stylus)",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
},
|
|
107
107
|
"peerDependencies": {
|
|
108
108
|
"@auth0/auth0-react": "^2.3.1",
|
|
109
|
+
"@sybilion/sdk": ">=0.0.1",
|
|
109
110
|
"react": ">=18.0.0",
|
|
110
111
|
"react-dom": ">=18.0.0",
|
|
111
112
|
"react-router-dom": ">=6.0.0"
|
|
@@ -113,10 +114,14 @@
|
|
|
113
114
|
"peerDependenciesMeta": {
|
|
114
115
|
"@auth0/auth0-react": {
|
|
115
116
|
"optional": true
|
|
117
|
+
},
|
|
118
|
+
"@sybilion/sdk": {
|
|
119
|
+
"optional": true
|
|
116
120
|
}
|
|
117
121
|
},
|
|
118
122
|
"devDependencies": {
|
|
119
123
|
"@auth0/auth0-react": "^2.3.1",
|
|
124
|
+
"@sybilion/sdk": "file:../sdk",
|
|
120
125
|
"@babel/core": "^7.20.12",
|
|
121
126
|
"@babel/preset-typescript": "^7.21.0",
|
|
122
127
|
"@homecode/ui": "^4.30.6",
|
|
@@ -20,14 +20,18 @@ export function AppHeaderHost({
|
|
|
20
20
|
|
|
21
21
|
export type AppHeaderPortalProps = {
|
|
22
22
|
children: ReactNode;
|
|
23
|
+
pageHeaderId?: string;
|
|
23
24
|
};
|
|
24
25
|
|
|
25
|
-
export function AppHeaderPortal({
|
|
26
|
+
export function AppHeaderPortal({
|
|
27
|
+
children,
|
|
28
|
+
pageHeaderId = PAGE_HEADER_ID,
|
|
29
|
+
}: AppHeaderPortalProps) {
|
|
26
30
|
const [container, setContainer] = useState<HTMLElement | null>(null);
|
|
27
31
|
|
|
28
32
|
useLayoutEffect(() => {
|
|
29
|
-
setContainer(document.getElementById(
|
|
30
|
-
}, []);
|
|
33
|
+
setContainer(document.getElementById(pageHeaderId));
|
|
34
|
+
}, [pageHeaderId]);
|
|
31
35
|
|
|
32
36
|
if (!container) {
|
|
33
37
|
return null;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
.loadingButton
|
|
2
|
+
gap 0.5rem
|
|
3
|
+
|
|
4
|
+
.avatarSkeleton
|
|
5
|
+
height 2rem
|
|
6
|
+
width 2rem
|
|
7
|
+
border-radius 9999px
|
|
8
|
+
background-color var(--color-muted)
|
|
9
|
+
animation pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite
|
|
10
|
+
|
|
11
|
+
.textSkeleton
|
|
12
|
+
height 1rem
|
|
13
|
+
width 5rem
|
|
14
|
+
border-radius 0.25rem
|
|
15
|
+
background-color var(--color-muted)
|
|
16
|
+
animation pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite
|
|
17
|
+
|
|
18
|
+
.loginButton
|
|
19
|
+
gap 0.5rem
|
|
20
|
+
|
|
21
|
+
.iconLg
|
|
22
|
+
height 1.25rem
|
|
23
|
+
width 1.25rem
|
|
24
|
+
|
|
25
|
+
.iconSm
|
|
26
|
+
height 0.75rem
|
|
27
|
+
width 0.75rem
|
|
28
|
+
|
|
29
|
+
.menuIcon
|
|
30
|
+
margin-right 0.5rem
|
|
31
|
+
height 1rem
|
|
32
|
+
width 1rem
|
|
33
|
+
|
|
34
|
+
.dropdownContent
|
|
35
|
+
width 14rem
|
|
36
|
+
|
|
37
|
+
.userButton
|
|
38
|
+
gap 0.5rem
|
|
39
|
+
height 52px
|
|
40
|
+
padding var(--p-2)
|
|
41
|
+
|
|
42
|
+
&.compact
|
|
43
|
+
padding 0
|
|
44
|
+
background-color transparent !important
|
|
45
|
+
transition transform 0.2s ease-in-out
|
|
46
|
+
|
|
47
|
+
&:hover
|
|
48
|
+
transform scale(1.1)
|
|
49
|
+
|
|
50
|
+
.avatar
|
|
51
|
+
height 2rem
|
|
52
|
+
width 2rem
|
|
53
|
+
|
|
54
|
+
.avatarImage
|
|
55
|
+
width 100%
|
|
56
|
+
height 100%
|
|
57
|
+
object-fit cover
|
|
58
|
+
border-radius inherit
|
|
59
|
+
|
|
60
|
+
.avatarFallback
|
|
61
|
+
background-color var(--color-primary)
|
|
62
|
+
color var(--color-primary-foreground)
|
|
63
|
+
background url('./avatar.svg') no-repeat center center
|
|
64
|
+
width 100%
|
|
65
|
+
height 100%
|
|
66
|
+
border-radius inherit
|
|
67
|
+
|
|
68
|
+
.userInfo
|
|
69
|
+
display flex
|
|
70
|
+
flex-direction column
|
|
71
|
+
align-items flex-start
|
|
72
|
+
text-align left
|
|
73
|
+
gap 0.25rem
|
|
74
|
+
|
|
75
|
+
.userName
|
|
76
|
+
font-size var(--text-sm)
|
|
77
|
+
font-weight 400
|
|
78
|
+
line-height 1
|
|
79
|
+
text-overflow ellipsis
|
|
80
|
+
overflow hidden
|
|
81
|
+
white-space nowrap
|
|
82
|
+
max-width 7.5rem
|
|
83
|
+
|
|
84
|
+
.userEmail
|
|
85
|
+
font-size var(--text-xs)
|
|
86
|
+
color var(--sb-slate-500)
|
|
87
|
+
line-height 1
|
|
88
|
+
|
|
89
|
+
.userLabel
|
|
90
|
+
padding 0
|
|
91
|
+
font-weight normal
|
|
92
|
+
|
|
93
|
+
.userLabelContent
|
|
94
|
+
display flex
|
|
95
|
+
align-items center
|
|
96
|
+
gap 0.5rem
|
|
97
|
+
padding 0.5rem
|
|
98
|
+
text-align left
|
|
99
|
+
font-size 0.875rem
|
|
100
|
+
|
|
101
|
+
.userDetails
|
|
102
|
+
display grid
|
|
103
|
+
flex 1
|
|
104
|
+
text-align left
|
|
105
|
+
font-size 0.875rem
|
|
106
|
+
line-height 1.25
|
|
107
|
+
|
|
108
|
+
.userDetailName
|
|
109
|
+
text-overflow ellipsis
|
|
110
|
+
overflow hidden
|
|
111
|
+
white-space nowrap
|
|
112
|
+
font-weight 500
|
|
113
|
+
|
|
114
|
+
.userDetailEmail
|
|
115
|
+
color var(--color-muted-foreground)
|
|
116
|
+
text-overflow ellipsis
|
|
117
|
+
overflow hidden
|
|
118
|
+
white-space nowrap
|
|
119
|
+
font-size 0.75rem
|
|
120
|
+
|
|
121
|
+
@keyframes pulse
|
|
122
|
+
0%, 100%
|
|
123
|
+
opacity 1
|
|
124
|
+
50%
|
|
125
|
+
opacity 0.5
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// This file is automatically generated.
|
|
2
|
+
// Please do not change this file!
|
|
3
|
+
interface CssExports {
|
|
4
|
+
'avatar': string;
|
|
5
|
+
'avatarFallback': string;
|
|
6
|
+
'avatarImage': string;
|
|
7
|
+
'avatarSkeleton': string;
|
|
8
|
+
'compact': string;
|
|
9
|
+
'dropdownContent': string;
|
|
10
|
+
'iconLg': string;
|
|
11
|
+
'iconSm': string;
|
|
12
|
+
'loadingButton': string;
|
|
13
|
+
'loginButton': string;
|
|
14
|
+
'menuIcon': string;
|
|
15
|
+
'pulse': string;
|
|
16
|
+
'textSkeleton': string;
|
|
17
|
+
'userButton': string;
|
|
18
|
+
'userDetailEmail': string;
|
|
19
|
+
'userDetailName': string;
|
|
20
|
+
'userDetails': string;
|
|
21
|
+
'userEmail': string;
|
|
22
|
+
'userInfo': string;
|
|
23
|
+
'userLabel': string;
|
|
24
|
+
'userLabelContent': string;
|
|
25
|
+
'userName': string;
|
|
26
|
+
}
|
|
27
|
+
export const cssExports: CssExports;
|
|
28
|
+
export default cssExports;
|