@sybilion/uilib 1.2.0 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/esm/components/ui/AppHeader/AppHeader.js +3 -3
  2. package/dist/esm/components/ui/Image/Image.styl.js +1 -1
  3. package/dist/esm/components/ui/NavUserHeader/NavUserHeader.js +28 -0
  4. package/dist/esm/components/ui/NavUserHeader/NavUserHeader.styl.js +7 -0
  5. package/dist/esm/components/ui/Page/AppShell/AppShell.styl.js +1 -1
  6. package/dist/esm/components/ui/Page/PageScroll/PageScroll.js +4 -4
  7. package/dist/esm/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.js +9 -9
  8. package/dist/esm/index.js +2 -1
  9. package/dist/esm/sybilion-auth/SybilionAuthProvider.js +30 -7
  10. package/dist/esm/sybilion-auth/exchangeSybilionToken.js +6 -2
  11. package/dist/esm/types/src/components/ui/AppHeader/AppHeader.d.ts +2 -1
  12. package/dist/esm/types/src/components/ui/NavUserHeader/NavUserHeader.d.ts +2 -0
  13. package/dist/esm/types/src/components/ui/NavUserHeader/NavUserHeader.types.d.ts +25 -0
  14. package/dist/esm/types/src/components/ui/NavUserHeader/index.d.ts +2 -0
  15. package/dist/esm/types/src/components/ui/Page/PageScroll/PageScroll.d.ts +2 -1
  16. package/dist/esm/types/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.d.ts +3 -1
  17. package/dist/esm/types/src/docs/pages/NavUserHeaderPage.d.ts +1 -0
  18. package/dist/esm/types/src/docs/pages/StandaloneAppLayoutPage.d.ts +1 -0
  19. package/dist/esm/types/src/index.d.ts +1 -0
  20. package/dist/esm/types/src/sybilion-auth/SybilionAuthProvider.d.ts +5 -2
  21. package/dist/esm/types/src/sybilion-auth/exchangeSybilionToken.d.ts +3 -1
  22. package/dist/esm/types/src/sybilion-auth/index.d.ts +1 -1
  23. package/docs/standalone-apps.md +266 -27
  24. package/package.json +6 -1
  25. package/src/components/ui/AppHeader/AppHeader.tsx +7 -3
  26. package/src/components/ui/Image/Image.styl +1 -0
  27. package/src/components/ui/NavUserHeader/NavUserHeader.styl +125 -0
  28. package/src/components/ui/NavUserHeader/NavUserHeader.styl.d.ts +28 -0
  29. package/src/components/ui/NavUserHeader/NavUserHeader.tsx +148 -0
  30. package/src/components/ui/NavUserHeader/NavUserHeader.types.ts +27 -0
  31. package/src/components/ui/NavUserHeader/avatar.svg +4 -0
  32. package/src/components/ui/NavUserHeader/index.ts +5 -0
  33. package/src/components/ui/Page/AppShell/AppShell.styl +1 -0
  34. package/src/components/ui/Page/PageScroll/PageScroll.tsx +9 -2
  35. package/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.tsx +9 -0
  36. package/src/docs/pages/NavUserHeaderPage.tsx +89 -0
  37. package/src/docs/pages/StandaloneAppLayoutPage.styl +46 -0
  38. package/src/docs/pages/StandaloneAppLayoutPage.styl.d.ts +8 -0
  39. package/src/docs/pages/StandaloneAppLayoutPage.tsx +242 -0
  40. package/src/docs/pages/SybilionAuthProviderPage.tsx +5 -2
  41. package/src/docs/registry.ts +12 -0
  42. package/src/index.ts +1 -0
  43. package/src/sybilion-auth/SybilionAuthProvider.tsx +33 -11
  44. package/src/sybilion-auth/exchangeSybilionToken.ts +5 -1
  45. package/src/sybilion-auth/index.ts +1 -0
@@ -0,0 +1,148 @@
1
+ import cn from 'classnames';
2
+
3
+ import {
4
+ MoonIcon,
5
+ SignOutIcon,
6
+ SunIcon,
7
+ UserCircleIcon,
8
+ } from '@phosphor-icons/react';
9
+ import { ChevronDownIcon } from 'lucide-react';
10
+
11
+ import { Avatar } from '../Avatar';
12
+ import { Button } from '../Button';
13
+ import {
14
+ DropdownMenu,
15
+ DropdownMenuContent,
16
+ DropdownMenuGroup,
17
+ DropdownMenuItem,
18
+ DropdownMenuLabel,
19
+ DropdownMenuSeparator,
20
+ DropdownMenuTrigger,
21
+ } from '../DropdownMenu';
22
+ import { Image } from '../Image';
23
+ import S from './NavUserHeader.styl';
24
+ import type { NavUserHeaderProps } from './NavUserHeader.types';
25
+
26
+ export function NavUserHeader({
27
+ variant = 'default',
28
+ isLoading = false,
29
+ isAuthenticated,
30
+ user = null,
31
+ menuItems,
32
+ theme,
33
+ onThemeToggle,
34
+ onLogout,
35
+ signInSlot,
36
+ onSignInClick,
37
+ }: NavUserHeaderProps) {
38
+ const authenticated = isAuthenticated ?? true;
39
+
40
+ const avatarUrl = user?.avatar ?? '';
41
+ const userName = user?.name ?? '';
42
+ const userEmail = user?.email ?? '';
43
+
44
+ if (isLoading) {
45
+ return (
46
+ <Button variant="ghost" size="sm" disabled className={S.loadingButton}>
47
+ <div className={S.avatarSkeleton} />
48
+ <div className={S.textSkeleton} />
49
+ </Button>
50
+ );
51
+ }
52
+
53
+ if (!authenticated) {
54
+ if (signInSlot) {
55
+ return signInSlot;
56
+ }
57
+ return (
58
+ <Button
59
+ variant="ghost"
60
+ size="sm"
61
+ className={S.loginButton}
62
+ type="button"
63
+ onClick={onSignInClick}
64
+ >
65
+ <UserCircleIcon className={S.iconLg} />
66
+ <span>Log in</span>
67
+ </Button>
68
+ );
69
+ }
70
+
71
+ return (
72
+ <DropdownMenu>
73
+ <DropdownMenuTrigger asChild>
74
+ <Button
75
+ variant="ghost"
76
+ size="sm"
77
+ className={cn(S.userButton, variant === 'compact' && S.compact)}
78
+ >
79
+ <Avatar className={S.avatar}>
80
+ <Image
81
+ url={avatarUrl}
82
+ alt={userName}
83
+ fallback={<div className={S.avatarFallback} />}
84
+ />
85
+ </Avatar>
86
+ {variant === 'default' && (
87
+ <>
88
+ <div className={S.userInfo}>
89
+ <span className={`${S.userName} ph-no-capture`}>
90
+ {userName}
91
+ </span>
92
+ <span className={S.userEmail}>{userEmail}</span>
93
+ </div>
94
+ <ChevronDownIcon className={S.iconSm} />
95
+ </>
96
+ )}
97
+ </Button>
98
+ </DropdownMenuTrigger>
99
+ <DropdownMenuContent
100
+ className={S.dropdownContent}
101
+ align="end"
102
+ elevation="md"
103
+ >
104
+ <DropdownMenuLabel className={S.userLabel}>
105
+ <div className={S.userLabelContent}>
106
+ <Avatar className={S.avatar}>
107
+ <Image
108
+ url={avatarUrl}
109
+ alt={userName}
110
+ fallback={<div className={S.avatarFallback} />}
111
+ />
112
+ </Avatar>
113
+ <div className={S.userDetails}>
114
+ <span className={`${S.userDetailName} ph-no-capture`}>
115
+ {userName}
116
+ </span>
117
+ <span className={S.userDetailEmail}>{userEmail}</span>
118
+ </div>
119
+ </div>
120
+ </DropdownMenuLabel>
121
+ <DropdownMenuSeparator />
122
+ <DropdownMenuGroup>
123
+ {menuItems}
124
+ {onThemeToggle ? (
125
+ <DropdownMenuItem onSelect={() => onThemeToggle()}>
126
+ {theme === 'dark' ? (
127
+ <>
128
+ <SunIcon />
129
+ Light theme
130
+ </>
131
+ ) : (
132
+ <>
133
+ <MoonIcon />
134
+ Dark theme
135
+ </>
136
+ )}
137
+ </DropdownMenuItem>
138
+ ) : null}
139
+ </DropdownMenuGroup>
140
+ <DropdownMenuSeparator />
141
+ <DropdownMenuItem variant="destructive" onSelect={() => onLogout()}>
142
+ <SignOutIcon />
143
+ Log out
144
+ </DropdownMenuItem>
145
+ </DropdownMenuContent>
146
+ </DropdownMenu>
147
+ );
148
+ }
@@ -0,0 +1,27 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export type NavUserHeaderUser = {
4
+ name: string;
5
+ email: string;
6
+ /** Passed to avatar `Image` as `src`. */
7
+ avatar?: string;
8
+ };
9
+
10
+ export type NavUserHeaderProps = {
11
+ variant?: 'default' | 'compact';
12
+ isLoading?: boolean;
13
+ /** When false, signed-out branch is shown. Defaults to true when omitted. */
14
+ isAuthenticated?: boolean;
15
+ /** Present when authenticated: shown in trigger and dropdown label. */
16
+ user?: NavUserHeaderUser | null;
17
+ /** Rows inside the menu above theme toggle and logout. Use `DropdownMenuItem` nodes. */
18
+ menuItems?: ReactNode;
19
+ /** Current theme drives the toggle row label/icons. */
20
+ theme: 'light' | 'dark';
21
+ /** When set, renders the light/dark theme menu row. */
22
+ onThemeToggle?: () => void;
23
+ onLogout: () => void;
24
+ /** Replaces default “Log in” control when signed out. */
25
+ signInSlot?: ReactNode;
26
+ onSignInClick?: () => void;
27
+ };
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none">
2
+ <path fill="#ECFEFF" d="M0 16C0 7.163 7.163 0 16 0s16 7.163 16 16-7.163 16-16 16S0 24.837 0 16" />
3
+ <path fill="#22D3EE" fill-rule="evenodd" d="M17.4 9.454h-2.8V8h2.8zm1.4 1.455V9.454h-1.4v1.455zm0 2.91v-2.91h1.4v2.91zm-1.4 1.454v-1.455h1.4v1.455zm-2.8 0h2.8v1.454h-2.8zm-1.4-1.455v1.455h1.4v-1.455zm0-2.91v2.91h-1.4v-2.91zm0 0h1.4V9.455h-1.4zm6.3 11.638h-7V21.09h-2.1v-1.455h1.4v-1.454h2.1v-1.454h-2.1v1.454h-1.4v1.454H9v1.455h1.4v1.455h2.1V24h7zm2.1-1.455v1.455h-2.1V21.09zm0-1.455H23v1.455h-1.4zm-1.4-1.454v1.454h1.4v-1.454zm0 0h-2.1v-1.454h2.1z" clip-rule="evenodd" />
4
+ </svg>
@@ -0,0 +1,5 @@
1
+ export { NavUserHeader } from './NavUserHeader';
2
+ export type {
3
+ NavUserHeaderProps,
4
+ NavUserHeaderUser,
5
+ } from './NavUserHeader.types';
@@ -5,6 +5,7 @@
5
5
  flex 1
6
6
  min-height 0
7
7
  width 100%
8
+ min-height 100%
8
9
  grid-template-columns 1fr
9
10
  grid-template-areas "main"
10
11
 
@@ -9,9 +9,12 @@ import S from './PageScroll.styl';
9
9
 
10
10
  function PageScrollInner({
11
11
  className,
12
+ rootClassName,
12
13
  children,
13
14
  }: {
14
15
  className?: string;
16
+ /** Merged onto the outer Scroll root (e.g. embed previews that must not use `100vh`). */
17
+ rootClassName?: string;
15
18
  children: React.ReactNode;
16
19
  }) {
17
20
  // const { setIsScrolled } = useContext(PageContext);
@@ -38,7 +41,7 @@ function PageScrollInner({
38
41
  y
39
42
  // fadeSize="l"
40
43
  offset={{ y: { before: 120, after: 130 } }}
41
- className={S.root}
44
+ className={cn(S.root, rootClassName)}
42
45
  autoHide
43
46
  innerClassName={cn(S.inner, className)}
44
47
  yScrollbarClassName={S.scrollbar}
@@ -55,15 +58,19 @@ function PageScrollInner({
55
58
  export const PageScroll = ({
56
59
  children,
57
60
  className,
61
+ rootClassName,
58
62
  }: {
59
63
  children: React.ReactNode;
60
64
  className?: string;
65
+ rootClassName?: string;
61
66
  }) => {
62
67
  const [isScrolled, setIsScrolled] = useState(false);
63
68
 
64
69
  return (
65
70
  <PageContext.Provider value={{ isScrolled, setIsScrolled }}>
66
- <PageScrollInner className={className}>{children}</PageScrollInner>
71
+ <PageScrollInner className={className} rootClassName={rootClassName}>
72
+ {children}
73
+ </PageScrollInner>
67
74
  </PageContext.Provider>
68
75
  );
69
76
  };
@@ -22,6 +22,8 @@ import {
22
22
  export type SidebarDatasetsItemsGroupedProps = {
23
23
  groupBy: SidebarDatasetsItemsGroupBy;
24
24
  datasets: SidebarDatasetsItemsGroupedDataset[];
25
+ preItems?: React.ReactNode;
26
+ postItems?: React.ReactNode;
25
27
  selectedDatasetId?: number;
26
28
  onDatasetClick?: (datasetId: number) => void;
27
29
  /** When omitted, all groups start expanded. */
@@ -32,6 +34,8 @@ export type SidebarDatasetsItemsGroupedProps = {
32
34
  export function SidebarDatasetsItemsGrouped({
33
35
  groupBy,
34
36
  datasets,
37
+ preItems,
38
+ postItems,
35
39
  selectedDatasetId,
36
40
  onDatasetClick,
37
41
  defaultExpandedGroupNames,
@@ -74,6 +78,8 @@ export function SidebarDatasetsItemsGrouped({
74
78
  return (
75
79
  <SidebarGroup className={className}>
76
80
  <SidebarMenu>
81
+ {preItems}
82
+
77
83
  {grouped.map(([groupName, groupDatasets]) => {
78
84
  const isExpanded = expanded.has(groupName);
79
85
  const parentActive = groupDatasets.some(
@@ -97,6 +103,7 @@ export function SidebarDatasetsItemsGrouped({
97
103
  )}
98
104
  </div>
99
105
  </SidebarMenuButton>
106
+
100
107
  {isExpanded && (
101
108
  <SidebarMenuSub className={S.subMenuContainer}>
102
109
  <div className={S.subMenuBorder} />
@@ -122,6 +129,8 @@ export function SidebarDatasetsItemsGrouped({
122
129
  </SidebarMenuItem>
123
130
  );
124
131
  })}
132
+
133
+ {postItems}
125
134
  </SidebarMenu>
126
135
  </SidebarGroup>
127
136
  );
@@ -0,0 +1,89 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+
3
+ import { DropdownMenuItem } from '#uilib/components/ui/DropdownMenu';
4
+ import { NavUserHeader } from '#uilib/components/ui/NavUserHeader';
5
+ import { PageContentSection } from '#uilib/components/ui/Page';
6
+ import { GearSixIcon, UserCircleIcon } from '@phosphor-icons/react';
7
+
8
+ import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
9
+ import { DocsHeaderActions } from '../docsHeaderActions';
10
+
11
+ export default function NavUserHeaderPage() {
12
+ const [theme, setTheme] = useState<'light' | 'dark'>('light');
13
+
14
+ useEffect(() => {
15
+ document.documentElement.dataset.theme = theme;
16
+ return () => {
17
+ delete document.documentElement.dataset.theme;
18
+ };
19
+ }, [theme]);
20
+
21
+ const onThemeToggle = useCallback(() => {
22
+ setTheme(t => (t === 'dark' ? 'light' : 'dark'));
23
+ }, []);
24
+
25
+ const customMenuItems = (
26
+ <>
27
+ <DropdownMenuItem>
28
+ <UserCircleIcon />
29
+ Account (custom)
30
+ </DropdownMenuItem>
31
+ <DropdownMenuItem>
32
+ <GearSixIcon />
33
+ Settings (custom)
34
+ </DropdownMenuItem>
35
+ </>
36
+ );
37
+
38
+ return (
39
+ <>
40
+ <AppPageHeader
41
+ breadcrumbs={[{ label: 'NavUserHeader' }]}
42
+ title="NavUserHeader"
43
+ subheader="User menu with label, custom rows, theme toggle, and logout."
44
+ actions={<DocsHeaderActions />}
45
+ />
46
+ <PageContentSection
47
+ style={{ display: 'flex', flexWrap: 'wrap', gap: '2rem' }}
48
+ >
49
+ <div>
50
+ <p style={{ marginBottom: 8, fontSize: 12 }}>
51
+ Signed in · default variant
52
+ </p>
53
+ <NavUserHeader
54
+ user={{
55
+ name: 'Demo Analyst',
56
+ email: 'demo@sybilion.io',
57
+ avatar: '',
58
+ }}
59
+ theme={theme}
60
+ onThemeToggle={onThemeToggle}
61
+ onLogout={() => {
62
+ console.info('[docs] logout');
63
+ }}
64
+ menuItems={customMenuItems}
65
+ />
66
+ </div>
67
+ <div>
68
+ <p style={{ marginBottom: 8, fontSize: 12 }}>
69
+ Signed in · compact variant
70
+ </p>
71
+ <NavUserHeader
72
+ variant="compact"
73
+ user={{
74
+ name: 'Compact',
75
+ email: 'compact@sybilion.io',
76
+ avatar: '',
77
+ }}
78
+ theme={theme}
79
+ onThemeToggle={onThemeToggle}
80
+ onLogout={() => {
81
+ console.info('[docs] logout compact');
82
+ }}
83
+ menuItems={customMenuItems}
84
+ />
85
+ </div>
86
+ </PageContentSection>
87
+ </>
88
+ );
89
+ }
@@ -0,0 +1,46 @@
1
+ @import '../../lib/theme.styl'
2
+
3
+ // Embed preview: Sidebar uses position:fixed + viewport top/height; PageScroll uses 100vh.
4
+ // Transform creates a containing block for `fixed` children; overrides stop bleed into docs chrome.
5
+ .preview
6
+ position relative
7
+ overflow hidden
8
+ transform translateZ(0)
9
+ border-radius var(--p-4)
10
+ border 1px solid var(--border)
11
+ background-color var(--background)
12
+ height min(720px, 80vh)
13
+ min-height 0
14
+ display flex
15
+ flex-direction column
16
+
17
+ .previewScrollRoot
18
+ flex 1
19
+ min-height 0
20
+ height 100% !important
21
+ max-height 100% !important
22
+
23
+ @media (max-width MOBILE)
24
+ height auto !important
25
+ flex 1
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%
@@ -0,0 +1,8 @@
1
+ // This file is automatically generated.
2
+ // Please do not change this file!
3
+ interface CssExports {
4
+ 'preview': string;
5
+ 'previewScrollRoot': string;
6
+ }
7
+ export const cssExports: CssExports;
8
+ export default cssExports;
@@ -0,0 +1,242 @@
1
+ import { useCallback, useState } from 'react';
2
+
3
+ import { AppHeaderHost, AppHeaderPortal } from '#uilib/components/ui/AppHeader';
4
+ import { Gap } from '#uilib/components/ui/Gap/Gap';
5
+ import { NavUserHeader } from '#uilib/components/ui/NavUserHeader/NavUserHeader';
6
+ import {
7
+ AppShell,
8
+ AppShellMainContent,
9
+ PageContentSection,
10
+ } from '#uilib/components/ui/Page';
11
+ import { PageFooter } from '#uilib/components/ui/Page/PageFooter/PageFooter';
12
+ import { PageScroll } from '#uilib/components/ui/Page/PageScroll/PageScroll';
13
+ import {
14
+ Sidebar,
15
+ SidebarContent,
16
+ SidebarGroup,
17
+ SidebarMenu,
18
+ SidebarMenuButton,
19
+ SidebarMenuItem,
20
+ SidebarProvider,
21
+ SidebarTrigger,
22
+ } from '#uilib/components/ui/Sidebar/Sidebar';
23
+ import {
24
+ SidebarDatasetsItemsGrouped,
25
+ type SidebarDatasetsItemsGroupedDataset,
26
+ } from '#uilib/components/widgets/SidebarDatasetsItemsGrouped';
27
+ import { House } from 'lucide-react';
28
+
29
+ import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
30
+ import { DocsHeaderActions } from '../docsHeaderActions';
31
+ import S from './StandaloneAppLayoutPage.styl';
32
+
33
+ const MOCK_DATASETS: SidebarDatasetsItemsGroupedDataset[] = [
34
+ {
35
+ id: 1,
36
+ name: 'Acetic Acid Price - China - Dollar/MT',
37
+ status: 'active',
38
+ created_at: '2024-01-01',
39
+ updated_at: '2024-01-02',
40
+ keywords: '',
41
+ category: { id: 10, name: 'Chemicals' },
42
+ target_type_id: 1,
43
+ target_type: { id: 1, name: 'Commodity price' },
44
+ trend: 0,
45
+ regular_price: '0',
46
+ sale_price: '0',
47
+ regions: [
48
+ { id: 1, name: 'Asia' },
49
+ { id: 2, name: 'China' },
50
+ ],
51
+ unit: { id: 1, name: 'USD/MT' },
52
+ },
53
+ {
54
+ id: 2,
55
+ name: 'Freight index — spot volume (China)',
56
+ status: 'active',
57
+ created_at: '2024-01-01',
58
+ updated_at: '2024-01-02',
59
+ keywords: '',
60
+ category: { id: 10, name: 'Chemicals' },
61
+ target_type_id: 2,
62
+ target_type: { id: 2, name: 'Freight index' },
63
+ trend: 0,
64
+ regular_price: '0',
65
+ sale_price: '0',
66
+ regions: [
67
+ { id: 1, name: 'Asia' },
68
+ { id: 2, name: 'China' },
69
+ ],
70
+ unit: { id: 1, name: 'Index' },
71
+ },
72
+ {
73
+ id: 3,
74
+ name: 'Ethanol Price Europe Per Kg In USD',
75
+ status: 'active',
76
+ created_at: '2024-01-01',
77
+ updated_at: '2024-01-02',
78
+ keywords: '',
79
+ category: { id: 11, name: 'Energy' },
80
+ target_type_id: 1,
81
+ target_type: { id: 1, name: 'Commodity price' },
82
+ trend: 0,
83
+ regular_price: '0',
84
+ sale_price: '0',
85
+ regions: [{ id: 5, name: 'Europe' }],
86
+ unit: { id: 1, name: 'USD/kg' },
87
+ },
88
+ ];
89
+
90
+ type PreviewPanel = 'home' | 'datasets';
91
+
92
+ const TEST_HEADER_ID = 'test-header-id';
93
+
94
+ function DemoAppSidebar({
95
+ panel,
96
+ onSelectPanel,
97
+ selectedDatasetId,
98
+ onSelectDatasetId,
99
+ }: {
100
+ panel: PreviewPanel;
101
+ onSelectPanel: (p: PreviewPanel) => void;
102
+ selectedDatasetId: number | undefined;
103
+ onSelectDatasetId: (id: number | undefined) => void;
104
+ }) {
105
+ return (
106
+ <Sidebar
107
+ variant="inset"
108
+ collapsible="offcanvas"
109
+ style={{ maxHeight: '100%', height: '100%' }}
110
+ >
111
+ <SidebarContent>
112
+ <SidebarDatasetsItemsGrouped
113
+ preItems={
114
+ <SidebarMenuItem>
115
+ <SidebarMenuButton
116
+ type="button"
117
+ isActive={panel === 'home'}
118
+ onClick={() => onSelectPanel('home')}
119
+ >
120
+ <House size={16} strokeWidth={1.75} aria-hidden />
121
+ Home
122
+ </SidebarMenuButton>
123
+ </SidebarMenuItem>
124
+ }
125
+ groupBy="regions"
126
+ datasets={MOCK_DATASETS}
127
+ selectedDatasetId={selectedDatasetId}
128
+ onDatasetClick={id => {
129
+ onSelectDatasetId(id);
130
+ onSelectPanel('datasets');
131
+ }}
132
+ />
133
+ </SidebarContent>
134
+ </Sidebar>
135
+ );
136
+ }
137
+
138
+ function DemoMainBody({ panel }: { panel: PreviewPanel }) {
139
+ if (panel === 'home') {
140
+ return (
141
+ <div style={{ padding: 'var(--p-6)' }}>
142
+ <h2 style={{ margin: '0 0 0.5rem', fontSize: '1.125rem' }}>Home</h2>
143
+ <p style={{ margin: 0, color: 'var(--muted-foreground)' }}>
144
+ Preview: greenfield shell layout only (no SDK or auth). Real SPA uses
145
+ one top-level Router; this embed cannot nest MemoryRouter under docs
146
+ BrowserRouter — sidebar uses local state instead of{' '}
147
+ <code>NavLink</code> / <code>Routes</code>.
148
+ </p>
149
+ </div>
150
+ );
151
+ }
152
+
153
+ return (
154
+ <div style={{ padding: 'var(--p-6)' }}>
155
+ <h2 style={{ margin: '0 0 0.5rem', fontSize: '1.125rem' }}>Datasets</h2>
156
+ <p style={{ margin: 0, color: 'var(--muted-foreground)' }}>
157
+ Use sidebar links and dataset rows; panel state stays inside this box.
158
+ </p>
159
+ </div>
160
+ );
161
+ }
162
+
163
+ function StandaloneLayoutPreview() {
164
+ const [theme, setTheme] = useState<'light' | 'dark'>('light');
165
+ const [panel, setPanel] = useState<PreviewPanel>('home');
166
+ const [selectedDatasetId, setSelectedDatasetId] = useState<
167
+ number | undefined
168
+ >();
169
+
170
+ const onThemeToggle = useCallback(() => {
171
+ setTheme(t => (t === 'dark' ? 'light' : 'dark'));
172
+ }, []);
173
+
174
+ return (
175
+ <div className={S.preview}>
176
+ <PageScroll rootClassName={S.previewScrollRoot}>
177
+ <AppShell>
178
+ <DemoAppSidebar
179
+ panel={panel}
180
+ onSelectPanel={setPanel}
181
+ selectedDatasetId={selectedDatasetId}
182
+ onSelectDatasetId={setSelectedDatasetId}
183
+ />
184
+
185
+ <AppShellMainContent
186
+ header={<AppHeaderHost anchorId={TEST_HEADER_ID} />}
187
+ footer={
188
+ <PageFooter
189
+ versionLink="#standalone-layout-preview"
190
+ versionLabel="0.0.0-preview"
191
+ />
192
+ }
193
+ >
194
+ <AppHeaderPortal pageHeaderId={TEST_HEADER_ID}>
195
+ <SidebarTrigger />
196
+ <Gap />
197
+ <NavUserHeader
198
+ user={{
199
+ name: 'Preview User',
200
+ email: 'preview@example.com',
201
+ avatar: '',
202
+ }}
203
+ theme={theme}
204
+ onThemeToggle={onThemeToggle}
205
+ onLogout={() => undefined}
206
+ />
207
+ </AppHeaderPortal>
208
+ <DemoMainBody panel={panel} />
209
+ </AppShellMainContent>
210
+ </AppShell>
211
+ </PageScroll>
212
+ </div>
213
+ );
214
+ }
215
+
216
+ export default function StandaloneAppLayoutPage() {
217
+ return (
218
+ <>
219
+ <AppPageHeader
220
+ breadcrumbs={[{ label: 'Standalone app layout' }]}
221
+ title="Standalone app layout"
222
+ subheader={
223
+ <>
224
+ Live preview of AppShell + Sidebar + main column from
225
+ docs/standalone-apps.md §4. <br />
226
+ Full greenfield setup (deps, global CSS, SybilionAuthProvider, SDK)
227
+ lives in that doc — this page is layout only.
228
+ </>
229
+ }
230
+ actions={<DocsHeaderActions />}
231
+ />
232
+ <PageContentSection title="Embedded mini-app (layout preview)">
233
+ <SidebarProvider
234
+ sidebarWidthStorageKey="uilib.docs.standaloneLayout.sidebarWidthPx"
235
+ persistSidebarWidthWithoutConsent
236
+ >
237
+ <StandaloneLayoutPreview />
238
+ </SidebarProvider>
239
+ </PageContentSection>
240
+ </>
241
+ );
242
+ }
@@ -9,7 +9,7 @@ export default function SybilionAuthProviderPage() {
9
9
  <AppPageHeader
10
10
  breadcrumbs={[{ label: 'SybilionAuthProvider' }]}
11
11
  title="SybilionAuthProvider"
12
- subheader="Auth0 SPA → POST /v1/auth/login → Sybilion JWT. Greenfield installs: docs/standalone-apps.md (yarn add line includes @auth0/auth0-react)."
12
+ subheader="Auth0 SPA → sdk.auth.loginWithAuth0Identity → Sybilion JWT. Pass createSybilionSDK instance via sdk prop; greenfield: docs/standalone-apps.md (yarn add includes @sybilion/sdk, @auth0/auth0-react)."
13
13
  actions={<DocsHeaderActions />}
14
14
  />
15
15
  <PageContentSection title="Exports">
@@ -18,7 +18,10 @@ export default function SybilionAuthProviderPage() {
18
18
  </p>
19
19
  <ul>
20
20
  <li>
21
- <code>SybilionAuthProvider</code>
21
+ <code>SybilionAuthProvider</code> (<code>sdk</code> prop)
22
+ </li>
23
+ <li>
24
+ <code>getSybilionApiOriginFromSdk</code>
22
25
  </li>
23
26
  <li>
24
27
  <code>useSybilionAuth</code>