@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
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import type { CSSProperties } from 'react';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { Button } from '#uilib/components/ui/Button/Button';
|
|
6
|
+
import {
|
|
7
|
+
DropdownMenu,
|
|
8
|
+
DropdownMenuContent,
|
|
9
|
+
DropdownMenuItem,
|
|
10
|
+
DropdownMenuTrigger,
|
|
11
|
+
} from '#uilib/components/ui/DropdownMenu';
|
|
12
|
+
import type { WorkspaceAppEntry } from '#uilib/components/ui/WorkspaceAppSwitcher/workspaceApp.types';
|
|
13
|
+
import { WORKSPACE_APP_ICONS } from '#uilib/components/ui/WorkspaceAppSwitcher/workspaceAppIcons';
|
|
14
|
+
import { findWorkspaceAppByPathname } from '#uilib/components/ui/WorkspaceAppSwitcher/workspaceAppPaths';
|
|
15
|
+
import { readWorkspaceAppsFromLocalStorage } from '#uilib/components/ui/WorkspaceAppSwitcher/workspaceAppsLocalStorage';
|
|
16
|
+
import { ChevronDown } from 'lucide-react';
|
|
17
|
+
|
|
18
|
+
import S from './WorkspaceAppSwitcher.styl';
|
|
19
|
+
|
|
20
|
+
export type WorkspaceAppSwitcherProps = {
|
|
21
|
+
pathname: string;
|
|
22
|
+
onNavigate: (href: string) => void;
|
|
23
|
+
/** When false, renders nothing (host maps from auth Loading + signed-in). */
|
|
24
|
+
authenticated?: boolean;
|
|
25
|
+
/** Fallback when localStorage missing/invalid/unset — keep referentially stable across renders where possible */
|
|
26
|
+
defaultApps?: WorkspaceAppEntry[];
|
|
27
|
+
/** When set, read apps from localStorage (`readWorkspaceAppsFromLocalStorage`). */
|
|
28
|
+
appsStorageKey?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function entryKey(entry: WorkspaceAppEntry): string {
|
|
32
|
+
return entry.id;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function IconTile({
|
|
36
|
+
iconKey,
|
|
37
|
+
accentMuted,
|
|
38
|
+
accent,
|
|
39
|
+
}: {
|
|
40
|
+
iconKey: WorkspaceAppEntry['iconKey'];
|
|
41
|
+
accentMuted: string;
|
|
42
|
+
accent: string;
|
|
43
|
+
}) {
|
|
44
|
+
const IconComponent = WORKSPACE_APP_ICONS[iconKey];
|
|
45
|
+
return (
|
|
46
|
+
<span
|
|
47
|
+
className={S.iconTile}
|
|
48
|
+
style={
|
|
49
|
+
{
|
|
50
|
+
'--bg-color': accentMuted,
|
|
51
|
+
'--fg-color': accent,
|
|
52
|
+
} as CSSProperties
|
|
53
|
+
}
|
|
54
|
+
>
|
|
55
|
+
<IconComponent className={S.icon} aria-hidden />
|
|
56
|
+
</span>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function useResolvedApps(
|
|
61
|
+
appsStorageKey: string | undefined,
|
|
62
|
+
defaultApps: WorkspaceAppEntry[] | undefined,
|
|
63
|
+
): WorkspaceAppEntry[] {
|
|
64
|
+
const [apps, setApps] = useState<WorkspaceAppEntry[]>(() => {
|
|
65
|
+
if (
|
|
66
|
+
typeof localStorage !== 'undefined' &&
|
|
67
|
+
appsStorageKey != null &&
|
|
68
|
+
appsStorageKey !== ''
|
|
69
|
+
) {
|
|
70
|
+
const fromLs = readWorkspaceAppsFromLocalStorage(appsStorageKey);
|
|
71
|
+
if (fromLs != null && fromLs.length > 0) {
|
|
72
|
+
return fromLs;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return defaultApps ?? [];
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!appsStorageKey || typeof localStorage === 'undefined') {
|
|
80
|
+
setApps(defaultApps ?? []);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const fromLs = readWorkspaceAppsFromLocalStorage(appsStorageKey);
|
|
84
|
+
setApps(fromLs != null && fromLs.length > 0 ? fromLs : (defaultApps ?? []));
|
|
85
|
+
}, [appsStorageKey, defaultApps]);
|
|
86
|
+
|
|
87
|
+
return apps;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function WorkspaceAppSwitcher({
|
|
91
|
+
pathname,
|
|
92
|
+
onNavigate,
|
|
93
|
+
authenticated = true,
|
|
94
|
+
defaultApps,
|
|
95
|
+
appsStorageKey,
|
|
96
|
+
}: WorkspaceAppSwitcherProps) {
|
|
97
|
+
const apps = useResolvedApps(appsStorageKey, defaultApps);
|
|
98
|
+
|
|
99
|
+
const current = findWorkspaceAppByPathname(pathname, apps);
|
|
100
|
+
|
|
101
|
+
if (!authenticated || apps.length === 0) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const displayApp = current ?? apps[0];
|
|
106
|
+
if (!displayApp) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<DropdownMenu>
|
|
112
|
+
<DropdownMenuTrigger asChild>
|
|
113
|
+
<Button
|
|
114
|
+
variant="ghost"
|
|
115
|
+
className={S.trigger}
|
|
116
|
+
aria-label="Select workspace app"
|
|
117
|
+
>
|
|
118
|
+
<IconTile
|
|
119
|
+
iconKey={displayApp.iconKey}
|
|
120
|
+
accentMuted={displayApp.accentMuted}
|
|
121
|
+
accent={displayApp.accent}
|
|
122
|
+
/>
|
|
123
|
+
<span className={S.textCol}>
|
|
124
|
+
<span className={S.name}>{displayApp.displayName}</span>
|
|
125
|
+
<span className={S.sub}>{displayApp.subtitle}</span>
|
|
126
|
+
</span>
|
|
127
|
+
<ChevronDown size={12} />
|
|
128
|
+
</Button>
|
|
129
|
+
</DropdownMenuTrigger>
|
|
130
|
+
<DropdownMenuContent
|
|
131
|
+
className={S.menuContent}
|
|
132
|
+
align="start"
|
|
133
|
+
sideOffset={8}
|
|
134
|
+
elevation="md"
|
|
135
|
+
>
|
|
136
|
+
{apps.map(entry => {
|
|
137
|
+
const active =
|
|
138
|
+
current != null ? entryKey(entry) === entryKey(current) : false;
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<DropdownMenuItem
|
|
142
|
+
key={entry.id}
|
|
143
|
+
className={cn(S.item, active && S.itemActive)}
|
|
144
|
+
onSelect={() => {
|
|
145
|
+
onNavigate(entry.href);
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
<IconTile
|
|
149
|
+
iconKey={entry.iconKey}
|
|
150
|
+
accentMuted={entry.accentMuted}
|
|
151
|
+
accent={entry.accent}
|
|
152
|
+
/>
|
|
153
|
+
<span className={S.textCol}>
|
|
154
|
+
<span className={S.name}>{entry.displayName}</span>
|
|
155
|
+
<span className={S.sub}>{entry.subtitle}</span>
|
|
156
|
+
</span>
|
|
157
|
+
</DropdownMenuItem>
|
|
158
|
+
);
|
|
159
|
+
})}
|
|
160
|
+
</DropdownMenuContent>
|
|
161
|
+
</DropdownMenu>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
@@ -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,53 @@
|
|
|
1
|
+
@import '../../../lib/theme.styl'
|
|
2
|
+
|
|
3
|
+
.actionsAnchor
|
|
4
|
+
display flex
|
|
5
|
+
align-items center
|
|
6
|
+
gap var(--p-4)
|
|
7
|
+
flex-shrink 0
|
|
8
|
+
|
|
9
|
+
.logoArea
|
|
10
|
+
position absolute
|
|
11
|
+
top 22px
|
|
12
|
+
left 40px
|
|
13
|
+
z-index 10
|
|
14
|
+
|
|
15
|
+
display flex
|
|
16
|
+
align-items center
|
|
17
|
+
gap var(--p-2)
|
|
18
|
+
|
|
19
|
+
@media (max-width MOBILE)
|
|
20
|
+
left 32px
|
|
21
|
+
@media (min-width MOBILE)
|
|
22
|
+
:global([data-slot='sidebar-wrapper'][data-state='expanded']) &
|
|
23
|
+
position fixed
|
|
24
|
+
|
|
25
|
+
.logoAreaWithBanner
|
|
26
|
+
top calc(22px + var(--welcome-banner-height, 0px))
|
|
27
|
+
|
|
28
|
+
@media (min-width MOBILE)
|
|
29
|
+
:global([data-slot='sidebar-wrapper'][data-state='collapsed']) &
|
|
30
|
+
top 22px
|
|
31
|
+
|
|
32
|
+
.logoLink
|
|
33
|
+
display flex
|
|
34
|
+
align-items center
|
|
35
|
+
gap 0.5rem
|
|
36
|
+
width fit-content
|
|
37
|
+
|
|
38
|
+
font-weight 400
|
|
39
|
+
font-size 1.5rem
|
|
40
|
+
color var(--color-foreground)
|
|
41
|
+
text-decoration none
|
|
42
|
+
white-space nowrap
|
|
43
|
+
|
|
44
|
+
svg
|
|
45
|
+
display inline-flex
|
|
46
|
+
height 32px
|
|
47
|
+
width auto
|
|
48
|
+
flex-shrink 0
|
|
49
|
+
transition transform 0.1s ease-in-out
|
|
50
|
+
|
|
51
|
+
&:hover svg
|
|
52
|
+
transform scale(1.05)
|
|
53
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// This file is automatically generated.
|
|
2
|
+
// Please do not change this file!
|
|
3
|
+
interface CssExports {
|
|
4
|
+
'actionsAnchor': string;
|
|
5
|
+
'logoArea': string;
|
|
6
|
+
'logoAreaWithBanner': string;
|
|
7
|
+
'logoLink': string;
|
|
8
|
+
}
|
|
9
|
+
export const cssExports: CssExports;
|
|
10
|
+
export default cssExports;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import { Link } from 'react-router-dom';
|
|
4
|
+
|
|
5
|
+
import { AppHeaderPortal } from '#uilib/components/ui/AppHeader/AppHeader';
|
|
6
|
+
import { PAGE_HEADER_ACTIONS_ID } from '#uilib/components/ui/AppHeader/appChromeAnchors';
|
|
7
|
+
import { Gap } from '#uilib/components/ui/Gap/Gap';
|
|
8
|
+
import { Logo } from '#uilib/components/ui/Logo/Logo';
|
|
9
|
+
import { NavUserHeader } from '#uilib/components/ui/NavUserHeader';
|
|
10
|
+
import type { NavUserHeaderProps } from '#uilib/components/ui/NavUserHeader';
|
|
11
|
+
import {
|
|
12
|
+
WorkspaceAppSwitcher,
|
|
13
|
+
type WorkspaceAppSwitcherProps,
|
|
14
|
+
} from '#uilib/components/ui/WorkspaceAppSwitcher';
|
|
15
|
+
|
|
16
|
+
import S from './SybilionAppHeader.styl';
|
|
17
|
+
|
|
18
|
+
export type SybilionAppHeaderProps = WorkspaceAppSwitcherProps &
|
|
19
|
+
NavUserHeaderProps & {
|
|
20
|
+
pageHeaderId?: string;
|
|
21
|
+
actionsAnchorId?: string;
|
|
22
|
+
actionsAnchorClassName?: string;
|
|
23
|
+
/** Branded markup; omit for default lucide tile + «Sybilion». */
|
|
24
|
+
logo?: ReactNode;
|
|
25
|
+
logoAreaClassName?: string;
|
|
26
|
+
/** Applies vertical offset when a welcome banner consumes top space (CSS `--welcome-banner-height` on shell). */
|
|
27
|
+
welcomeBannerOffset?: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function SybilionAppHeader({
|
|
31
|
+
pageHeaderId,
|
|
32
|
+
actionsAnchorId = PAGE_HEADER_ACTIONS_ID,
|
|
33
|
+
actionsAnchorClassName,
|
|
34
|
+
pathname,
|
|
35
|
+
onNavigate,
|
|
36
|
+
authenticated,
|
|
37
|
+
defaultApps,
|
|
38
|
+
appsStorageKey,
|
|
39
|
+
logo,
|
|
40
|
+
logoAreaClassName,
|
|
41
|
+
welcomeBannerOffset,
|
|
42
|
+
...navUserHeaderProps
|
|
43
|
+
}: SybilionAppHeaderProps) {
|
|
44
|
+
return (
|
|
45
|
+
<AppHeaderPortal pageHeaderId={pageHeaderId}>
|
|
46
|
+
<div
|
|
47
|
+
className={cn(
|
|
48
|
+
S.logoArea,
|
|
49
|
+
welcomeBannerOffset && S.logoAreaWithBanner,
|
|
50
|
+
logoAreaClassName,
|
|
51
|
+
)}
|
|
52
|
+
>
|
|
53
|
+
<Link to="/" className={S.logoLink}>
|
|
54
|
+
{logo ?? <Logo size="md" aria-hidden />}
|
|
55
|
+
</Link>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<WorkspaceAppSwitcher
|
|
59
|
+
pathname={pathname}
|
|
60
|
+
onNavigate={onNavigate}
|
|
61
|
+
authenticated={authenticated}
|
|
62
|
+
defaultApps={defaultApps}
|
|
63
|
+
appsStorageKey={appsStorageKey}
|
|
64
|
+
/>
|
|
65
|
+
<Gap />
|
|
66
|
+
<div
|
|
67
|
+
id={actionsAnchorId}
|
|
68
|
+
className={cn(S.actionsAnchor, actionsAnchorClassName)}
|
|
69
|
+
>
|
|
70
|
+
<NavUserHeader {...navUserHeaderProps} />
|
|
71
|
+
</div>
|
|
72
|
+
</AppHeaderPortal>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@import '
|
|
1
|
+
@import '../../../lib/theme.styl'
|
|
2
2
|
|
|
3
3
|
// Embed preview: Sidebar uses position:fixed + viewport top/height; PageScroll uses 100vh.
|
|
4
4
|
// Transform creates a containing block for `fixed` children; overrides stop bleed into docs chrome.
|
|
@@ -24,23 +24,13 @@
|
|
|
24
24
|
height auto !important
|
|
25
25
|
flex 1
|
|
26
26
|
|
|
27
|
-
.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
height 100% !important
|
|
38
|
-
max-height 100% !important
|
|
39
|
-
|
|
40
|
-
@media (min-width MOBILE)
|
|
41
|
-
height 100% !important
|
|
42
|
-
|
|
43
|
-
.preview [data-slot="sidebar-resize-handle"]
|
|
44
|
-
top 0 !important
|
|
45
|
-
height 100% !important
|
|
46
|
-
max-height 100%
|
|
27
|
+
// Datasets panel: tall block for scroll testing inside PageScroll.
|
|
28
|
+
.scrollTestBlock
|
|
29
|
+
min-height 100vh
|
|
30
|
+
margin-top var(--p-4)
|
|
31
|
+
padding var(--p-4)
|
|
32
|
+
border-radius var(--p-2)
|
|
33
|
+
border 2px dashed var(--border)
|
|
34
|
+
background-color var(--background)
|
|
35
|
+
color var(--muted-foreground)
|
|
36
|
+
font-size var(--text-sm)
|