@sybilion/uilib 1.0.32 → 1.2.0
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/README.md +8 -8
- package/assets/{mini-app-global.css → standalone-global.css} +1 -1
- package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.constants.js +11 -0
- package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.js +2 -1
- package/dist/esm/components/ui/Sidebar/Sidebar.js +1 -8
- package/dist/esm/components/ui/Sidebar/Sidebar.styl.js +2 -2
- package/dist/esm/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.js +48 -0
- package/dist/esm/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.styl.js +7 -0
- package/dist/esm/components/widgets/SidebarDatasetsItemsGrouped/groupSidebarDatasets.js +38 -0
- package/dist/esm/index.js +6 -6
- package/dist/esm/sybilion-auth/SybilionAuthProvider.js +185 -0
- package/dist/esm/sybilion-auth/authPaths.js +7 -0
- package/dist/esm/sybilion-auth/exchangeSybilionToken.js +40 -0
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.constants.d.ts +9 -0
- package/dist/esm/types/src/components/ui/Input/Input.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Sidebar/Sidebar.d.ts +1 -5
- package/dist/esm/types/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.d.ts +11 -0
- package/dist/esm/types/src/components/widgets/SidebarDatasetsItemsGrouped/groupSidebarDatasets.d.ts +8 -0
- package/dist/esm/types/src/components/widgets/SidebarDatasetsItemsGrouped/index.d.ts +3 -0
- package/dist/esm/types/src/docs/pages/SidebarDatasetsItemsGroupedPage.d.ts +1 -0
- package/dist/esm/types/src/docs/pages/SybilionAuthProviderPage.d.ts +1 -0
- package/dist/esm/types/src/index.d.ts +3 -1
- package/dist/esm/types/src/sybilion-auth/SybilionAuthProvider.d.ts +39 -0
- package/dist/esm/types/src/sybilion-auth/authPaths.d.ts +3 -0
- package/dist/esm/types/src/sybilion-auth/exchangeSybilionToken.d.ts +2 -0
- package/dist/esm/types/src/sybilion-auth/index.d.ts +4 -0
- package/dist/esm/types/src/{mini-app/miniAppDataTypes.d.ts → types/sybilionDatasetSnapshots.d.ts} +5 -8
- package/docs/standalone-apps.md +65 -0
- package/package.json +10 -3
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.constants.ts +9 -0
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.tsx +2 -1
- package/src/components/ui/Sidebar/Sidebar.styl +0 -30
- package/src/components/ui/Sidebar/Sidebar.styl.d.ts +0 -2
- package/src/components/ui/Sidebar/Sidebar.tsx +0 -30
- package/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.styl +29 -0
- package/src/{mini-app/MiniAppRoot.styl.d.ts → components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.styl.d.ts} +4 -2
- package/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.tsx +128 -0
- package/src/components/widgets/SidebarDatasetsItemsGrouped/groupSidebarDatasets.ts +51 -0
- package/src/components/widgets/SidebarDatasetsItemsGrouped/index.ts +10 -0
- package/src/docs/components/DocsSidebar/DocsSidebar.tsx +47 -50
- package/src/docs/pages/SidebarDatasetsItemsGroupedPage.tsx +136 -0
- package/src/docs/pages/SidebarPage.tsx +21 -28
- package/src/docs/pages/SybilionAuthProviderPage.tsx +37 -0
- package/src/docs/registry.ts +9 -3
- package/src/index.ts +3 -1
- package/src/sybilion-auth/SybilionAuthProvider.tsx +322 -0
- package/src/sybilion-auth/authPaths.ts +6 -0
- package/src/sybilion-auth/exchangeSybilionToken.ts +47 -0
- package/src/sybilion-auth/index.ts +16 -0
- package/src/{mini-app/miniAppDataTypes.ts → types/sybilionDatasetSnapshots.ts} +5 -8
- package/dist/esm/mini-app/MiniAppRoot.js +0 -82
- package/dist/esm/mini-app/MiniAppRoot.styl.js +0 -7
- package/dist/esm/mini-app/miniAppChatBridge.js +0 -45
- package/dist/esm/mini-app/miniAppDataClient.js +0 -98
- package/dist/esm/mini-app/miniAppProtocol.js +0 -153
- package/dist/esm/mini-app/miniAppThemeConfig.js +0 -40
- package/dist/esm/types/src/docs/pages/MiniAppRootPage.d.ts +0 -1
- package/dist/esm/types/src/mini-app/MiniAppRoot.d.ts +0 -18
- package/dist/esm/types/src/mini-app/index.d.ts +0 -10
- package/dist/esm/types/src/mini-app/miniAppChatBridge.d.ts +0 -6
- package/dist/esm/types/src/mini-app/miniAppDataClient.d.ts +0 -16
- package/dist/esm/types/src/mini-app/miniAppProtocol.d.ts +0 -89
- package/dist/esm/types/src/mini-app/miniAppThemeConfig.d.ts +0 -3
- package/docs/workspace-mini-apps.md +0 -51
- package/src/docs/pages/MiniAppRootPage.tsx +0 -58
- package/src/mini-app/MiniAppRoot.styl +0 -24
- package/src/mini-app/MiniAppRoot.tsx +0 -150
- package/src/mini-app/index.ts +0 -43
- package/src/mini-app/miniAppChatBridge.ts +0 -55
- package/src/mini-app/miniAppDataClient.ts +0 -165
- package/src/mini-app/miniAppProtocol.ts +0 -247
- package/src/mini-app/miniAppThemeConfig.ts +0 -45
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function SybilionAuthProviderPage(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './sybilion-auth';
|
|
2
|
+
export * from './types/sybilionDatasetSnapshots';
|
|
2
3
|
export * from './contexts/chat-context';
|
|
3
4
|
export * from './types/chat-api.types';
|
|
4
5
|
export * from './components/ui/AnalysesSelector';
|
|
@@ -53,3 +54,4 @@ export * from './components/ui/Toggle';
|
|
|
53
54
|
export * from './components/ui/ToggleGroup';
|
|
54
55
|
export * from './components/ui/Tooltip';
|
|
55
56
|
export * from './components/ui/VimeoEmbed';
|
|
57
|
+
export * from './components/widgets/SidebarDatasetsItemsGrouped';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type RedirectLoginOptions } from '@auth0/auth0-react';
|
|
2
|
+
import type { JSX, ReactNode } from 'react';
|
|
3
|
+
export type SybilionAuthProviderProps = {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
apiBaseUrl: string;
|
|
6
|
+
auth0Domain: string;
|
|
7
|
+
auth0ClientId: string;
|
|
8
|
+
redirectUri: string;
|
|
9
|
+
/**
|
|
10
|
+
* Defaults match sybilion-client AuthProvider (Management API audience + metadata scope).
|
|
11
|
+
* Override if your Auth0 SPA app uses a Resource Server audience for `/v1/auth/login`.
|
|
12
|
+
*/
|
|
13
|
+
authorizationParams?: {
|
|
14
|
+
audience?: string;
|
|
15
|
+
scope?: string;
|
|
16
|
+
redirect_uri?: string;
|
|
17
|
+
};
|
|
18
|
+
sybilionTokenStorageKey?: string;
|
|
19
|
+
logoutReturnTo?: string;
|
|
20
|
+
};
|
|
21
|
+
export type SybilionAuthContextValue = {
|
|
22
|
+
apiBaseUrl: string;
|
|
23
|
+
isLoading: boolean;
|
|
24
|
+
/** Auth0 authenticated and Sybilion JWT present. */
|
|
25
|
+
isAuthenticated: boolean;
|
|
26
|
+
sybilionAccessToken: string | null;
|
|
27
|
+
error: string | null;
|
|
28
|
+
loginWithRedirect: (options?: RedirectLoginOptions) => Promise<void>;
|
|
29
|
+
logout: () => void;
|
|
30
|
+
getAuth0AccessToken: () => Promise<string | undefined>;
|
|
31
|
+
getSybilionAccessToken: () => Promise<string | null>;
|
|
32
|
+
};
|
|
33
|
+
export declare function useSybilionAuth(): SybilionAuthContextValue;
|
|
34
|
+
/** fetch() relative to Sybilion API base with Bearer Sybilion JWT. */
|
|
35
|
+
export declare function sybilionApiFetch(apiBaseUrl: string, bearerToken: string, path: string, init?: RequestInit): Promise<Response>;
|
|
36
|
+
export declare function createSybilionApiFetch(apiBaseUrl: string, getSybilionAccessToken: () => Promise<string | null>): (path: string, init?: RequestInit) => Promise<Response>;
|
|
37
|
+
/** Authenticated fetch using {@link useSybilionAuth} context. */
|
|
38
|
+
export declare function useSybilionApiFetch(): (path: string, init?: RequestInit) => Promise<Response>;
|
|
39
|
+
export declare function SybilionAuthProvider({ children, apiBaseUrl, auth0Domain, auth0ClientId, redirectUri, authorizationParams, sybilionTokenStorageKey, logoutReturnTo, }: SybilionAuthProviderProps): JSX.Element;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { SybilionAuthProviderProps, SybilionAuthContextValue, } from '#uilib/sybilion-auth/SybilionAuthProvider';
|
|
2
|
+
export { SybilionAuthProvider, useSybilionAuth, sybilionApiFetch, createSybilionApiFetch, useSybilionApiFetch, } from '#uilib/sybilion-auth/SybilionAuthProvider';
|
|
3
|
+
export { SYBILION_AUTH_LOGIN_PATH, normalizeApiBaseUrl, } from '#uilib/sybilion-auth/authPaths';
|
|
4
|
+
export { exchangeAuth0AccessTokenForSybilionJwt } from '#uilib/sybilion-auth/exchangeSybilionToken';
|
package/dist/esm/types/src/{mini-app/miniAppDataTypes.d.ts → types/sybilionDatasetSnapshots.d.ts}
RENAMED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Subset of Sybilion `Dataset` / API types — use `unknown` where payloads are large or evolving.
|
|
2
|
+
* Loose JSON shapes aligned with Sybilion dataset payloads (standalone apps / widgets).
|
|
4
3
|
*/
|
|
5
|
-
export type
|
|
4
|
+
export type SybilionDatasetSnapshot = {
|
|
6
5
|
id: number;
|
|
7
6
|
user_id?: number;
|
|
8
7
|
name: string;
|
|
@@ -34,14 +33,12 @@ export type MiniAppDataset = {
|
|
|
34
33
|
version?: number;
|
|
35
34
|
};
|
|
36
35
|
/** `forecastData` slice: analysis id (string) → forecast series blob. */
|
|
37
|
-
export type
|
|
38
|
-
export type
|
|
39
|
-
/** Cached Performance-tab API payload, or null if user never opened Performance / cache empty. */
|
|
36
|
+
export type SybilionForecastMap = Record<string, unknown>;
|
|
37
|
+
export type SybilionPerformanceSnapshot = {
|
|
40
38
|
table: unknown;
|
|
41
|
-
/** In-memory spaghetti lines from shell context, or null. */
|
|
42
39
|
spaghetti: unknown;
|
|
43
40
|
};
|
|
44
|
-
export type
|
|
41
|
+
export type SybilionDriversComparisonSnapshot = {
|
|
45
42
|
byRegion: unknown;
|
|
46
43
|
byCountry: unknown;
|
|
47
44
|
byCategory: unknown;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Standalone Sybilion apps (@sybilion/uilib)
|
|
2
|
+
|
|
3
|
+
Greenfield SPA on **your own origin**: `@sybilion/uilib` for layout/UI, `**SybilionAuthProvider`\*\* + Sybilion API for data—no iframe in the main client.
|
|
4
|
+
|
|
5
|
+
**Agents / humans:** Use `AppShell` + `AppShellMainContent`; stick to uilib spacing primitives (no random root horizontal gutters).
|
|
6
|
+
|
|
7
|
+
## 1. Dependencies and global CSS
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
yarn add react react-dom react-router-dom @auth0/auth0-react @sybilion/uilib
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Import tokens/fonts once:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import '@sybilion/uilib/standalone-global.css';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 2. Layout (AppShell)
|
|
20
|
+
|
|
21
|
+
Pattern: `[src/docs/DocsShell.tsx](https://github.com/Mir-Insight/uilib/blob/main/src/docs/DocsShell.tsx)` — `PageScroll` → `AppShell` → sidebar → `AppShellMainContent` with `AppHeaderHost`, `PageFooter`, main content (`Outlet` / routes). Add `Theme` and optional `SidebarProvider` from `@homecode/ui`.
|
|
22
|
+
|
|
23
|
+
## 3. Auth (`SybilionAuthProvider`)
|
|
24
|
+
|
|
25
|
+
Use inside `BrowserRouter` if redirects hit a callback route.
|
|
26
|
+
|
|
27
|
+
Env vars depend on bundler—for **Vite** only `import.meta.env.VITE_`\* is exposed client-side:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { SybilionAuthProvider } from '@sybilion/uilib';
|
|
31
|
+
|
|
32
|
+
const apiBaseUrl = import.meta.env.VITE_SYBILION_API_BASE_URL as string;
|
|
33
|
+
const auth0Domain = import.meta.env.VITE_AUTH0_DOMAIN as string;
|
|
34
|
+
const auth0ClientId = import.meta.env.VITE_AUTH0_CLIENT_ID as string;
|
|
35
|
+
|
|
36
|
+
<SybilionAuthProvider
|
|
37
|
+
apiBaseUrl={apiBaseUrl}
|
|
38
|
+
auth0Domain={auth0Domain}
|
|
39
|
+
auth0ClientId={auth0ClientId}
|
|
40
|
+
redirectUri={window.location.origin}
|
|
41
|
+
>
|
|
42
|
+
<App />
|
|
43
|
+
</SybilionAuthProvider>;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Flow:** Auth0 SPA → `POST {apiBaseUrl}/v1/auth/login` with `{ identity: "<Auth0 AT>", type: "auth0" }` → Sybilion JWT in response (`data.token` or `token`) → Bearer on API calls.
|
|
47
|
+
|
|
48
|
+
**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
|
+
|
|
50
|
+
| Layer | Configure |
|
|
51
|
+
| ---------------- | ------------------------------------------------------------------------------------- |
|
|
52
|
+
| **Auth0** | Callback, logout, and web origins → your URLs (+ previews). |
|
|
53
|
+
| **Sybilion API** | CORS → your deploy `Origin`. |
|
|
54
|
+
| **App** | `apiBaseUrl`, Auth0 `domain` / `clientId`, redirect usually `window.location.origin`. |
|
|
55
|
+
|
|
56
|
+
**Hooks:** `useSybilionAuth()`, `useSybilionApiFetch()` (or `createSybilionApiFetch` / `sybilionApiFetch` helpers).
|
|
57
|
+
|
|
58
|
+
## 4. Data
|
|
59
|
+
|
|
60
|
+
Fetch Sybilion API with the JWT above—no host `postMessage` bridge.
|
|
61
|
+
|
|
62
|
+
## Related
|
|
63
|
+
|
|
64
|
+
- [auth0-configuration-guide.md](https://github.com/Mir-Insight/sybilion-client/blob/main/docs/auth0-configuration-guide.md)
|
|
65
|
+
- [server-auth-verification.md](https://github.com/Mir-Insight/sybilion-client/blob/main/docs/server-auth-verification.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sybilion/uilib",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Sybilion Design System — React UI components (Webpack + Stylus)",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"default": "./src/index.ts"
|
|
31
31
|
},
|
|
32
32
|
"./src/*": "./src/*",
|
|
33
|
-
"./
|
|
33
|
+
"./standalone-global.css": "./assets/standalone-global.css"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
36
|
"assets",
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
"homepage": "https://github.com/Mir-Insight/uilib#readme",
|
|
78
78
|
"dependencies": {
|
|
79
|
+
"@homecode/ui": "^4.17.0",
|
|
79
80
|
"@phosphor-icons/react": "^2.1.10",
|
|
80
81
|
"@radix-ui/react-avatar": "^1.1.10",
|
|
81
82
|
"@radix-ui/react-checkbox": "^1.3.3",
|
|
@@ -104,12 +105,18 @@
|
|
|
104
105
|
"vaul": "^1.1.2"
|
|
105
106
|
},
|
|
106
107
|
"peerDependencies": {
|
|
107
|
-
"@
|
|
108
|
+
"@auth0/auth0-react": "^2.3.1",
|
|
108
109
|
"react": ">=18.0.0",
|
|
109
110
|
"react-dom": ">=18.0.0",
|
|
110
111
|
"react-router-dom": ">=6.0.0"
|
|
111
112
|
},
|
|
113
|
+
"peerDependenciesMeta": {
|
|
114
|
+
"@auth0/auth0-react": {
|
|
115
|
+
"optional": true
|
|
116
|
+
}
|
|
117
|
+
},
|
|
112
118
|
"devDependencies": {
|
|
119
|
+
"@auth0/auth0-react": "^2.3.1",
|
|
113
120
|
"@babel/core": "^7.20.12",
|
|
114
121
|
"@babel/preset-typescript": "^7.21.0",
|
|
115
122
|
"@homecode/ui": "^4.30.6",
|
|
@@ -10,6 +10,7 @@ import { InteractionOverlay } from '#uilib/components/ui/InteractionOverlay/Inte
|
|
|
10
10
|
import { TimeRangeControls } from '#uilib/components/ui/TimeRangeControls/TimeRangeControls';
|
|
11
11
|
import { ensureChartForecastBridge } from '#uilib/utils/chartConnectionPoint';
|
|
12
12
|
|
|
13
|
+
import { CHART_MARGINS } from './ChartAreaInteractive.constants';
|
|
13
14
|
import {
|
|
14
15
|
filterDataForTimeRange,
|
|
15
16
|
longDateFormatter,
|
|
@@ -151,7 +152,7 @@ export function ChartAreaInteractive({
|
|
|
151
152
|
formatDate: shortDateFormatter,
|
|
152
153
|
labelFormatter: (v: unknown) => longDateFormatter(v as string),
|
|
153
154
|
chartType: 'composed' as const,
|
|
154
|
-
margin: {
|
|
155
|
+
margin: { ...CHART_MARGINS },
|
|
155
156
|
hiddenSeries: seriesHidden,
|
|
156
157
|
onLegendClick: handleLegendClick,
|
|
157
158
|
onAnalysisSelect: onAnalysisSelect,
|
|
@@ -219,31 +219,6 @@
|
|
|
219
219
|
padding 0
|
|
220
220
|
padding-top 40px
|
|
221
221
|
|
|
222
|
-
// Sidebar group label
|
|
223
|
-
.sidebarGroupLabel
|
|
224
|
-
color var(--sidebar-foreground)
|
|
225
|
-
opacity 0.7
|
|
226
|
-
display flex
|
|
227
|
-
height 2rem
|
|
228
|
-
flex-shrink 0
|
|
229
|
-
align-items center
|
|
230
|
-
border-radius 0.375rem
|
|
231
|
-
padding-left 0.5rem
|
|
232
|
-
padding-right 0.5rem
|
|
233
|
-
font-size 0.75rem
|
|
234
|
-
font-weight 500
|
|
235
|
-
outline none
|
|
236
|
-
transition margin 200ms ease-linear, opacity 200ms ease-linear
|
|
237
|
-
&:focus-visible
|
|
238
|
-
box-shadow 0 0 0 2px var(--sidebar-ring)
|
|
239
|
-
& > svg
|
|
240
|
-
width 1rem
|
|
241
|
-
height 1rem
|
|
242
|
-
flex-shrink 0
|
|
243
|
-
&[data-collapsible="icon"]
|
|
244
|
-
margin-top -2rem
|
|
245
|
-
opacity 0
|
|
246
|
-
|
|
247
222
|
// Sidebar group action
|
|
248
223
|
.sidebarGroupAction
|
|
249
224
|
color var(--sidebar-foreground)
|
|
@@ -278,11 +253,6 @@
|
|
|
278
253
|
&[data-collapsible="icon"]
|
|
279
254
|
display none
|
|
280
255
|
|
|
281
|
-
// Sidebar group content
|
|
282
|
-
.sidebarGroupContent
|
|
283
|
-
width 100%
|
|
284
|
-
font-size 0.875rem
|
|
285
|
-
|
|
286
256
|
// Sidebar menu
|
|
287
257
|
.sidebarMenu
|
|
288
258
|
display flex
|
|
@@ -11,8 +11,6 @@ interface CssExports {
|
|
|
11
11
|
'sidebarFooter': string;
|
|
12
12
|
'sidebarGroup': string;
|
|
13
13
|
'sidebarGroupAction': string;
|
|
14
|
-
'sidebarGroupContent': string;
|
|
15
|
-
'sidebarGroupLabel': string;
|
|
16
14
|
'sidebarHeader': string;
|
|
17
15
|
'sidebarInput': string;
|
|
18
16
|
'sidebarMainShell': string;
|
|
@@ -738,23 +738,6 @@ function SidebarGroup({ className, ...props }: ComponentProps<'div'>) {
|
|
|
738
738
|
);
|
|
739
739
|
}
|
|
740
740
|
|
|
741
|
-
function SidebarGroupLabel({
|
|
742
|
-
className,
|
|
743
|
-
asChild = false,
|
|
744
|
-
...props
|
|
745
|
-
}: ComponentProps<'div'> & { asChild?: boolean }) {
|
|
746
|
-
const Comp = asChild ? Slot : 'div';
|
|
747
|
-
|
|
748
|
-
return (
|
|
749
|
-
<Comp
|
|
750
|
-
data-slot="sidebar-group-label"
|
|
751
|
-
data-sidebar="group-label"
|
|
752
|
-
className={cn(S.sidebarGroupLabel, className)}
|
|
753
|
-
{...props}
|
|
754
|
-
/>
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
|
|
758
741
|
function SidebarGroupAction({
|
|
759
742
|
className,
|
|
760
743
|
asChild = false,
|
|
@@ -772,17 +755,6 @@ function SidebarGroupAction({
|
|
|
772
755
|
);
|
|
773
756
|
}
|
|
774
757
|
|
|
775
|
-
function SidebarGroupContent({ className, ...props }: ComponentProps<'div'>) {
|
|
776
|
-
return (
|
|
777
|
-
<div
|
|
778
|
-
data-slot="sidebar-group-content"
|
|
779
|
-
data-sidebar="group-content"
|
|
780
|
-
className={cn(S.sidebarGroupContent, className)}
|
|
781
|
-
{...props}
|
|
782
|
-
/>
|
|
783
|
-
);
|
|
784
|
-
}
|
|
785
|
-
|
|
786
758
|
function SidebarMenu({ className, ...props }: ComponentProps<'ul'>) {
|
|
787
759
|
return (
|
|
788
760
|
<ul
|
|
@@ -952,8 +924,6 @@ export {
|
|
|
952
924
|
SidebarFooter,
|
|
953
925
|
SidebarGroup,
|
|
954
926
|
SidebarGroupAction,
|
|
955
|
-
SidebarGroupContent,
|
|
956
|
-
SidebarGroupLabel,
|
|
957
927
|
SidebarMenu,
|
|
958
928
|
SidebarMenuAction,
|
|
959
929
|
SidebarMenuBadge,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.chevronContainer
|
|
2
|
+
margin-left auto
|
|
3
|
+
|
|
4
|
+
.subMenuContainer
|
|
5
|
+
position relative
|
|
6
|
+
|
|
7
|
+
.subMenuBorder
|
|
8
|
+
position absolute
|
|
9
|
+
left var(--p-5)
|
|
10
|
+
top 0
|
|
11
|
+
bottom 26px
|
|
12
|
+
width 1px
|
|
13
|
+
background var(--sidebar-border)
|
|
14
|
+
|
|
15
|
+
.subMenuItem
|
|
16
|
+
position relative
|
|
17
|
+
margin-left var(--p-6)
|
|
18
|
+
|
|
19
|
+
&::before
|
|
20
|
+
content ''
|
|
21
|
+
position absolute
|
|
22
|
+
left calc(var(--p-1) * -1)
|
|
23
|
+
top var(--p-2)
|
|
24
|
+
bottom 0
|
|
25
|
+
width var(--p-3)
|
|
26
|
+
height var(--p-3)
|
|
27
|
+
border-left 1px solid var(--sidebar-border)
|
|
28
|
+
border-bottom 1px solid var(--sidebar-border)
|
|
29
|
+
border-bottom-left-radius var(--p-2)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// This file is automatically generated.
|
|
2
2
|
// Please do not change this file!
|
|
3
3
|
interface CssExports {
|
|
4
|
-
'
|
|
5
|
-
'
|
|
4
|
+
'chevronContainer': string;
|
|
5
|
+
'subMenuBorder': string;
|
|
6
|
+
'subMenuContainer': string;
|
|
7
|
+
'subMenuItem': string;
|
|
6
8
|
}
|
|
7
9
|
export const cssExports: CssExports;
|
|
8
10
|
export default cssExports;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
SidebarGroup,
|
|
5
|
+
SidebarMenu,
|
|
6
|
+
SidebarMenuButton,
|
|
7
|
+
SidebarMenuItem,
|
|
8
|
+
SidebarMenuSub,
|
|
9
|
+
SidebarMenuSubButton,
|
|
10
|
+
SidebarMenuSubItem,
|
|
11
|
+
} from '#uilib/components/ui/Sidebar/Sidebar';
|
|
12
|
+
import { SmartTextTruncate } from '#uilib/components/ui/SmartTextTruncate';
|
|
13
|
+
import { ChevronDown, ChevronRight, PackageOpen } from 'lucide-react';
|
|
14
|
+
|
|
15
|
+
import S from './SidebarDatasetsItemsGrouped.styl';
|
|
16
|
+
import {
|
|
17
|
+
type SidebarDatasetsItemsGroupBy,
|
|
18
|
+
type SidebarDatasetsItemsGroupedDataset,
|
|
19
|
+
groupSidebarDatasets,
|
|
20
|
+
} from './groupSidebarDatasets';
|
|
21
|
+
|
|
22
|
+
export type SidebarDatasetsItemsGroupedProps = {
|
|
23
|
+
groupBy: SidebarDatasetsItemsGroupBy;
|
|
24
|
+
datasets: SidebarDatasetsItemsGroupedDataset[];
|
|
25
|
+
selectedDatasetId?: number;
|
|
26
|
+
onDatasetClick?: (datasetId: number) => void;
|
|
27
|
+
/** When omitted, all groups start expanded. */
|
|
28
|
+
defaultExpandedGroupNames?: string[];
|
|
29
|
+
className?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function SidebarDatasetsItemsGrouped({
|
|
33
|
+
groupBy,
|
|
34
|
+
datasets,
|
|
35
|
+
selectedDatasetId,
|
|
36
|
+
onDatasetClick,
|
|
37
|
+
defaultExpandedGroupNames,
|
|
38
|
+
className,
|
|
39
|
+
}: SidebarDatasetsItemsGroupedProps) {
|
|
40
|
+
const grouped = useMemo(
|
|
41
|
+
() => groupSidebarDatasets(datasets, groupBy),
|
|
42
|
+
[datasets, groupBy],
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const [expanded, setExpanded] = useState<Set<string>>(new Set());
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (defaultExpandedGroupNames !== undefined) {
|
|
49
|
+
setExpanded(new Set(defaultExpandedGroupNames));
|
|
50
|
+
} else {
|
|
51
|
+
setExpanded(new Set(grouped.map(([name]) => name)));
|
|
52
|
+
}
|
|
53
|
+
}, [grouped, defaultExpandedGroupNames]);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (selectedDatasetId == null) return;
|
|
57
|
+
const groupName = grouped.find(([, ds]) =>
|
|
58
|
+
ds.some(d => d.id === selectedDatasetId),
|
|
59
|
+
)?.[0];
|
|
60
|
+
if (groupName) {
|
|
61
|
+
setExpanded(prev => new Set(prev).add(groupName));
|
|
62
|
+
}
|
|
63
|
+
}, [selectedDatasetId, grouped]);
|
|
64
|
+
|
|
65
|
+
const toggleGroup = (name: string) => {
|
|
66
|
+
setExpanded(prev => {
|
|
67
|
+
const next = new Set(prev);
|
|
68
|
+
if (next.has(name)) next.delete(name);
|
|
69
|
+
else next.add(name);
|
|
70
|
+
return next;
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<SidebarGroup className={className}>
|
|
76
|
+
<SidebarMenu>
|
|
77
|
+
{grouped.map(([groupName, groupDatasets]) => {
|
|
78
|
+
const isExpanded = expanded.has(groupName);
|
|
79
|
+
const parentActive = groupDatasets.some(
|
|
80
|
+
d => d.id === selectedDatasetId,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<SidebarMenuItem key={groupName}>
|
|
85
|
+
<SidebarMenuButton
|
|
86
|
+
type="button"
|
|
87
|
+
isActive={parentActive}
|
|
88
|
+
onClick={() => toggleGroup(groupName)}
|
|
89
|
+
>
|
|
90
|
+
<PackageOpen strokeWidth={1.5} size={16} />
|
|
91
|
+
<SmartTextTruncate>{groupName}</SmartTextTruncate>
|
|
92
|
+
<div className={S.chevronContainer}>
|
|
93
|
+
{isExpanded ? (
|
|
94
|
+
<ChevronDown size={12} />
|
|
95
|
+
) : (
|
|
96
|
+
<ChevronRight size={12} />
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
</SidebarMenuButton>
|
|
100
|
+
{isExpanded && (
|
|
101
|
+
<SidebarMenuSub className={S.subMenuContainer}>
|
|
102
|
+
<div className={S.subMenuBorder} />
|
|
103
|
+
{groupDatasets.map(dataset => (
|
|
104
|
+
<SidebarMenuSubItem
|
|
105
|
+
key={dataset.id}
|
|
106
|
+
className={S.subMenuItem}
|
|
107
|
+
>
|
|
108
|
+
<SidebarMenuSubButton
|
|
109
|
+
href={`#dataset-${dataset.id}`}
|
|
110
|
+
isActive={dataset.id === selectedDatasetId}
|
|
111
|
+
onClick={e => {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
onDatasetClick?.(dataset.id);
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
<SmartTextTruncate>{dataset.name}</SmartTextTruncate>
|
|
117
|
+
</SidebarMenuSubButton>
|
|
118
|
+
</SidebarMenuSubItem>
|
|
119
|
+
))}
|
|
120
|
+
</SidebarMenuSub>
|
|
121
|
+
)}
|
|
122
|
+
</SidebarMenuItem>
|
|
123
|
+
);
|
|
124
|
+
})}
|
|
125
|
+
</SidebarMenu>
|
|
126
|
+
</SidebarGroup>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { SybilionDatasetSnapshot } from '#uilib/types/sybilionDatasetSnapshots';
|
|
2
|
+
|
|
3
|
+
export type SidebarDatasetsItemsGroupedDataset = SybilionDatasetSnapshot;
|
|
4
|
+
|
|
5
|
+
export type SidebarDatasetsItemsGroupBy =
|
|
6
|
+
| 'target_type'
|
|
7
|
+
| 'regions'
|
|
8
|
+
| 'categories';
|
|
9
|
+
|
|
10
|
+
export type GroupedSidebarDatasetsEntry = readonly [
|
|
11
|
+
groupName: string,
|
|
12
|
+
datasets: SidebarDatasetsItemsGroupedDataset[],
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export function groupSidebarDatasets(
|
|
16
|
+
datasets: SidebarDatasetsItemsGroupedDataset[],
|
|
17
|
+
groupBy: SidebarDatasetsItemsGroupBy,
|
|
18
|
+
): GroupedSidebarDatasetsEntry[] {
|
|
19
|
+
const grouped: Record<string, SidebarDatasetsItemsGroupedDataset[]> = {};
|
|
20
|
+
|
|
21
|
+
datasets.forEach(dataset => {
|
|
22
|
+
if (groupBy === 'target_type') {
|
|
23
|
+
const key = dataset.target_type?.name || 'Unknown';
|
|
24
|
+
if (!grouped[key]) grouped[key] = [];
|
|
25
|
+
grouped[key].push(dataset);
|
|
26
|
+
} else if (groupBy === 'regions') {
|
|
27
|
+
if (dataset.regions && dataset.regions.length > 0) {
|
|
28
|
+
const mostSpecificRegion = dataset.regions[dataset.regions.length - 1];
|
|
29
|
+
const key = mostSpecificRegion.name || 'Unknown';
|
|
30
|
+
if (!grouped[key]) grouped[key] = [];
|
|
31
|
+
grouped[key].push(dataset);
|
|
32
|
+
} else {
|
|
33
|
+
const key = 'No Region';
|
|
34
|
+
if (!grouped[key]) grouped[key] = [];
|
|
35
|
+
grouped[key].push(dataset);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
const key = dataset.category?.name || 'Unknown';
|
|
39
|
+
if (!grouped[key]) grouped[key] = [];
|
|
40
|
+
grouped[key].push(dataset);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return Object.entries(grouped)
|
|
45
|
+
.map(
|
|
46
|
+
([name, ds]) =>
|
|
47
|
+
[name, [...ds].sort((a, b) => a.name.localeCompare(b.name))] as const,
|
|
48
|
+
)
|
|
49
|
+
.filter(([, ds]) => ds.length > 0)
|
|
50
|
+
.sort((a, b) => a[0].localeCompare(b[0]));
|
|
51
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export {
|
|
2
|
+
SidebarDatasetsItemsGrouped,
|
|
3
|
+
type SidebarDatasetsItemsGroupedProps,
|
|
4
|
+
} from './SidebarDatasetsItemsGrouped';
|
|
5
|
+
export type {
|
|
6
|
+
GroupedSidebarDatasetsEntry,
|
|
7
|
+
SidebarDatasetsItemsGroupBy,
|
|
8
|
+
SidebarDatasetsItemsGroupedDataset,
|
|
9
|
+
} from './groupSidebarDatasets';
|
|
10
|
+
export { groupSidebarDatasets } from './groupSidebarDatasets';
|