@sybilion/uilib 1.2.10 → 1.2.11

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 (50) hide show
  1. package/README.md +10 -7
  2. package/dist/esm/components/ui/AppHeader/AppHeader.styl.js +1 -1
  3. package/dist/esm/components/ui/Logo/Logo.js +2 -1
  4. package/dist/esm/components/ui/NavUserHeader/NavUserHeader.js +11 -3
  5. package/dist/esm/components/ui/NavUserHeader/NavUserHeader.styl.js +2 -2
  6. package/dist/esm/components/ui/Sidebar/Sidebar.styl.js +1 -1
  7. package/dist/esm/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.js +8 -2
  8. package/dist/esm/components/widgets/SignInPage/SignInPage.js +2 -2
  9. package/dist/esm/components/widgets/SybilionAuthLayout/SybilionAuthHeadline.styl.js +1 -1
  10. package/dist/esm/components/widgets/SybilionAuthLayout/SybilionAuthLayout.js +3 -8
  11. package/dist/esm/components/widgets/SybilionAuthLayout/SybilionAuthLayout.styl.js +2 -2
  12. package/dist/esm/components/widgets/SybilionSignInPanel/SybilionSignInPanel.styl.js +1 -1
  13. package/dist/esm/index.js +1 -1
  14. package/dist/esm/sybilion-auth/SybilionAuthProvider.js +23 -10
  15. package/dist/esm/types/src/components/ui/Logo/Logo.d.ts +2 -1
  16. package/dist/esm/types/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.d.ts +4 -1
  17. package/dist/esm/types/src/components/widgets/SignInPage/SignInPage.d.ts +2 -2
  18. package/dist/esm/types/src/components/widgets/SybilionAuthLayout/SybilionAuthLayout.d.ts +2 -5
  19. package/dist/esm/types/src/components/widgets/SybilionAuthLayout/index.d.ts +1 -1
  20. package/dist/esm/types/src/docs/pages/SybilionAuthLayoutPage.d.ts +1 -0
  21. package/dist/esm/types/src/sybilion-auth/SybilionAuthProvider.d.ts +10 -1
  22. package/package.json +2 -4
  23. package/src/components/ui/AppHeader/AppHeader.styl +2 -0
  24. package/src/components/ui/Logo/Logo.tsx +2 -1
  25. package/src/components/ui/NavUserHeader/NavUserHeader.styl +2 -20
  26. package/src/components/ui/NavUserHeader/NavUserHeader.styl.d.ts +0 -3
  27. package/src/components/ui/NavUserHeader/NavUserHeader.tsx +30 -28
  28. package/src/components/ui/Sidebar/Sidebar.styl +2 -2
  29. package/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.tsx +14 -3
  30. package/src/components/widgets/SignInPage/SignInPage.tsx +1 -3
  31. package/src/components/widgets/SybilionAuthLayout/SybilionAuthHeadline.styl +2 -2
  32. package/src/components/widgets/SybilionAuthLayout/SybilionAuthHeadline.styl.d.ts +10 -2
  33. package/src/components/widgets/SybilionAuthLayout/SybilionAuthLayout.styl +20 -9
  34. package/src/components/widgets/SybilionAuthLayout/SybilionAuthLayout.styl.d.ts +16 -2
  35. package/src/components/widgets/SybilionAuthLayout/SybilionAuthLayout.tsx +4 -17
  36. package/src/components/widgets/SybilionAuthLayout/index.ts +0 -1
  37. package/src/components/widgets/SybilionSignInPanel/SybilionSignInPanel.styl +1 -1
  38. package/src/components/widgets/SybilionSignInPanel/SybilionSignInPanel.styl.d.ts +12 -2
  39. package/src/docs/DocsShell.tsx +16 -7
  40. package/src/docs/components/DocsSidebar/DocsSidebar.tsx +1 -0
  41. package/src/docs/pages/ChartAreaInteractivePage.tsx +1 -1
  42. package/src/docs/pages/StandaloneAppLayoutPage/StandaloneAppLayoutPage.styl +1 -1
  43. package/src/docs/pages/StandaloneAppLayoutPage/StandaloneAppLayoutPage.tsx +5 -4
  44. package/src/docs/pages/SybilionAuthLayoutPage.tsx +47 -0
  45. package/src/docs/registry.ts +3 -3
  46. package/src/sybilion-auth/SybilionAuthProvider.tsx +34 -8
  47. package/assets/standalone-global.css +0 -257
  48. package/dist/esm/types/src/docs/pages/SybilionAuthProviderPage.d.ts +0 -1
  49. package/docs/standalone-apps.md +0 -553
  50. package/src/docs/pages/SybilionAuthProviderPage.tsx +0 -40
@@ -67,10 +67,12 @@
67
67
 
68
68
  .userInfo
69
69
  display flex
70
+ flex 1
70
71
  flex-direction column
71
72
  align-items flex-start
72
73
  text-align left
73
74
  gap 0.25rem
75
+ min-width 0
74
76
 
75
77
  .userName
76
78
  font-size var(--text-sm)
@@ -98,26 +100,6 @@
98
100
  text-align left
99
101
  font-size 0.875rem
100
102
 
101
- .userDetails
102
- display grid
103
- flex 1
104
- text-align left
105
- font-size 0.875rem
106
- line-height 1.25
107
-
108
- .userDetailName
109
- text-overflow ellipsis
110
- overflow hidden
111
- white-space nowrap
112
- font-weight 500
113
-
114
- .userDetailEmail
115
- color var(--color-muted-foreground)
116
- text-overflow ellipsis
117
- overflow hidden
118
- white-space nowrap
119
- font-size 0.75rem
120
-
121
103
  @keyframes pulse
122
104
  0%, 100%
123
105
  opacity 1
@@ -15,9 +15,6 @@ interface CssExports {
15
15
  'pulse': string;
16
16
  'textSkeleton': string;
17
17
  'userButton': string;
18
- 'userDetailEmail': string;
19
- 'userDetailName': string;
20
- 'userDetails': string;
21
18
  'userEmail': string;
22
19
  'userInfo': string;
23
20
  'userLabel': string;
@@ -1,4 +1,5 @@
1
1
  import cn from 'classnames';
2
+ import { useMemo } from 'react';
2
3
 
3
4
  import { useTheme } from '#uilib/contexts/theme-context';
4
5
  import {
@@ -9,7 +10,7 @@ import {
9
10
  } from '@phosphor-icons/react';
10
11
  import { ChevronDownIcon } from 'lucide-react';
11
12
 
12
- import { Avatar } from '../Avatar';
13
+ import { Avatar, AvatarFallback, AvatarImage } from '../Avatar';
13
14
  import { Button } from '../Button';
14
15
  import {
15
16
  DropdownMenu,
@@ -20,7 +21,6 @@ import {
20
21
  DropdownMenuSeparator,
21
22
  DropdownMenuTrigger,
22
23
  } from '../DropdownMenu';
23
- import { Image } from '../Image';
24
24
  import S from './NavUserHeader.styl';
25
25
  import type { NavUserHeaderProps } from './NavUserHeader.types';
26
26
 
@@ -49,6 +49,30 @@ export function NavUserHeader({
49
49
  const userName = user?.name ?? '';
50
50
  const userEmail = user?.email ?? '';
51
51
 
52
+ const avatar = useMemo(() => {
53
+ const initials = userName
54
+ .split(' ')
55
+ .map(name => name.charAt(0))
56
+ .join('');
57
+
58
+ return (
59
+ <Avatar className={S.avatar}>
60
+ <AvatarImage src={avatarUrl || undefined} alt={userName} />
61
+ <AvatarFallback>{initials}</AvatarFallback>
62
+ </Avatar>
63
+ );
64
+ }, [avatarUrl, userName]);
65
+
66
+ const userIdentity = useMemo(
67
+ () => (
68
+ <div className={S.userInfo}>
69
+ <span className={`${S.userName} ph-no-capture`}>{userName}</span>
70
+ <span className={S.userEmail}>{userEmail}</span>
71
+ </div>
72
+ ),
73
+ [userEmail, userName],
74
+ );
75
+
52
76
  if (isLoading) {
53
77
  return (
54
78
  <Button variant="ghost" size="sm" disabled className={S.loadingButton}>
@@ -84,21 +108,10 @@ export function NavUserHeader({
84
108
  size="sm"
85
109
  className={cn(S.userButton, variant === 'compact' && S.compact)}
86
110
  >
87
- <Avatar className={S.avatar}>
88
- <Image
89
- url={avatarUrl}
90
- alt={userName}
91
- fallback={<div className={S.avatarFallback} />}
92
- />
93
- </Avatar>
111
+ {avatar}
94
112
  {variant === 'default' && (
95
113
  <>
96
- <div className={S.userInfo}>
97
- <span className={`${S.userName} ph-no-capture`}>
98
- {userName}
99
- </span>
100
- <span className={S.userEmail}>{userEmail}</span>
101
- </div>
114
+ {userIdentity}
102
115
  <ChevronDownIcon className={S.iconSm} />
103
116
  </>
104
117
  )}
@@ -111,19 +124,8 @@ export function NavUserHeader({
111
124
  >
112
125
  <DropdownMenuLabel className={S.userLabel}>
113
126
  <div className={S.userLabelContent}>
114
- <Avatar className={S.avatar}>
115
- <Image
116
- url={avatarUrl}
117
- alt={userName}
118
- fallback={<div className={S.avatarFallback} />}
119
- />
120
- </Avatar>
121
- <div className={S.userDetails}>
122
- <span className={`${S.userDetailName} ph-no-capture`}>
123
- {userName}
124
- </span>
125
- <span className={S.userDetailEmail}>{userEmail}</span>
126
- </div>
127
+ {avatar}
128
+ {userIdentity}
127
129
  </div>
128
130
  </DropdownMenuLabel>
129
131
  <DropdownMenuSeparator />
@@ -193,7 +193,6 @@
193
193
  .sidebarContent
194
194
  position absolute
195
195
  display flex
196
- min-height 0
197
196
  flex 1
198
197
  flex-direction column
199
198
  gap var(--p-16)
@@ -201,7 +200,8 @@
201
200
 
202
201
  width 100%
203
202
  height 100vh
204
- max-height @height
203
+ min-height 0
204
+ max-height 100%
205
205
 
206
206
  @media (min-width MOBILE)
207
207
  height calc(100vh - var(--gap-top))
@@ -26,7 +26,10 @@ export type SidebarDatasetsItemsGroupedProps = {
26
26
  postItems?: React.ReactNode;
27
27
  selectedDatasetId?: number;
28
28
  onDatasetClick?: (datasetId: number) => void;
29
- /** When omitted, all groups start expanded. */
29
+ /**
30
+ * When set, expanded state resets to this list whenever `grouped` changes.
31
+ * Omit: start collapsed; any group containing `selectedDatasetId` opens (toggle still adds more).
32
+ */
30
33
  defaultExpandedGroupNames?: string[];
31
34
  className?: string;
32
35
  };
@@ -46,15 +49,23 @@ export function SidebarDatasetsItemsGrouped({
46
49
  [datasets, groupBy],
47
50
  );
48
51
 
52
+ /** Content key — inline `[]` must not retrigger reset every render (new array ref). */
53
+ const defaultExpandedKey =
54
+ defaultExpandedGroupNames === undefined
55
+ ? '__smart__'
56
+ : defaultExpandedGroupNames.join('\0');
57
+
49
58
  const [expanded, setExpanded] = useState<Set<string>>(new Set());
50
59
 
51
60
  useEffect(() => {
52
61
  if (defaultExpandedGroupNames !== undefined) {
53
62
  setExpanded(new Set(defaultExpandedGroupNames));
54
63
  } else {
55
- setExpanded(new Set(grouped.map(([name]) => name)));
64
+ setExpanded(new Set());
56
65
  }
57
- }, [grouped, defaultExpandedGroupNames]);
66
+ // Intentionally `defaultExpandedKey` not `defaultExpandedGroupNames` (reference stability).
67
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- key encodes array contents
68
+ }, [grouped, defaultExpandedKey]);
58
69
 
59
70
  useEffect(() => {
60
71
  if (selectedDatasetId == null) return;
@@ -13,7 +13,7 @@ import {
13
13
 
14
14
  export type SignInPageProps = Pick<
15
15
  SybilionAuthLayoutProps,
16
- 'heroBackgroundUrl' | 'logoSize' | 'containerClassName'
16
+ 'logoSize' | 'containerClassName'
17
17
  > &
18
18
  Pick<
19
19
  SybilionSignInPanelProps,
@@ -42,7 +42,6 @@ export function SignInPage({
42
42
  primaryButtonLabel,
43
43
  connectingLabel,
44
44
  loginRedirectOptions,
45
- heroBackgroundUrl,
46
45
  logoSize,
47
46
  containerClassName,
48
47
  }: SignInPageProps) {
@@ -65,7 +64,6 @@ export function SignInPage({
65
64
  <SybilionAuthLayout
66
65
  title={title}
67
66
  subtitle={subtitle}
68
- heroBackgroundUrl={heroBackgroundUrl}
69
67
  logoSize={logoSize}
70
68
  containerClassName={containerClassName}
71
69
  >
@@ -10,7 +10,7 @@
10
10
  height 100%
11
11
  font-family var(--font-family-heading)
12
12
  font-weight 300
13
- text-shadow 0 0 2px var(--background)
13
+ text-shadow 0 0 7px var(--background)
14
14
 
15
15
  .headline
16
16
  font-size 40px
@@ -23,4 +23,4 @@
23
23
  margin 0
24
24
 
25
25
  .headlineCyan
26
- color #27d1ef
26
+ color var(--brand-color)
@@ -1,2 +1,10 @@
1
- const mod: { [cls: string]: string };
2
- export default mod;
1
+ // This file is automatically generated.
2
+ // Please do not change this file!
3
+ interface CssExports {
4
+ 'headline': string;
5
+ 'headlineCyan': string;
6
+ 'headlineParagraph': string;
7
+ 'root': string;
8
+ }
9
+ export const cssExports: CssExports;
10
+ export default cssExports;
@@ -2,6 +2,7 @@
2
2
  height 100vh
3
3
  display flex
4
4
  width 100%
5
+ background-color var(--background)
5
6
 
6
7
  .leftPanel
7
8
  display none
@@ -9,6 +10,8 @@
9
10
  @media (min-width: 768px)
10
11
  display flex
11
12
  width 50%
13
+ height calc(100% - var(--p-6))
14
+ margin var(--p-3) 0 var(--p-3) var(--p-3)
12
15
  position relative
13
16
  overflow hidden
14
17
  background-color var(--secondary)
@@ -20,15 +23,23 @@
20
23
  :global(.dark) &
21
24
  background-color var(--page-color-alpha-800)
22
25
 
23
- .bgImage
26
+ // ~500px graphic, -200px offset → ~300px (~60%) visible inside panel (clipped by .leftPanel overflow)
27
+ .heroWatermark
24
28
  position absolute
25
- bottom 0
26
- left 0
27
- width 300px
28
- height 300px
29
- background-size contain
30
- background-repeat no-repeat
31
- background-position left bottom
29
+ bottom -180px
30
+ left -180px
31
+ width 500px
32
+ height 500px
33
+ align-items flex-end
34
+ justify-content flex-start
35
+ gap 0
36
+ pointer-events none
37
+
38
+ img
39
+ width 100% !important
40
+ height 100% !important
41
+ object-fit contain
42
+ object-position left bottom
32
43
 
33
44
  .logoContainer
34
45
  z-index 10
@@ -45,8 +56,8 @@
45
56
  height 24px
46
57
 
47
58
  .rightPanel
59
+ position relative
48
60
  flex 1
49
- background-color var(--background)
50
61
  display flex
51
62
  flex-direction column
52
63
  justify-content center
@@ -1,2 +1,16 @@
1
- const mod: { [cls: string]: string };
2
- export default mod;
1
+ // This file is automatically generated.
2
+ // Please do not change this file!
3
+ interface CssExports {
4
+ 'formContainer': string;
5
+ 'header': string;
6
+ 'heroWatermark': string;
7
+ 'leftPanel': string;
8
+ 'logo': string;
9
+ 'logoContainer': string;
10
+ 'rightPanel': string;
11
+ 'root': string;
12
+ 'subtitle': string;
13
+ 'title': string;
14
+ }
15
+ export const cssExports: CssExports;
16
+ export default cssExports;
@@ -7,18 +7,13 @@ import type { LogoSize } from '#uilib/components/ui/Logo/Logo.types';
7
7
  import { SybilionAuthHeadline } from './SybilionAuthHeadline';
8
8
  import S from './SybilionAuthLayout.styl';
9
9
 
10
- /** Same convention as {@link SYBILION_STANDALONE_LOGO_PUBLIC_URL}: copy `sybilion-bg.svg` from the package into `public/`. */
11
- export const SYBILION_STANDALONE_AUTH_HERO_BG_PUBLIC_URL =
12
- '/sybilion_bg.svg' as const;
13
-
14
10
  export type SybilionAuthLayoutProps = {
15
11
  title: string;
16
12
  subtitle?: string;
17
13
  children: ReactNode;
18
14
  logoSize?: LogoSize;
19
15
  containerClassName?: string;
20
- /** Public URL for the hero watermark SVG (default {@link SYBILION_STANDALONE_AUTH_HERO_BG_PUBLIC_URL}). */
21
- heroBackgroundUrl?: string;
16
+ style?: React.CSSProperties;
22
17
  };
23
18
 
24
19
  export function SybilionAuthLayout({
@@ -27,20 +22,12 @@ export function SybilionAuthLayout({
27
22
  children,
28
23
  logoSize,
29
24
  containerClassName,
30
- heroBackgroundUrl = SYBILION_STANDALONE_AUTH_HERO_BG_PUBLIC_URL,
25
+ style,
31
26
  }: SybilionAuthLayoutProps) {
32
- const bg = heroBackgroundUrl
33
- ? `url(${JSON.stringify(heroBackgroundUrl)})`
34
- : 'none';
35
-
36
27
  return (
37
- <div className={S.root}>
28
+ <div className={S.root} style={style}>
38
29
  <div className={S.leftPanel}>
39
- <div
40
- className={S.bgImage}
41
- style={{ backgroundImage: bg }}
42
- aria-hidden
43
- />
30
+ <Logo showText={false} className={S.heroWatermark} aria-hidden />
44
31
 
45
32
  <div className={S.logoContainer}>
46
33
  <Logo className={S.logo} size={logoSize} />
@@ -1,6 +1,5 @@
1
1
  export {
2
2
  SybilionAuthLayout,
3
- SYBILION_STANDALONE_AUTH_HERO_BG_PUBLIC_URL,
4
3
  type SybilionAuthLayoutProps,
5
4
  } from './SybilionAuthLayout';
6
5
  export { SybilionAuthHeadline } from './SybilionAuthHeadline';
@@ -41,7 +41,7 @@
41
41
  display block
42
42
  margin-top var(--p-8)
43
43
  padding-bottom var(--p-2)
44
- width 50%
44
+ width 100%
45
45
  box-sizing border-box
46
46
  text-align center
47
47
  font-size 14px
@@ -1,2 +1,12 @@
1
- const mod: { [cls: string]: string };
2
- export default mod;
1
+ // This file is automatically generated.
2
+ // Please do not change this file!
3
+ interface CssExports {
4
+ 'errorMessage': string;
5
+ 'forgotPassword': string;
6
+ 'forgotPasswordLink': string;
7
+ 'socialButton': string;
8
+ 'socialButtonContainer': string;
9
+ 'version': string;
10
+ }
11
+ export const cssExports: CssExports;
12
+ export default cssExports;
@@ -1,10 +1,10 @@
1
- import { Outlet } from 'react-router-dom';
1
+ import { Outlet, useLocation, useNavigate } from 'react-router-dom';
2
2
 
3
- import { AppHeaderHost, AppHeaderPortal } from '#uilib/components/ui/AppHeader';
4
- import { Gap } from '#uilib/components/ui/Gap/Gap';
3
+ import { AppHeaderHost } from '#uilib/components/ui/AppHeader';
5
4
  import { AppShell, AppShellMainContent } from '#uilib/components/ui/Page';
6
5
  import { PageFooter } from '#uilib/components/ui/Page/PageFooter/PageFooter';
7
6
  import { PageScroll } from '#uilib/components/ui/Page/PageScroll/PageScroll';
7
+ import { SybilionAppHeader } from '#uilib/components/widgets/SybilionAppHeader';
8
8
  import { ThemeToggle } from '#uilib/docs/App/ThemeToggle';
9
9
  import { DocsSidebar } from '#uilib/docs/components/DocsSidebar/DocsSidebar';
10
10
 
@@ -12,6 +12,9 @@ import LogoSvg from '../../assets/logo.svg';
12
12
  import S from './DocsShell.styl';
13
13
 
14
14
  export function DocsShell() {
15
+ const location = useLocation();
16
+ const navigate = useNavigate();
17
+
15
18
  return (
16
19
  <PageScroll className={S.root}>
17
20
  <AppShell>
@@ -27,10 +30,16 @@ export function DocsShell() {
27
30
  />
28
31
  }
29
32
  >
30
- <AppHeaderPortal>
31
- <Gap />
32
- <ThemeToggle />
33
- </AppHeaderPortal>
33
+ <SybilionAppHeader
34
+ pathname={location.pathname}
35
+ onNavigate={href => {
36
+ void navigate(href);
37
+ }}
38
+ authenticated={false}
39
+ isAuthenticated={false}
40
+ onLogout={() => undefined}
41
+ signInSlot={<ThemeToggle />}
42
+ />
34
43
  <Outlet />
35
44
  </AppShellMainContent>
36
45
  </AppShell>
@@ -79,6 +79,7 @@ export function DocsSidebar() {
79
79
  useEffect(() => {
80
80
  if (activeSection) {
81
81
  setExpandedSections(prev => new Set(prev).add(activeSection));
82
+ setSidebarSearch('');
82
83
  }
83
84
  }, [activeSection]);
84
85
 
@@ -84,7 +84,7 @@ const INITIAL_CHART: ChartDataPoint[] = [
84
84
  ];
85
85
 
86
86
  const DEMO_FORECAST_ITEMS: ForecastItemData[] = [
87
- { id: DEMO_FORECAST_ID, name: 'Forecast' },
87
+ { id: DEMO_FORECAST_ID, name: 'My custom forecast' },
88
88
  ];
89
89
 
90
90
  type DemoMode = 'none' | OverlayMode;
@@ -29,7 +29,7 @@
29
29
  min-height 100vh
30
30
  margin-top var(--p-4)
31
31
  padding var(--p-4)
32
- border-radius var(--p-2)
32
+ border-radius var(--p-4)
33
33
  border 2px dashed var(--border)
34
34
  background-color var(--background)
35
35
  color var(--muted-foreground)
@@ -191,10 +191,11 @@ export default function StandaloneAppLayoutPage() {
191
191
  title="Standalone app layout"
192
192
  subheader={
193
193
  <>
194
- Live preview of AppShell + Sidebar + main column from
195
- docs/standalone-apps.md §4. <br />
196
- Full greenfield setup (deps, global CSS, SybilionAuthProvider, SDK)
197
- lives in that doc — this page is layout only.
194
+ Live preview of AppShell + Sidebar + main column.{' '}
195
+ <br />
196
+ Greenfield wiring: copy{' '}
197
+ <code>mini/sybilion-app-template/</code> and follow its README — this
198
+ page is layout only.
198
199
  </>
199
200
  }
200
201
  actions={<DocsHeaderActions />}
@@ -0,0 +1,47 @@
1
+ import { PageContentSection } from '#uilib/components/ui/Page';
2
+ import { SybilionAuthLayout } from '#uilib/components/widgets/SybilionAuthLayout';
3
+ import { SybilionSignInPanel } from '#uilib/components/widgets/SybilionSignInPanel';
4
+
5
+ import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
6
+ import { DocsHeaderActions } from '../docsHeaderActions';
7
+
8
+ export default function SybilionAuthLayoutPage() {
9
+ return (
10
+ <>
11
+ <AppPageHeader
12
+ breadcrumbs={[{ label: 'SybilionAuthLayout' }]}
13
+ title="SybilionAuthLayout"
14
+ subheader="Split-view auth chrome (hero column + form). Standalone apps: copy package logo to public/logo.svg — see Logo."
15
+ actions={<DocsHeaderActions />}
16
+ />
17
+ <PageContentSection title="Preview">
18
+ <p style={{ marginTop: 0, color: 'var(--muted-foreground)' }}>
19
+ Clipped height so <code>100vh</code> layout fits the docs shell. Real
20
+ sign-in route uses full viewport — e.g. <code>SignInPage</code> (wraps
21
+ this layout + <code>SybilionSignInPanel</code>).
22
+ </p>
23
+ <div
24
+ style={{
25
+ overflow: 'hidden',
26
+ borderRadius: 34,
27
+ border: '2px dashed var(--border)',
28
+ height: 'min(720px, 85vh)',
29
+ }}
30
+ >
31
+ <SybilionAuthLayout
32
+ title="Sign In"
33
+ subtitle="Example — authenticate to continue."
34
+ style={{ height: '100%' }}
35
+ >
36
+ <SybilionSignInPanel
37
+ onSignIn={() => undefined}
38
+ forgotPasswordTo="/forgot-password"
39
+ releasesTo="/releases"
40
+ versionLabel="0.0.1"
41
+ />
42
+ </SybilionAuthLayout>
43
+ </div>
44
+ </PageContentSection>
45
+ </>
46
+ );
47
+ }
@@ -163,10 +163,10 @@ export const DOC_REGISTRY: DocEntry[] = [
163
163
  load: () => import('./pages/InteractiveContentPage'),
164
164
  },
165
165
  {
166
- slug: 'sybilion-auth-provider',
167
- title: 'SybilionAuthProvider',
166
+ slug: 'sybilion-auth-layout',
167
+ title: 'SybilionAuthLayout',
168
168
  section: 'Layout',
169
- load: () => import('./pages/SybilionAuthProviderPage'),
169
+ load: () => import('./pages/SybilionAuthLayoutPage'),
170
170
  },
171
171
  {
172
172
  slug: 'label',
@@ -57,7 +57,16 @@ export type SybilionAuthProviderProps = {
57
57
  redirect_uri?: string;
58
58
  };
59
59
  sybilionTokenStorageKey?: string;
60
+ /**
61
+ * When set, passed to Auth0 `logout({ logoutParams: { returnTo } })` if
62
+ * {@link SybilionAuthProviderProps.useFederatedLogout} is true.
63
+ */
60
64
  logoutReturnTo?: string;
65
+ /**
66
+ * If true, redirect to Auth0 `/v2/logout` (clears IdP session). Default false matches
67
+ * sybilion-client: local session clear only, no redirect (`openUrl: false`).
68
+ */
69
+ useFederatedLogout?: boolean;
61
70
  };
62
71
 
63
72
  export type SybilionAuthContextValue = {
@@ -144,11 +153,13 @@ function InnerSybilionSession({
144
153
  sdk,
145
154
  storageKey,
146
155
  logoutReturnTo,
156
+ useFederatedLogout = false,
147
157
  }: {
148
158
  children: ReactNode;
149
159
  sdk: SybilionSDK;
150
160
  storageKey: string;
151
161
  logoutReturnTo?: string;
162
+ useFederatedLogout?: boolean;
152
163
  }): JSX.Element {
153
164
  const auth0 = useAuth0();
154
165
  const auth0Ref = useRef(auth0);
@@ -157,6 +168,7 @@ function InnerSybilionSession({
157
168
  const [sybilionToken, setSybilionToken] = useState<string | null>(null);
158
169
  const [exchangeLoading, setExchangeLoading] = useState(false);
159
170
  const [exchangeError, setExchangeError] = useState<string | null>(null);
171
+ const [isLoggingOut, setIsLoggingOut] = useState(false);
160
172
 
161
173
  const persistToken = useCallback(
162
174
  (t: string | null) => {
@@ -167,15 +179,26 @@ function InnerSybilionSession({
167
179
  );
168
180
 
169
181
  const doLogout = useCallback(() => {
182
+ setIsLoggingOut(true);
170
183
  persistToken(null);
171
184
  setExchangeError(null);
172
- const returnTo =
173
- logoutReturnTo ??
174
- (typeof window !== 'undefined' ? window.location.origin : undefined);
175
- auth0Ref.current.logout({
176
- logoutParams: returnTo ? { returnTo } : undefined,
177
- });
178
- }, [persistToken, logoutReturnTo]);
185
+ if (useFederatedLogout) {
186
+ const returnTo =
187
+ logoutReturnTo ??
188
+ (typeof window !== 'undefined' ? window.location.origin : undefined);
189
+ void auth0Ref.current.logout({
190
+ logoutParams: returnTo ? { returnTo } : undefined,
191
+ });
192
+ } else {
193
+ void auth0Ref.current.logout({ openUrl: false });
194
+ }
195
+ }, [persistToken, logoutReturnTo, useFederatedLogout]);
196
+
197
+ useEffect(() => {
198
+ if (!auth0.isAuthenticated) {
199
+ setIsLoggingOut(false);
200
+ }
201
+ }, [auth0.isAuthenticated]);
179
202
 
180
203
  const apiBaseUrl = useMemo(() => getSybilionApiOriginFromSdk(sdk), [sdk]);
181
204
 
@@ -255,7 +278,8 @@ function InnerSybilionSession({
255
278
  exchangeLoading ||
256
279
  (Boolean(auth0.isAuthenticated && auth0.user) &&
257
280
  sybilionToken === null &&
258
- exchangeError === null);
281
+ exchangeError === null &&
282
+ !isLoggingOut);
259
283
 
260
284
  const value = useMemo(
261
285
  (): SybilionAuthContextValue => ({
@@ -299,6 +323,7 @@ export function SybilionAuthProvider({
299
323
  authorizationParams,
300
324
  sybilionTokenStorageKey = DEFAULT_TOKEN_KEY,
301
325
  logoutReturnTo,
326
+ useFederatedLogout = false,
302
327
  }: SybilionAuthProviderProps): JSX.Element {
303
328
  const mergedAuthParams = useMemo(
304
329
  () => ({
@@ -336,6 +361,7 @@ export function SybilionAuthProvider({
336
361
  sdk={sdk}
337
362
  storageKey={sybilionTokenStorageKey}
338
363
  logoutReturnTo={logoutReturnTo}
364
+ useFederatedLogout={useFederatedLogout}
339
365
  >
340
366
  {children}
341
367
  </InnerSybilionSession>