@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.
- package/dist/esm/components/ui/AppHeader/AppHeader.js +3 -3
- package/dist/esm/components/ui/Image/Image.styl.js +1 -1
- package/dist/esm/components/ui/NavUserHeader/NavUserHeader.js +28 -0
- package/dist/esm/components/ui/NavUserHeader/NavUserHeader.styl.js +7 -0
- package/dist/esm/components/ui/Page/AppShell/AppShell.styl.js +1 -1
- package/dist/esm/components/ui/Page/PageScroll/PageScroll.js +4 -4
- package/dist/esm/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.js +9 -9
- package/dist/esm/index.js +2 -1
- package/dist/esm/sybilion-auth/SybilionAuthProvider.js +30 -7
- package/dist/esm/sybilion-auth/exchangeSybilionToken.js +6 -2
- package/dist/esm/types/src/components/ui/AppHeader/AppHeader.d.ts +2 -1
- package/dist/esm/types/src/components/ui/NavUserHeader/NavUserHeader.d.ts +2 -0
- package/dist/esm/types/src/components/ui/NavUserHeader/NavUserHeader.types.d.ts +25 -0
- package/dist/esm/types/src/components/ui/NavUserHeader/index.d.ts +2 -0
- package/dist/esm/types/src/components/ui/Page/PageScroll/PageScroll.d.ts +2 -1
- package/dist/esm/types/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.d.ts +3 -1
- package/dist/esm/types/src/docs/pages/NavUserHeaderPage.d.ts +1 -0
- package/dist/esm/types/src/docs/pages/StandaloneAppLayoutPage.d.ts +1 -0
- package/dist/esm/types/src/index.d.ts +1 -0
- package/dist/esm/types/src/sybilion-auth/SybilionAuthProvider.d.ts +5 -2
- package/dist/esm/types/src/sybilion-auth/exchangeSybilionToken.d.ts +3 -1
- package/dist/esm/types/src/sybilion-auth/index.d.ts +1 -1
- package/docs/standalone-apps.md +266 -27
- package/package.json +6 -1
- package/src/components/ui/AppHeader/AppHeader.tsx +7 -3
- package/src/components/ui/Image/Image.styl +1 -0
- package/src/components/ui/NavUserHeader/NavUserHeader.styl +125 -0
- package/src/components/ui/NavUserHeader/NavUserHeader.styl.d.ts +28 -0
- package/src/components/ui/NavUserHeader/NavUserHeader.tsx +148 -0
- package/src/components/ui/NavUserHeader/NavUserHeader.types.ts +27 -0
- package/src/components/ui/NavUserHeader/avatar.svg +4 -0
- package/src/components/ui/NavUserHeader/index.ts +5 -0
- package/src/components/ui/Page/AppShell/AppShell.styl +1 -0
- package/src/components/ui/Page/PageScroll/PageScroll.tsx +9 -2
- package/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.tsx +9 -0
- package/src/docs/pages/NavUserHeaderPage.tsx +89 -0
- package/src/docs/pages/StandaloneAppLayoutPage.styl +46 -0
- package/src/docs/pages/StandaloneAppLayoutPage.styl.d.ts +8 -0
- package/src/docs/pages/StandaloneAppLayoutPage.tsx +242 -0
- package/src/docs/pages/SybilionAuthProviderPage.tsx +5 -2
- package/src/docs/registry.ts +12 -0
- package/src/index.ts +1 -0
- package/src/sybilion-auth/SybilionAuthProvider.tsx +33 -11
- package/src/sybilion-auth/exchangeSybilionToken.ts +5 -1
- 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>
|
|
@@ -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}
|
|
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,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 →
|
|
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>
|