@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.
Files changed (51) hide show
  1. package/dist/esm/components/ui/AppHeader/AppHeader.styl.js +1 -1
  2. package/dist/esm/components/ui/AppHeader/appChromeAnchors.js +3 -1
  3. package/dist/esm/components/ui/Sidebar/Sidebar.styl.js +1 -1
  4. package/dist/esm/components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.js +62 -0
  5. package/dist/esm/components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.styl.js +7 -0
  6. package/dist/esm/components/ui/WorkspaceAppSwitcher/workspaceApp.types.js +4 -0
  7. package/dist/esm/components/ui/WorkspaceAppSwitcher/workspaceAppIcons.js +16 -0
  8. package/dist/esm/components/ui/WorkspaceAppSwitcher/workspaceAppPaths.js +29 -0
  9. package/dist/esm/components/ui/WorkspaceAppSwitcher/workspaceAppsConstants.js +4 -0
  10. package/dist/esm/components/ui/WorkspaceAppSwitcher/workspaceAppsLocalStorage.js +84 -0
  11. package/dist/esm/components/widgets/SybilionAppHeader/SybilionAppHeader.js +18 -0
  12. package/dist/esm/components/widgets/SybilionAppHeader/SybilionAppHeader.styl.js +7 -0
  13. package/dist/esm/index.js +8 -1
  14. package/dist/esm/types/src/components/ui/AppHeader/appChromeAnchors.d.ts +2 -0
  15. package/dist/esm/types/src/components/ui/AppHeader/index.d.ts +1 -1
  16. package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.d.ts +12 -0
  17. package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/index.d.ts +7 -0
  18. package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/workspaceApp.types.d.ts +19 -0
  19. package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/workspaceAppIcons.d.ts +9 -0
  20. package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/workspaceAppPaths.d.ts +3 -0
  21. package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/workspaceAppsConstants.d.ts +2 -0
  22. package/dist/esm/types/src/components/ui/WorkspaceAppSwitcher/workspaceAppsLocalStorage.d.ts +6 -0
  23. package/dist/esm/types/src/components/widgets/SybilionAppHeader/SybilionAppHeader.d.ts +14 -0
  24. package/dist/esm/types/src/components/widgets/SybilionAppHeader/index.d.ts +1 -0
  25. package/dist/esm/types/src/index.d.ts +2 -0
  26. package/docs/standalone-apps.md +195 -53
  27. package/package.json +1 -1
  28. package/src/components/ui/AppHeader/AppHeader.styl +5 -0
  29. package/src/components/ui/AppHeader/appChromeAnchors.ts +3 -0
  30. package/src/components/ui/AppHeader/index.ts +1 -1
  31. package/src/components/ui/Sidebar/Sidebar.styl +1 -1
  32. package/src/components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.styl +91 -0
  33. package/src/components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.styl.d.ts +15 -0
  34. package/src/components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.tsx +163 -0
  35. package/src/components/ui/WorkspaceAppSwitcher/index.ts +20 -0
  36. package/src/components/ui/WorkspaceAppSwitcher/workspaceApp.types.ts +21 -0
  37. package/src/components/ui/WorkspaceAppSwitcher/workspaceAppIcons.ts +27 -0
  38. package/src/components/ui/WorkspaceAppSwitcher/workspaceAppPaths.ts +34 -0
  39. package/src/components/ui/WorkspaceAppSwitcher/workspaceAppsConstants.ts +2 -0
  40. package/src/components/ui/WorkspaceAppSwitcher/workspaceAppsLocalStorage.ts +95 -0
  41. package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.styl +53 -0
  42. package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.styl.d.ts +10 -0
  43. package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.tsx +74 -0
  44. package/src/components/widgets/SybilionAppHeader/index.ts +4 -0
  45. package/src/docs/pages/{StandaloneAppLayoutPage.styl → StandaloneAppLayoutPage/StandaloneAppLayoutPage.styl} +11 -21
  46. package/src/docs/pages/{StandaloneAppLayoutPage.styl.d.ts → StandaloneAppLayoutPage/StandaloneAppLayoutPage.styl.d.ts} +1 -0
  47. package/src/docs/pages/StandaloneAppLayoutPage/StandaloneAppLayoutPage.tsx +659 -0
  48. package/src/docs/registry.ts +2 -1
  49. package/src/index.ts +2 -0
  50. package/src/docs/pages/StandaloneAppLayoutPage.tsx +0 -242
  51. /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,2 @@
1
+ /** localStorage key for workspace app list JSON (shared by host app + demos). */
2
+ export const WORKSPACE_APPS_LS_KEY = 'sybilion.workspaceApps.v1';
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ export {
2
+ SybilionAppHeader,
3
+ type SybilionAppHeaderProps,
4
+ } from './SybilionAppHeader';
@@ -1,4 +1,4 @@
1
- @import '../../lib/theme.styl'
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
- .preview aside[data-side="left"]
28
- top 0 !important
29
- left 0 !important
30
- height 100% !important
31
- min-height 0 !important
32
-
33
- @media (min-width MOBILE)
34
- height 100% !important
35
-
36
- .preview aside[data-side="left"] > div:first-child
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)
@@ -3,6 +3,7 @@
3
3
  interface CssExports {
4
4
  'preview': string;
5
5
  'previewScrollRoot': string;
6
+ 'scrollTestBlock': string;
6
7
  }
7
8
  export const cssExports: CssExports;
8
9
  export default cssExports;