@sybilion/uilib 1.1.0 → 1.2.1

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 (77) hide show
  1. package/README.md +8 -8
  2. package/assets/{mini-app-global.css → standalone-global.css} +1 -1
  3. package/dist/esm/components/ui/Image/Image.styl.js +1 -1
  4. package/dist/esm/components/ui/NavUserHeader/NavUserHeader.js +28 -0
  5. package/dist/esm/components/ui/NavUserHeader/NavUserHeader.styl.js +7 -0
  6. package/dist/esm/components/ui/Sidebar/Sidebar.styl.js +2 -2
  7. package/dist/esm/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.js +48 -0
  8. package/dist/esm/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.styl.js +7 -0
  9. package/dist/esm/components/widgets/SidebarDatasetsItemsGrouped/groupSidebarDatasets.js +38 -0
  10. package/dist/esm/index.js +6 -5
  11. package/dist/esm/sybilion-auth/SybilionAuthProvider.js +208 -0
  12. package/dist/esm/sybilion-auth/authPaths.js +7 -0
  13. package/dist/esm/sybilion-auth/exchangeSybilionToken.js +44 -0
  14. package/dist/esm/types/src/components/ui/Input/Input.d.ts +1 -1
  15. package/dist/esm/types/src/components/ui/NavUserHeader/NavUserHeader.d.ts +2 -0
  16. package/dist/esm/types/src/components/ui/NavUserHeader/NavUserHeader.types.d.ts +25 -0
  17. package/dist/esm/types/src/components/ui/NavUserHeader/index.d.ts +2 -0
  18. package/dist/esm/types/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.d.ts +11 -0
  19. package/dist/esm/types/src/components/widgets/SidebarDatasetsItemsGrouped/groupSidebarDatasets.d.ts +8 -0
  20. package/dist/esm/types/src/components/widgets/SidebarDatasetsItemsGrouped/index.d.ts +3 -0
  21. package/dist/esm/types/src/docs/pages/NavUserHeaderPage.d.ts +1 -0
  22. package/dist/esm/types/src/docs/pages/SidebarDatasetsItemsGroupedPage.d.ts +1 -0
  23. package/dist/esm/types/src/docs/pages/SybilionAuthProviderPage.d.ts +1 -0
  24. package/dist/esm/types/src/index.d.ts +4 -1
  25. package/dist/esm/types/src/sybilion-auth/SybilionAuthProvider.d.ts +42 -0
  26. package/dist/esm/types/src/sybilion-auth/authPaths.d.ts +3 -0
  27. package/dist/esm/types/src/sybilion-auth/exchangeSybilionToken.d.ts +4 -0
  28. package/dist/esm/types/src/sybilion-auth/index.d.ts +4 -0
  29. package/dist/esm/types/src/{mini-app/miniAppDataTypes.d.ts → types/sybilionDatasetSnapshots.d.ts} +5 -8
  30. package/docs/standalone-apps.md +181 -0
  31. package/package.json +15 -3
  32. package/src/components/ui/Image/Image.styl +1 -0
  33. package/src/components/ui/NavUserHeader/NavUserHeader.styl +125 -0
  34. package/src/components/ui/NavUserHeader/NavUserHeader.styl.d.ts +28 -0
  35. package/src/components/ui/NavUserHeader/NavUserHeader.tsx +148 -0
  36. package/src/components/ui/NavUserHeader/NavUserHeader.types.ts +27 -0
  37. package/src/components/ui/NavUserHeader/avatar.svg +4 -0
  38. package/src/components/ui/NavUserHeader/index.ts +5 -0
  39. package/src/components/ui/Sidebar/Sidebar.styl +0 -25
  40. package/src/components/ui/Sidebar/Sidebar.styl.d.ts +0 -1
  41. package/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.styl +29 -0
  42. package/src/{mini-app/MiniAppRoot.styl.d.ts → components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.styl.d.ts} +4 -2
  43. package/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.tsx +128 -0
  44. package/src/components/widgets/SidebarDatasetsItemsGrouped/groupSidebarDatasets.ts +51 -0
  45. package/src/components/widgets/SidebarDatasetsItemsGrouped/index.ts +10 -0
  46. package/src/docs/pages/NavUserHeaderPage.tsx +89 -0
  47. package/src/docs/pages/SidebarDatasetsItemsGroupedPage.tsx +136 -0
  48. package/src/docs/pages/SybilionAuthProviderPage.tsx +40 -0
  49. package/src/docs/registry.ts +15 -3
  50. package/src/index.ts +4 -1
  51. package/src/sybilion-auth/SybilionAuthProvider.tsx +344 -0
  52. package/src/sybilion-auth/authPaths.ts +6 -0
  53. package/src/sybilion-auth/exchangeSybilionToken.ts +51 -0
  54. package/src/sybilion-auth/index.ts +17 -0
  55. package/src/{mini-app/miniAppDataTypes.ts → types/sybilionDatasetSnapshots.ts} +5 -8
  56. package/dist/esm/mini-app/MiniAppRoot.js +0 -82
  57. package/dist/esm/mini-app/MiniAppRoot.styl.js +0 -7
  58. package/dist/esm/mini-app/miniAppChatBridge.js +0 -45
  59. package/dist/esm/mini-app/miniAppDataClient.js +0 -98
  60. package/dist/esm/mini-app/miniAppProtocol.js +0 -153
  61. package/dist/esm/mini-app/miniAppThemeConfig.js +0 -40
  62. package/dist/esm/types/src/docs/pages/MiniAppRootPage.d.ts +0 -1
  63. package/dist/esm/types/src/mini-app/MiniAppRoot.d.ts +0 -18
  64. package/dist/esm/types/src/mini-app/index.d.ts +0 -10
  65. package/dist/esm/types/src/mini-app/miniAppChatBridge.d.ts +0 -6
  66. package/dist/esm/types/src/mini-app/miniAppDataClient.d.ts +0 -16
  67. package/dist/esm/types/src/mini-app/miniAppProtocol.d.ts +0 -89
  68. package/dist/esm/types/src/mini-app/miniAppThemeConfig.d.ts +0 -3
  69. package/docs/workspace-mini-apps.md +0 -51
  70. package/src/docs/pages/MiniAppRootPage.tsx +0 -58
  71. package/src/mini-app/MiniAppRoot.styl +0 -24
  72. package/src/mini-app/MiniAppRoot.tsx +0 -150
  73. package/src/mini-app/index.ts +0 -43
  74. package/src/mini-app/miniAppChatBridge.ts +0 -55
  75. package/src/mini-app/miniAppDataClient.ts +0 -165
  76. package/src/mini-app/miniAppProtocol.ts +0 -247
  77. package/src/mini-app/miniAppThemeConfig.ts +0 -45
@@ -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';
@@ -219,31 +219,6 @@
219
219
  padding 0
220
220
  padding-top 40px
221
221
 
222
- // Sidebar group label
223
- .sidebarGroupLabel
224
- color var(--sidebar-foreground)
225
- opacity 0.7
226
- display flex
227
- height 2rem
228
- flex-shrink 0
229
- align-items center
230
- border-radius 0.375rem
231
- padding-left 0.5rem
232
- padding-right 0.5rem
233
- font-size 0.75rem
234
- font-weight 500
235
- outline none
236
- transition margin 200ms ease-linear, opacity 200ms ease-linear
237
- &:focus-visible
238
- box-shadow 0 0 0 2px var(--sidebar-ring)
239
- & > svg
240
- width 1rem
241
- height 1rem
242
- flex-shrink 0
243
- &[data-collapsible="icon"]
244
- margin-top -2rem
245
- opacity 0
246
-
247
222
  // Sidebar group action
248
223
  .sidebarGroupAction
249
224
  color var(--sidebar-foreground)
@@ -11,7 +11,6 @@ interface CssExports {
11
11
  'sidebarFooter': string;
12
12
  'sidebarGroup': string;
13
13
  'sidebarGroupAction': string;
14
- 'sidebarGroupLabel': string;
15
14
  'sidebarHeader': string;
16
15
  'sidebarInput': string;
17
16
  'sidebarMainShell': string;
@@ -0,0 +1,29 @@
1
+ .chevronContainer
2
+ margin-left auto
3
+
4
+ .subMenuContainer
5
+ position relative
6
+
7
+ .subMenuBorder
8
+ position absolute
9
+ left var(--p-5)
10
+ top 0
11
+ bottom 26px
12
+ width 1px
13
+ background var(--sidebar-border)
14
+
15
+ .subMenuItem
16
+ position relative
17
+ margin-left var(--p-6)
18
+
19
+ &::before
20
+ content ''
21
+ position absolute
22
+ left calc(var(--p-1) * -1)
23
+ top var(--p-2)
24
+ bottom 0
25
+ width var(--p-3)
26
+ height var(--p-3)
27
+ border-left 1px solid var(--sidebar-border)
28
+ border-bottom 1px solid var(--sidebar-border)
29
+ border-bottom-left-radius var(--p-2)
@@ -1,8 +1,10 @@
1
1
  // This file is automatically generated.
2
2
  // Please do not change this file!
3
3
  interface CssExports {
4
- 'inner': string;
5
- 'root': string;
4
+ 'chevronContainer': string;
5
+ 'subMenuBorder': string;
6
+ 'subMenuContainer': string;
7
+ 'subMenuItem': string;
6
8
  }
7
9
  export const cssExports: CssExports;
8
10
  export default cssExports;
@@ -0,0 +1,128 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+
3
+ import {
4
+ SidebarGroup,
5
+ SidebarMenu,
6
+ SidebarMenuButton,
7
+ SidebarMenuItem,
8
+ SidebarMenuSub,
9
+ SidebarMenuSubButton,
10
+ SidebarMenuSubItem,
11
+ } from '#uilib/components/ui/Sidebar/Sidebar';
12
+ import { SmartTextTruncate } from '#uilib/components/ui/SmartTextTruncate';
13
+ import { ChevronDown, ChevronRight, PackageOpen } from 'lucide-react';
14
+
15
+ import S from './SidebarDatasetsItemsGrouped.styl';
16
+ import {
17
+ type SidebarDatasetsItemsGroupBy,
18
+ type SidebarDatasetsItemsGroupedDataset,
19
+ groupSidebarDatasets,
20
+ } from './groupSidebarDatasets';
21
+
22
+ export type SidebarDatasetsItemsGroupedProps = {
23
+ groupBy: SidebarDatasetsItemsGroupBy;
24
+ datasets: SidebarDatasetsItemsGroupedDataset[];
25
+ selectedDatasetId?: number;
26
+ onDatasetClick?: (datasetId: number) => void;
27
+ /** When omitted, all groups start expanded. */
28
+ defaultExpandedGroupNames?: string[];
29
+ className?: string;
30
+ };
31
+
32
+ export function SidebarDatasetsItemsGrouped({
33
+ groupBy,
34
+ datasets,
35
+ selectedDatasetId,
36
+ onDatasetClick,
37
+ defaultExpandedGroupNames,
38
+ className,
39
+ }: SidebarDatasetsItemsGroupedProps) {
40
+ const grouped = useMemo(
41
+ () => groupSidebarDatasets(datasets, groupBy),
42
+ [datasets, groupBy],
43
+ );
44
+
45
+ const [expanded, setExpanded] = useState<Set<string>>(new Set());
46
+
47
+ useEffect(() => {
48
+ if (defaultExpandedGroupNames !== undefined) {
49
+ setExpanded(new Set(defaultExpandedGroupNames));
50
+ } else {
51
+ setExpanded(new Set(grouped.map(([name]) => name)));
52
+ }
53
+ }, [grouped, defaultExpandedGroupNames]);
54
+
55
+ useEffect(() => {
56
+ if (selectedDatasetId == null) return;
57
+ const groupName = grouped.find(([, ds]) =>
58
+ ds.some(d => d.id === selectedDatasetId),
59
+ )?.[0];
60
+ if (groupName) {
61
+ setExpanded(prev => new Set(prev).add(groupName));
62
+ }
63
+ }, [selectedDatasetId, grouped]);
64
+
65
+ const toggleGroup = (name: string) => {
66
+ setExpanded(prev => {
67
+ const next = new Set(prev);
68
+ if (next.has(name)) next.delete(name);
69
+ else next.add(name);
70
+ return next;
71
+ });
72
+ };
73
+
74
+ return (
75
+ <SidebarGroup className={className}>
76
+ <SidebarMenu>
77
+ {grouped.map(([groupName, groupDatasets]) => {
78
+ const isExpanded = expanded.has(groupName);
79
+ const parentActive = groupDatasets.some(
80
+ d => d.id === selectedDatasetId,
81
+ );
82
+
83
+ return (
84
+ <SidebarMenuItem key={groupName}>
85
+ <SidebarMenuButton
86
+ type="button"
87
+ isActive={parentActive}
88
+ onClick={() => toggleGroup(groupName)}
89
+ >
90
+ <PackageOpen strokeWidth={1.5} size={16} />
91
+ <SmartTextTruncate>{groupName}</SmartTextTruncate>
92
+ <div className={S.chevronContainer}>
93
+ {isExpanded ? (
94
+ <ChevronDown size={12} />
95
+ ) : (
96
+ <ChevronRight size={12} />
97
+ )}
98
+ </div>
99
+ </SidebarMenuButton>
100
+ {isExpanded && (
101
+ <SidebarMenuSub className={S.subMenuContainer}>
102
+ <div className={S.subMenuBorder} />
103
+ {groupDatasets.map(dataset => (
104
+ <SidebarMenuSubItem
105
+ key={dataset.id}
106
+ className={S.subMenuItem}
107
+ >
108
+ <SidebarMenuSubButton
109
+ href={`#dataset-${dataset.id}`}
110
+ isActive={dataset.id === selectedDatasetId}
111
+ onClick={e => {
112
+ e.preventDefault();
113
+ onDatasetClick?.(dataset.id);
114
+ }}
115
+ >
116
+ <SmartTextTruncate>{dataset.name}</SmartTextTruncate>
117
+ </SidebarMenuSubButton>
118
+ </SidebarMenuSubItem>
119
+ ))}
120
+ </SidebarMenuSub>
121
+ )}
122
+ </SidebarMenuItem>
123
+ );
124
+ })}
125
+ </SidebarMenu>
126
+ </SidebarGroup>
127
+ );
128
+ }
@@ -0,0 +1,51 @@
1
+ import type { SybilionDatasetSnapshot } from '#uilib/types/sybilionDatasetSnapshots';
2
+
3
+ export type SidebarDatasetsItemsGroupedDataset = SybilionDatasetSnapshot;
4
+
5
+ export type SidebarDatasetsItemsGroupBy =
6
+ | 'target_type'
7
+ | 'regions'
8
+ | 'categories';
9
+
10
+ export type GroupedSidebarDatasetsEntry = readonly [
11
+ groupName: string,
12
+ datasets: SidebarDatasetsItemsGroupedDataset[],
13
+ ];
14
+
15
+ export function groupSidebarDatasets(
16
+ datasets: SidebarDatasetsItemsGroupedDataset[],
17
+ groupBy: SidebarDatasetsItemsGroupBy,
18
+ ): GroupedSidebarDatasetsEntry[] {
19
+ const grouped: Record<string, SidebarDatasetsItemsGroupedDataset[]> = {};
20
+
21
+ datasets.forEach(dataset => {
22
+ if (groupBy === 'target_type') {
23
+ const key = dataset.target_type?.name || 'Unknown';
24
+ if (!grouped[key]) grouped[key] = [];
25
+ grouped[key].push(dataset);
26
+ } else if (groupBy === 'regions') {
27
+ if (dataset.regions && dataset.regions.length > 0) {
28
+ const mostSpecificRegion = dataset.regions[dataset.regions.length - 1];
29
+ const key = mostSpecificRegion.name || 'Unknown';
30
+ if (!grouped[key]) grouped[key] = [];
31
+ grouped[key].push(dataset);
32
+ } else {
33
+ const key = 'No Region';
34
+ if (!grouped[key]) grouped[key] = [];
35
+ grouped[key].push(dataset);
36
+ }
37
+ } else {
38
+ const key = dataset.category?.name || 'Unknown';
39
+ if (!grouped[key]) grouped[key] = [];
40
+ grouped[key].push(dataset);
41
+ }
42
+ });
43
+
44
+ return Object.entries(grouped)
45
+ .map(
46
+ ([name, ds]) =>
47
+ [name, [...ds].sort((a, b) => a.name.localeCompare(b.name))] as const,
48
+ )
49
+ .filter(([, ds]) => ds.length > 0)
50
+ .sort((a, b) => a[0].localeCompare(b[0]));
51
+ }
@@ -0,0 +1,10 @@
1
+ export {
2
+ SidebarDatasetsItemsGrouped,
3
+ type SidebarDatasetsItemsGroupedProps,
4
+ } from './SidebarDatasetsItemsGrouped';
5
+ export type {
6
+ GroupedSidebarDatasetsEntry,
7
+ SidebarDatasetsItemsGroupBy,
8
+ SidebarDatasetsItemsGroupedDataset,
9
+ } from './groupSidebarDatasets';
10
+ export { groupSidebarDatasets } from './groupSidebarDatasets';
@@ -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,136 @@
1
+ import { useState } from 'react';
2
+
3
+ import { PageContentSection } from '#uilib/components/ui/Page';
4
+ import { ToggleGroup, ToggleGroupItem } from '#uilib/components/ui/ToggleGroup';
5
+ import {
6
+ type SidebarDatasetsItemsGroupBy,
7
+ SidebarDatasetsItemsGrouped,
8
+ type SidebarDatasetsItemsGroupedDataset,
9
+ } from '#uilib/components/widgets/SidebarDatasetsItemsGrouped';
10
+
11
+ import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
12
+ import { DocsHeaderActions } from '../docsHeaderActions';
13
+
14
+ const MOCK_DATASETS: SidebarDatasetsItemsGroupedDataset[] = [
15
+ {
16
+ id: 1,
17
+ name: 'Acetic Acid Price - China - Dollar/MT',
18
+ status: 'active',
19
+ created_at: '2024-01-01',
20
+ updated_at: '2024-01-02',
21
+ keywords: '',
22
+ category: { id: 10, name: 'Chemicals' },
23
+ target_type_id: 1,
24
+ target_type: { id: 1, name: 'Commodity price' },
25
+ trend: 0,
26
+ regular_price: '0',
27
+ sale_price: '0',
28
+ regions: [
29
+ { id: 1, name: 'Asia' },
30
+ { id: 2, name: 'China' },
31
+ ],
32
+ unit: { id: 1, name: 'USD/MT' },
33
+ },
34
+ {
35
+ id: 2,
36
+ name: 'Freight index — spot volume (China)',
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: 2,
43
+ target_type: { id: 2, name: 'Freight index' },
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: 'Index' },
52
+ },
53
+ {
54
+ id: 3,
55
+ name: 'Generic industrial chemical — spot volume (Germany)',
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: 1,
62
+ target_type: { id: 1, name: 'Commodity price' },
63
+ trend: 0,
64
+ regular_price: '0',
65
+ sale_price: '0',
66
+ regions: [
67
+ { id: 3, name: 'Europe' },
68
+ { id: 4, name: 'Germany' },
69
+ ],
70
+ unit: { id: 1, name: 'MT' },
71
+ },
72
+ {
73
+ id: 4,
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
+ export default function SidebarDatasetsItemsGroupedPage() {
91
+ const [groupBy, setGroupBy] =
92
+ useState<SidebarDatasetsItemsGroupBy>('regions');
93
+ const [selectedDatasetId, setSelectedDatasetId] = useState<number>(1);
94
+
95
+ return (
96
+ <>
97
+ <AppPageHeader
98
+ breadcrumbs={[{ label: 'SidebarDatasetsItemsGrouped' }]}
99
+ title="SidebarDatasetsItemsGrouped"
100
+ subheader="Datasets sidebar: collapsible groups and nested rows."
101
+ actions={<DocsHeaderActions />}
102
+ />
103
+ <PageContentSection>
104
+ <ToggleGroup
105
+ type="single"
106
+ value={groupBy}
107
+ onValueChange={val => {
108
+ if (val) setGroupBy(val as SidebarDatasetsItemsGroupBy);
109
+ }}
110
+ >
111
+ <ToggleGroupItem value="target_type">Target type</ToggleGroupItem>
112
+ <ToggleGroupItem value="regions">Regions</ToggleGroupItem>
113
+ <ToggleGroupItem value="categories">Categories</ToggleGroupItem>
114
+ </ToggleGroup>
115
+ <div
116
+ style={{
117
+ maxWidth: 420,
118
+ marginTop: 16,
119
+ padding: 12,
120
+ background: 'var(--sidebar)',
121
+ color: 'var(--sidebar-foreground)',
122
+ border: '1px solid var(--border)',
123
+ borderRadius: 'var(--p-4)',
124
+ }}
125
+ >
126
+ <SidebarDatasetsItemsGrouped
127
+ groupBy={groupBy}
128
+ datasets={MOCK_DATASETS}
129
+ selectedDatasetId={selectedDatasetId}
130
+ onDatasetClick={setSelectedDatasetId}
131
+ />
132
+ </div>
133
+ </PageContentSection>
134
+ </>
135
+ );
136
+ }