@sybilion/uilib 1.2.3 → 1.2.5
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/appChromeAnchors.js +3 -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 +16 -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 +8 -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/dist/standalone/vite-sybilion-standalone-dev.d.ts +13 -0
- package/dist/standalone/vite-sybilion-standalone-dev.js +49 -0
- package/docs/standalone-apps.md +172 -43
- package/package.json +13 -3
- package/src/components/ui/AppHeader/appChromeAnchors.ts +3 -0
- package/src/components/ui/AppHeader/index.ts +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 +7 -0
- package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.styl.d.ts +7 -0
- package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.tsx +51 -0
- package/src/components/widgets/SybilionAppHeader/index.ts +4 -0
- package/src/docs/pages/StandaloneAppLayoutPage.tsx +102 -34
- package/src/index.ts +2 -0
- package/src/standalone/vite-sybilion-standalone-dev.ts +65 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export {
|
|
2
|
+
WorkspaceAppSwitcher,
|
|
3
|
+
type WorkspaceAppSwitcherProps,
|
|
4
|
+
} from './WorkspaceAppSwitcher';
|
|
5
|
+
export type { WorkspaceAppEntry } from './workspaceApp.types';
|
|
6
|
+
export { WORKSPACE_APP_SLUG_BASE_PATH } from './workspaceApp.types';
|
|
7
|
+
export { WORKSPACE_APPS_LS_KEY } from './workspaceAppsConstants';
|
|
8
|
+
export {
|
|
9
|
+
readWorkspaceAppsFromLocalStorage,
|
|
10
|
+
writeWorkspaceAppsToLocalStorage,
|
|
11
|
+
} from './workspaceAppsLocalStorage';
|
|
12
|
+
export {
|
|
13
|
+
WORKSPACE_APP_ICONS,
|
|
14
|
+
type WorkspaceAppIconKey,
|
|
15
|
+
isWorkspaceAppIconKey,
|
|
16
|
+
} from './workspaceAppIcons';
|
|
17
|
+
export {
|
|
18
|
+
findWorkspaceAppByPathname,
|
|
19
|
+
workspaceAppSlugPath,
|
|
20
|
+
} from './workspaceAppPaths';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { WorkspaceAppIconKey } from './workspaceAppIcons';
|
|
2
|
+
|
|
3
|
+
/** Path segment for slug apps: pathname matches `/apps/{id}` */
|
|
4
|
+
export const WORKSPACE_APP_SLUG_BASE_PATH = '/apps';
|
|
5
|
+
|
|
6
|
+
/** One surface in the workspace app switcher (serializable for localStorage). */
|
|
7
|
+
export type WorkspaceAppEntry = {
|
|
8
|
+
/** Slug (e.g. `my-custom-app` → `https://sybilion.io/apps/my-custom-app` via `href`). */
|
|
9
|
+
id: string;
|
|
10
|
+
displayName: string;
|
|
11
|
+
subtitle: string;
|
|
12
|
+
iconKey: WorkspaceAppIconKey;
|
|
13
|
+
accent: string;
|
|
14
|
+
accentMuted: string;
|
|
15
|
+
href: string;
|
|
16
|
+
/**
|
|
17
|
+
* Optional. Native / multi-route shells: match current app by pathname prefix.
|
|
18
|
+
* Omit for custom apps deployed under a single slug (`/apps/{id}` only).
|
|
19
|
+
*/
|
|
20
|
+
matchPathPrefixes?: readonly string[];
|
|
21
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Boat, Package, TreeStructure } from '@phosphor-icons/react';
|
|
4
|
+
import { ClockAlert, LayoutDashboard } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
type IconLike = ComponentType<{ className?: string; 'aria-hidden'?: boolean }>;
|
|
7
|
+
|
|
8
|
+
export type WorkspaceAppIconKey =
|
|
9
|
+
| 'grid-four'
|
|
10
|
+
| 'clock'
|
|
11
|
+
| 'boat'
|
|
12
|
+
| 'package'
|
|
13
|
+
| 'tree-structure';
|
|
14
|
+
|
|
15
|
+
export const WORKSPACE_APP_ICONS: Record<WorkspaceAppIconKey, IconLike> = {
|
|
16
|
+
'grid-four': LayoutDashboard as IconLike,
|
|
17
|
+
clock: ClockAlert as IconLike,
|
|
18
|
+
boat: Boat as IconLike,
|
|
19
|
+
package: Package as IconLike,
|
|
20
|
+
'tree-structure': TreeStructure as IconLike,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const ICON_KEYS = Object.keys(WORKSPACE_APP_ICONS) as WorkspaceAppIconKey[];
|
|
24
|
+
|
|
25
|
+
export function isWorkspaceAppIconKey(v: string): v is WorkspaceAppIconKey {
|
|
26
|
+
return (ICON_KEYS as string[]).includes(v);
|
|
27
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { WorkspaceAppEntry } from './workspaceApp.types';
|
|
2
|
+
import { WORKSPACE_APP_SLUG_BASE_PATH } from './workspaceApp.types';
|
|
3
|
+
|
|
4
|
+
export function workspaceAppSlugPath(id: string): string {
|
|
5
|
+
return `${WORKSPACE_APP_SLUG_BASE_PATH}/${id}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function pathnameMatchesPrefix(pathname: string, prefix: string): boolean {
|
|
9
|
+
return pathname === prefix || pathname.startsWith(`${prefix}/`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function pathnameMatchesSlugApp(pathname: string, id: string): boolean {
|
|
13
|
+
const base = workspaceAppSlugPath(id);
|
|
14
|
+
return pathname === base || pathname.startsWith(`${base}/`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function findWorkspaceAppByPathname(
|
|
18
|
+
pathname: string,
|
|
19
|
+
apps: readonly WorkspaceAppEntry[],
|
|
20
|
+
): WorkspaceAppEntry | null {
|
|
21
|
+
for (const app of apps) {
|
|
22
|
+
const prefixes = app.matchPathPrefixes;
|
|
23
|
+
if (prefixes && prefixes.length > 0) {
|
|
24
|
+
if (prefixes.some(prefix => pathnameMatchesPrefix(pathname, prefix))) {
|
|
25
|
+
return app;
|
|
26
|
+
}
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (pathnameMatchesSlugApp(pathname, app.id)) {
|
|
30
|
+
return app;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { WorkspaceAppEntry } from './workspaceApp.types';
|
|
2
|
+
import { isWorkspaceAppIconKey } from './workspaceAppIcons';
|
|
3
|
+
|
|
4
|
+
function parseEntry(raw: unknown): WorkspaceAppEntry | null {
|
|
5
|
+
if (!raw || typeof raw !== 'object') {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const o = raw as Record<string, unknown>;
|
|
9
|
+
const idRaw = o.id ?? o.nativeId;
|
|
10
|
+
const displayName = o.displayName;
|
|
11
|
+
const subtitle = o.subtitle;
|
|
12
|
+
const iconKey = o.iconKey;
|
|
13
|
+
const accent = o.accent;
|
|
14
|
+
const accentMuted = o.accentMuted;
|
|
15
|
+
const href = o.href;
|
|
16
|
+
const prefixesRaw = o.matchPathPrefixes;
|
|
17
|
+
|
|
18
|
+
if (
|
|
19
|
+
typeof idRaw !== 'string' ||
|
|
20
|
+
!idRaw ||
|
|
21
|
+
typeof displayName !== 'string' ||
|
|
22
|
+
typeof subtitle !== 'string' ||
|
|
23
|
+
typeof iconKey !== 'string' ||
|
|
24
|
+
!isWorkspaceAppIconKey(iconKey) ||
|
|
25
|
+
typeof accent !== 'string' ||
|
|
26
|
+
typeof accentMuted !== 'string' ||
|
|
27
|
+
typeof href !== 'string' ||
|
|
28
|
+
!href
|
|
29
|
+
) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let matchPathPrefixes: WorkspaceAppEntry['matchPathPrefixes'];
|
|
34
|
+
if (prefixesRaw != null) {
|
|
35
|
+
if (!Array.isArray(prefixesRaw)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const prefixes = prefixesRaw.filter(
|
|
39
|
+
(p): p is string => typeof p === 'string',
|
|
40
|
+
);
|
|
41
|
+
if (prefixes.length > 0) {
|
|
42
|
+
matchPathPrefixes = prefixes;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
id: idRaw,
|
|
48
|
+
displayName,
|
|
49
|
+
subtitle,
|
|
50
|
+
iconKey,
|
|
51
|
+
accent,
|
|
52
|
+
accentMuted,
|
|
53
|
+
href,
|
|
54
|
+
matchPathPrefixes,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read validated workspace apps JSON from localStorage; returns null if missing or invalid.
|
|
60
|
+
*/
|
|
61
|
+
export function readWorkspaceAppsFromLocalStorage(
|
|
62
|
+
key: string,
|
|
63
|
+
): WorkspaceAppEntry[] | null {
|
|
64
|
+
try {
|
|
65
|
+
const raw = localStorage.getItem(key);
|
|
66
|
+
if (raw == null || raw === '') {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const parsed: unknown = JSON.parse(raw);
|
|
70
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const apps: WorkspaceAppEntry[] = [];
|
|
74
|
+
for (const item of parsed) {
|
|
75
|
+
const entry = parseEntry(item);
|
|
76
|
+
if (entry) {
|
|
77
|
+
apps.push(entry);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return apps.length > 0 ? apps : null;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function writeWorkspaceAppsToLocalStorage(
|
|
87
|
+
key: string,
|
|
88
|
+
apps: WorkspaceAppEntry[],
|
|
89
|
+
): void {
|
|
90
|
+
try {
|
|
91
|
+
localStorage.setItem(key, JSON.stringify(apps));
|
|
92
|
+
} catch {
|
|
93
|
+
// ignore quota / private mode
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
|
|
3
|
+
import { AppHeaderPortal } from '#uilib/components/ui/AppHeader/AppHeader';
|
|
4
|
+
import { PAGE_HEADER_ACTIONS_ID } from '#uilib/components/ui/AppHeader/appChromeAnchors';
|
|
5
|
+
import { Gap } from '#uilib/components/ui/Gap/Gap';
|
|
6
|
+
import { NavUserHeader } from '#uilib/components/ui/NavUserHeader';
|
|
7
|
+
import type { NavUserHeaderProps } from '#uilib/components/ui/NavUserHeader';
|
|
8
|
+
import {
|
|
9
|
+
WorkspaceAppSwitcher,
|
|
10
|
+
type WorkspaceAppSwitcherProps,
|
|
11
|
+
} from '#uilib/components/ui/WorkspaceAppSwitcher';
|
|
12
|
+
|
|
13
|
+
import S from './SybilionAppHeader.styl';
|
|
14
|
+
|
|
15
|
+
export type SybilionAppHeaderProps = WorkspaceAppSwitcherProps &
|
|
16
|
+
NavUserHeaderProps & {
|
|
17
|
+
pageHeaderId?: string;
|
|
18
|
+
actionsAnchorId?: string;
|
|
19
|
+
actionsAnchorClassName?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function SybilionAppHeader({
|
|
23
|
+
pageHeaderId,
|
|
24
|
+
actionsAnchorId = PAGE_HEADER_ACTIONS_ID,
|
|
25
|
+
actionsAnchorClassName,
|
|
26
|
+
pathname,
|
|
27
|
+
onNavigate,
|
|
28
|
+
authenticated,
|
|
29
|
+
defaultApps,
|
|
30
|
+
appsStorageKey,
|
|
31
|
+
...navUserHeaderProps
|
|
32
|
+
}: SybilionAppHeaderProps) {
|
|
33
|
+
return (
|
|
34
|
+
<AppHeaderPortal pageHeaderId={pageHeaderId}>
|
|
35
|
+
<WorkspaceAppSwitcher
|
|
36
|
+
pathname={pathname}
|
|
37
|
+
onNavigate={onNavigate}
|
|
38
|
+
authenticated={authenticated}
|
|
39
|
+
defaultApps={defaultApps}
|
|
40
|
+
appsStorageKey={appsStorageKey}
|
|
41
|
+
/>
|
|
42
|
+
<Gap />
|
|
43
|
+
<div
|
|
44
|
+
id={actionsAnchorId}
|
|
45
|
+
className={cn(S.actionsAnchor, actionsAnchorClassName)}
|
|
46
|
+
>
|
|
47
|
+
<NavUserHeader {...navUserHeaderProps} />
|
|
48
|
+
</div>
|
|
49
|
+
</AppHeaderPortal>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
import { useCallback, useState } from 'react';
|
|
2
2
|
|
|
3
|
-
import { AppHeaderHost
|
|
4
|
-
import {
|
|
5
|
-
import { NavUserHeader } from '#uilib/components/ui/NavUserHeader/NavUserHeader';
|
|
3
|
+
import { AppHeaderHost } from '#uilib/components/ui/AppHeader';
|
|
4
|
+
import { DropdownMenuItem } from '#uilib/components/ui/DropdownMenu';
|
|
6
5
|
import {
|
|
7
6
|
AppShell,
|
|
8
7
|
AppShellMainContent,
|
|
8
|
+
PageContent,
|
|
9
9
|
PageContentSection,
|
|
10
|
+
PageHeader,
|
|
10
11
|
} from '#uilib/components/ui/Page';
|
|
11
12
|
import { PageFooter } from '#uilib/components/ui/Page/PageFooter/PageFooter';
|
|
12
13
|
import { PageScroll } from '#uilib/components/ui/Page/PageScroll/PageScroll';
|
|
13
14
|
import {
|
|
14
15
|
Sidebar,
|
|
15
16
|
SidebarContent,
|
|
16
|
-
SidebarGroup,
|
|
17
17
|
SidebarMenu,
|
|
18
18
|
SidebarMenuButton,
|
|
19
19
|
SidebarMenuItem,
|
|
20
20
|
SidebarProvider,
|
|
21
|
-
SidebarTrigger,
|
|
22
21
|
} from '#uilib/components/ui/Sidebar/Sidebar';
|
|
22
|
+
import type { WorkspaceAppEntry } from '#uilib/components/ui/WorkspaceAppSwitcher';
|
|
23
23
|
import {
|
|
24
24
|
SidebarDatasetsItemsGrouped,
|
|
25
25
|
type SidebarDatasetsItemsGroupedDataset,
|
|
26
26
|
} from '#uilib/components/widgets/SidebarDatasetsItemsGrouped';
|
|
27
|
+
import { SybilionAppHeader } from '#uilib/components/widgets/SybilionAppHeader';
|
|
28
|
+
import { GearSixIcon, UserCircleIcon } from '@phosphor-icons/react';
|
|
27
29
|
import { House } from 'lucide-react';
|
|
28
30
|
|
|
29
31
|
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
@@ -91,6 +93,40 @@ type PreviewPanel = 'home' | 'datasets';
|
|
|
91
93
|
|
|
92
94
|
const TEST_HEADER_ID = 'test-header-id';
|
|
93
95
|
|
|
96
|
+
const DOCS_WORKSPACE_APPS_LS_KEY = 'uilib.docs.workspaceApps';
|
|
97
|
+
|
|
98
|
+
const DOCS_PREVIEW_APPS: WorkspaceAppEntry[] = [
|
|
99
|
+
{
|
|
100
|
+
id: 'docs-home',
|
|
101
|
+
displayName: 'Datasets Dashboard',
|
|
102
|
+
subtitle: 'Data analysis tools',
|
|
103
|
+
iconKey: 'grid-four',
|
|
104
|
+
accent: '#0d9488',
|
|
105
|
+
accentMuted: 'rgba(13, 148, 136, 0.12)',
|
|
106
|
+
href: '/docs-preview/home',
|
|
107
|
+
matchPathPrefixes: ['/docs-preview/home'],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'docs-datasets',
|
|
111
|
+
displayName: 'Datasets',
|
|
112
|
+
subtitle: 'Dataset library',
|
|
113
|
+
iconKey: 'package',
|
|
114
|
+
accent: '#6366f1',
|
|
115
|
+
accentMuted: 'rgba(99, 102, 241, 0.12)',
|
|
116
|
+
href: '/docs-preview/datasets',
|
|
117
|
+
matchPathPrefixes: ['/docs-preview/datasets'],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'my-custom-app',
|
|
121
|
+
displayName: 'Custom app',
|
|
122
|
+
subtitle: 'Slug-only app (sybilion.io/apps/…)',
|
|
123
|
+
iconKey: 'boat',
|
|
124
|
+
accent: '#0ea5e9',
|
|
125
|
+
accentMuted: 'rgba(14, 165, 233, 0.12)',
|
|
126
|
+
href: '/apps/my-custom-app',
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
|
|
94
130
|
function DemoAppSidebar({
|
|
95
131
|
panel,
|
|
96
132
|
onSelectPanel,
|
|
@@ -138,31 +174,49 @@ function DemoAppSidebar({
|
|
|
138
174
|
function DemoMainBody({ panel }: { panel: PreviewPanel }) {
|
|
139
175
|
if (panel === 'home') {
|
|
140
176
|
return (
|
|
141
|
-
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
177
|
+
<>
|
|
178
|
+
<PageHeader
|
|
179
|
+
breadcrumbs={[{ label: 'Home' }]}
|
|
180
|
+
title="Home"
|
|
181
|
+
subheader="Greenfield shell preview (no SDK or auth in this embed)."
|
|
182
|
+
/>
|
|
183
|
+
<PageContent>
|
|
184
|
+
<PageContentSection>
|
|
185
|
+
<p style={{ margin: 0, color: 'var(--muted-foreground)' }}>
|
|
186
|
+
Preview: greenfield shell layout only (no SDK or auth). Real SPA
|
|
187
|
+
uses one top-level Router; this embed cannot nest MemoryRouter
|
|
188
|
+
under docs BrowserRouter — sidebar uses local state instead of{' '}
|
|
189
|
+
<code>NavLink</code> / <code>Routes</code>.
|
|
190
|
+
</p>
|
|
191
|
+
</PageContentSection>
|
|
192
|
+
</PageContent>
|
|
193
|
+
</>
|
|
150
194
|
);
|
|
151
195
|
}
|
|
152
196
|
|
|
153
197
|
return (
|
|
154
|
-
|
|
155
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
198
|
+
<>
|
|
199
|
+
<PageHeader
|
|
200
|
+
breadcrumbs={[{ label: 'Datasets' }]}
|
|
201
|
+
title="Datasets"
|
|
202
|
+
subheader="Sidebar + dataset list use local state (not Router)."
|
|
203
|
+
/>
|
|
204
|
+
<PageContent>
|
|
205
|
+
<PageContentSection>
|
|
206
|
+
<p style={{ margin: 0, color: 'var(--muted-foreground)' }}>
|
|
207
|
+
Use sidebar links and dataset rows; panel state stays inside this
|
|
208
|
+
box.
|
|
209
|
+
</p>
|
|
210
|
+
</PageContentSection>
|
|
211
|
+
</PageContent>
|
|
212
|
+
</>
|
|
160
213
|
);
|
|
161
214
|
}
|
|
162
215
|
|
|
163
216
|
function StandaloneLayoutPreview() {
|
|
164
217
|
const [theme, setTheme] = useState<'light' | 'dark'>('light');
|
|
165
218
|
const [panel, setPanel] = useState<PreviewPanel>('home');
|
|
219
|
+
const [previewPath, setPreviewPath] = useState('/docs-preview/home');
|
|
166
220
|
const [selectedDatasetId, setSelectedDatasetId] = useState<
|
|
167
221
|
number | undefined
|
|
168
222
|
>();
|
|
@@ -191,20 +245,34 @@ function StandaloneLayoutPreview() {
|
|
|
191
245
|
/>
|
|
192
246
|
}
|
|
193
247
|
>
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
248
|
+
<SybilionAppHeader
|
|
249
|
+
pageHeaderId={TEST_HEADER_ID}
|
|
250
|
+
pathname={previewPath}
|
|
251
|
+
onNavigate={setPreviewPath}
|
|
252
|
+
authenticated
|
|
253
|
+
appsStorageKey={DOCS_WORKSPACE_APPS_LS_KEY}
|
|
254
|
+
defaultApps={DOCS_PREVIEW_APPS}
|
|
255
|
+
user={{
|
|
256
|
+
name: 'Preview User',
|
|
257
|
+
email: 'preview@example.com',
|
|
258
|
+
avatar: '',
|
|
259
|
+
}}
|
|
260
|
+
theme={theme}
|
|
261
|
+
onThemeToggle={onThemeToggle}
|
|
262
|
+
onLogout={() => undefined}
|
|
263
|
+
menuItems={
|
|
264
|
+
<>
|
|
265
|
+
<DropdownMenuItem>
|
|
266
|
+
<UserCircleIcon />
|
|
267
|
+
Account (preview)
|
|
268
|
+
</DropdownMenuItem>
|
|
269
|
+
<DropdownMenuItem>
|
|
270
|
+
<GearSixIcon />
|
|
271
|
+
Settings (preview)
|
|
272
|
+
</DropdownMenuItem>
|
|
273
|
+
</>
|
|
274
|
+
}
|
|
275
|
+
/>
|
|
208
276
|
<DemoMainBody panel={panel} />
|
|
209
277
|
</AppShellMainContent>
|
|
210
278
|
</AppShell>
|
package/src/index.ts
CHANGED
|
@@ -55,4 +55,6 @@ export * from './components/ui/Toggle';
|
|
|
55
55
|
export * from './components/ui/ToggleGroup';
|
|
56
56
|
export * from './components/ui/Tooltip';
|
|
57
57
|
export * from './components/ui/VimeoEmbed';
|
|
58
|
+
export * from './components/ui/WorkspaceAppSwitcher';
|
|
58
59
|
export * from './components/widgets/SidebarDatasetsItemsGrouped';
|
|
60
|
+
export * from './components/widgets/SybilionAppHeader';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { UserConfig } from 'vite';
|
|
2
|
+
import { loadEnv } from 'vite';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_PORT = 3000;
|
|
5
|
+
const SYBILION_API_ENV = 'VITE_SYBILION_API_BASE_URL';
|
|
6
|
+
|
|
7
|
+
export type SybilionStandaloneViteDevOptions = {
|
|
8
|
+
mode: string;
|
|
9
|
+
/** Directory containing `.env*` files. @default process.cwd() */
|
|
10
|
+
envDir?: string;
|
|
11
|
+
/** Prefix proxied to Sybilion API (SDK `apiPrefix`). @default `/api` */
|
|
12
|
+
apiPrefix?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let warnedMissingApiUrl = false;
|
|
16
|
+
|
|
17
|
+
function parsePort(raw: string | undefined): number {
|
|
18
|
+
if (raw == null || raw === '') return DEFAULT_PORT;
|
|
19
|
+
const n = Number.parseInt(raw, 10);
|
|
20
|
+
if (!Number.isFinite(n) || n <= 0 || n > 65_535) return DEFAULT_PORT;
|
|
21
|
+
return n;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeApiPrefix(apiPrefix: string): string {
|
|
25
|
+
return apiPrefix.startsWith('/') ? apiPrefix : `/${apiPrefix}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Vite `server` + `preview` fragment for standalone Sybilion SPAs: same-origin `/api` in dev,
|
|
30
|
+
* proxied to `VITE_SYBILION_API_BASE_URL`. Uses `PORT` from env (default `3000`).
|
|
31
|
+
*/
|
|
32
|
+
export function sybilionStandaloneViteDev(
|
|
33
|
+
options: SybilionStandaloneViteDevOptions,
|
|
34
|
+
): Pick<UserConfig, 'server' | 'preview'> {
|
|
35
|
+
const envDir = options.envDir ?? process.cwd();
|
|
36
|
+
const apiPrefix = normalizeApiPrefix(options.apiPrefix ?? '/api');
|
|
37
|
+
const env = loadEnv(options.mode, envDir, '');
|
|
38
|
+
const port = parsePort(env.PORT);
|
|
39
|
+
const target = (env[SYBILION_API_ENV] ?? '').replace(/\/$/, '');
|
|
40
|
+
|
|
41
|
+
const proxy: NonNullable<UserConfig['server']>['proxy'] = {};
|
|
42
|
+
|
|
43
|
+
if (target) {
|
|
44
|
+
proxy[apiPrefix] = {
|
|
45
|
+
target,
|
|
46
|
+
changeOrigin: true,
|
|
47
|
+
secure: true,
|
|
48
|
+
};
|
|
49
|
+
} else if (options.mode === 'development' && !warnedMissingApiUrl) {
|
|
50
|
+
warnedMissingApiUrl = true;
|
|
51
|
+
console.warn(
|
|
52
|
+
`[@sybilion/uilib] ${SYBILION_API_ENV} is not set; API dev proxy disabled.`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const serverPreview = {
|
|
57
|
+
port,
|
|
58
|
+
proxy,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
server: serverPreview,
|
|
63
|
+
preview: serverPreview,
|
|
64
|
+
};
|
|
65
|
+
}
|