@sybilion/uilib 1.2.8 → 1.2.10

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 (53) hide show
  1. package/dist/esm/components/ui/NavUserHeader/NavUserHeader.js +8 -3
  2. package/dist/esm/components/widgets/SignInPage/SignInPage.js +25 -0
  3. package/dist/esm/components/widgets/SybilionAppHeader/SybilionAppHeader.js +2 -2
  4. package/dist/esm/components/widgets/SybilionAuthLayout/SybilionAuthHeadline.js +8 -0
  5. package/dist/esm/components/widgets/SybilionAuthLayout/SybilionAuthHeadline.styl.js +7 -0
  6. package/dist/esm/components/widgets/SybilionAuthLayout/SybilionAuthLayout.js +16 -0
  7. package/dist/esm/components/widgets/SybilionAuthLayout/SybilionAuthLayout.styl.js +7 -0
  8. package/dist/esm/components/widgets/SybilionSignInPanel/SybilionSignInPanel.js +11 -0
  9. package/dist/esm/components/widgets/SybilionSignInPanel/SybilionSignInPanel.styl.js +7 -0
  10. package/dist/esm/contexts/theme-context.js +44 -0
  11. package/dist/esm/docs/lib/theme.js +35 -3
  12. package/dist/esm/index.js +6 -0
  13. package/dist/esm/types/src/components/ui/NavUserHeader/NavUserHeader.d.ts +1 -1
  14. package/dist/esm/types/src/components/ui/NavUserHeader/NavUserHeader.types.d.ts +3 -0
  15. package/dist/esm/types/src/components/widgets/SignInPage/SignInPage.d.ts +10 -0
  16. package/dist/esm/types/src/components/widgets/SignInPage/index.d.ts +1 -0
  17. package/dist/esm/types/src/components/widgets/SybilionAppHeader/SybilionAppHeader.d.ts +5 -1
  18. package/dist/esm/types/src/components/widgets/SybilionAuthLayout/SybilionAuthHeadline.d.ts +1 -0
  19. package/dist/esm/types/src/components/widgets/SybilionAuthLayout/SybilionAuthLayout.d.ts +14 -0
  20. package/dist/esm/types/src/components/widgets/SybilionAuthLayout/index.d.ts +2 -0
  21. package/dist/esm/types/src/components/widgets/SybilionSignInPanel/SybilionSignInPanel.d.ts +11 -0
  22. package/dist/esm/types/src/components/widgets/SybilionSignInPanel/index.d.ts +1 -0
  23. package/dist/esm/types/src/contexts/theme-context.d.ts +20 -0
  24. package/dist/esm/types/src/docs/contexts/theme-context.d.ts +1 -10
  25. package/dist/esm/types/src/docs/lib/theme.d.ts +5 -1
  26. package/dist/esm/types/src/index.d.ts +5 -0
  27. package/docs/standalone-apps.md +58 -37
  28. package/package.json +3 -2
  29. package/src/assets/sybilion_bg.svg +8 -0
  30. package/src/components/ui/NavUserHeader/NavUserHeader.tsx +10 -2
  31. package/src/components/ui/NavUserHeader/NavUserHeader.types.ts +3 -0
  32. package/src/components/widgets/SignInPage/SignInPage.tsx +84 -0
  33. package/src/components/widgets/SignInPage/index.ts +1 -0
  34. package/src/components/widgets/SybilionAppHeader/SybilionAppHeader.tsx +8 -0
  35. package/src/components/widgets/SybilionAuthLayout/SybilionAuthHeadline.styl +26 -0
  36. package/src/components/widgets/SybilionAuthLayout/SybilionAuthHeadline.styl.d.ts +2 -0
  37. package/src/components/widgets/SybilionAuthLayout/SybilionAuthHeadline.tsx +18 -0
  38. package/src/components/widgets/SybilionAuthLayout/SybilionAuthLayout.styl +79 -0
  39. package/src/components/widgets/SybilionAuthLayout/SybilionAuthLayout.styl.d.ts +2 -0
  40. package/src/components/widgets/SybilionAuthLayout/SybilionAuthLayout.tsx +64 -0
  41. package/src/components/widgets/SybilionAuthLayout/index.ts +6 -0
  42. package/src/components/widgets/SybilionSignInPanel/SybilionSignInPanel.styl +51 -0
  43. package/src/components/widgets/SybilionSignInPanel/SybilionSignInPanel.styl.d.ts +2 -0
  44. package/src/components/widgets/SybilionSignInPanel/SybilionSignInPanel.tsx +59 -0
  45. package/src/components/widgets/SybilionSignInPanel/index.ts +4 -0
  46. package/src/contexts/theme-context.tsx +106 -0
  47. package/src/docs/App/ThemeToggle.tsx +1 -1
  48. package/src/docs/contexts/theme-context.tsx +8 -68
  49. package/src/docs/index.tsx +1 -1
  50. package/src/docs/lib/theme.ts +13 -2
  51. package/src/docs/pages/ChartAreaInteractivePage.tsx +1 -1
  52. package/src/index.ts +5 -0
  53. package/dist/esm/docs/contexts/theme-context.js +0 -14
@@ -1,6 +1,6 @@
1
1
  import cn from 'classnames';
2
2
 
3
- import { useTheme } from '#uilib/docs/contexts/theme-context';
3
+ import { useTheme } from '#uilib/contexts/theme-context';
4
4
  import {
5
5
  MoonIcon,
6
6
  SignOutIcon,
@@ -33,8 +33,16 @@ export function NavUserHeader({
33
33
  onLogout,
34
34
  signInSlot,
35
35
  onSignInClick,
36
+ theme: themeFromHost,
37
+ onThemeToggle: onThemeToggleFromHost,
36
38
  }: NavUserHeaderProps) {
37
- const { toggleTheme, theme } = useTheme();
39
+ const docsTheme = useTheme();
40
+ const hostControlsTheme =
41
+ themeFromHost !== undefined && onThemeToggleFromHost !== undefined;
42
+ const theme = hostControlsTheme ? themeFromHost : docsTheme.theme;
43
+ const toggleTheme = hostControlsTheme
44
+ ? onThemeToggleFromHost
45
+ : docsTheme.toggleTheme;
38
46
  const authenticated = isAuthenticated ?? true;
39
47
 
40
48
  const avatarUrl = user?.avatar ?? '';
@@ -20,4 +20,7 @@ export type NavUserHeaderProps = {
20
20
  /** Replaces default “Log in” control when signed out. */
21
21
  signInSlot?: ReactNode;
22
22
  onSignInClick?: () => void;
23
+ /** When both are set, theme row uses these instead of uilib `ThemeProvider` context. */
24
+ theme?: 'light' | 'dark';
25
+ onThemeToggle?: () => void;
23
26
  };
@@ -0,0 +1,84 @@
1
+ import type { RedirectLoginOptions } from '@auth0/auth0-react';
2
+
3
+ import {
4
+ SybilionAuthLayout,
5
+ type SybilionAuthLayoutProps,
6
+ } from '#uilib/components/widgets/SybilionAuthLayout';
7
+ import { useSybilionAuth } from '#uilib/sybilion-auth/SybilionAuthProvider';
8
+
9
+ import {
10
+ SybilionSignInPanel,
11
+ type SybilionSignInPanelProps,
12
+ } from '../SybilionSignInPanel';
13
+
14
+ export type SignInPageProps = Pick<
15
+ SybilionAuthLayoutProps,
16
+ 'heroBackgroundUrl' | 'logoSize' | 'containerClassName'
17
+ > &
18
+ Pick<
19
+ SybilionSignInPanelProps,
20
+ | 'forgotPasswordTo'
21
+ | 'releasesTo'
22
+ | 'versionLabel'
23
+ | 'primaryButtonLabel'
24
+ | 'connectingLabel'
25
+ > & {
26
+ title?: string;
27
+ subtitle?: string;
28
+ /** Extra Auth0 `loginWithRedirect` options; merged with default sign-in params. */
29
+ loginRedirectOptions?: RedirectLoginOptions;
30
+ };
31
+
32
+ const DEFAULT_TITLE = 'Sign In';
33
+ const DEFAULT_SUBTITLE =
34
+ 'To get access authenticate through google or your email.';
35
+
36
+ export function SignInPage({
37
+ title = DEFAULT_TITLE,
38
+ subtitle = DEFAULT_SUBTITLE,
39
+ forgotPasswordTo = '/forgot-password',
40
+ releasesTo = '/releases',
41
+ versionLabel,
42
+ primaryButtonLabel,
43
+ connectingLabel,
44
+ loginRedirectOptions,
45
+ heroBackgroundUrl,
46
+ logoSize,
47
+ containerClassName,
48
+ }: SignInPageProps) {
49
+ const { error, loginWithRedirect, isLoading } = useSybilionAuth();
50
+
51
+ const handleSignIn = () =>
52
+ loginWithRedirect({
53
+ ...loginRedirectOptions,
54
+ authorizationParams: {
55
+ prompt: 'login',
56
+ screen_hint: 'login',
57
+ ...(typeof window !== 'undefined'
58
+ ? { origin: window.location.origin }
59
+ : {}),
60
+ ...loginRedirectOptions?.authorizationParams,
61
+ },
62
+ });
63
+
64
+ return (
65
+ <SybilionAuthLayout
66
+ title={title}
67
+ subtitle={subtitle}
68
+ heroBackgroundUrl={heroBackgroundUrl}
69
+ logoSize={logoSize}
70
+ containerClassName={containerClassName}
71
+ >
72
+ <SybilionSignInPanel
73
+ onSignIn={handleSignIn}
74
+ isSigningIn={isLoading}
75
+ error={error}
76
+ forgotPasswordTo={forgotPasswordTo}
77
+ releasesTo={releasesTo}
78
+ versionLabel={versionLabel}
79
+ primaryButtonLabel={primaryButtonLabel}
80
+ connectingLabel={connectingLabel}
81
+ />
82
+ </SybilionAuthLayout>
83
+ );
84
+ }
@@ -0,0 +1 @@
1
+ export { SignInPage, type SignInPageProps } from './SignInPage';
@@ -20,6 +20,10 @@ export type SybilionAppHeaderProps = WorkspaceAppSwitcherProps &
20
20
  pageHeaderId?: string;
21
21
  actionsAnchorId?: string;
22
22
  actionsAnchorClassName?: string;
23
+ /** Renders before `NavUserHeader` inside the page actions anchor (e.g. impersonation). */
24
+ actionsStart?: ReactNode;
25
+ /** Renders after `NavUserHeader` inside the page actions anchor (e.g. notifications). */
26
+ actionsEnd?: ReactNode;
23
27
  /** Branded markup; omit for default lucide tile + «Sybilion». */
24
28
  logo?: ReactNode;
25
29
  logoAreaClassName?: string;
@@ -31,6 +35,8 @@ export function SybilionAppHeader({
31
35
  pageHeaderId,
32
36
  actionsAnchorId = PAGE_HEADER_ACTIONS_ID,
33
37
  actionsAnchorClassName,
38
+ actionsStart,
39
+ actionsEnd,
34
40
  pathname,
35
41
  onNavigate,
36
42
  authenticated,
@@ -67,7 +73,9 @@ export function SybilionAppHeader({
67
73
  id={actionsAnchorId}
68
74
  className={cn(S.actionsAnchor, actionsAnchorClassName)}
69
75
  >
76
+ {actionsStart}
70
77
  <NavUserHeader {...navUserHeaderProps} />
78
+ {actionsEnd}
71
79
  </div>
72
80
  </AppHeaderPortal>
73
81
  );
@@ -0,0 +1,26 @@
1
+ .root
2
+ display flex
3
+ flex-direction column
4
+ justify-content center
5
+ align-items center
6
+ width 100%
7
+ padding 64px 48px
8
+ position relative
9
+ z-index 10
10
+ height 100%
11
+ font-family var(--font-family-heading)
12
+ font-weight 300
13
+ text-shadow 0 0 2px var(--background)
14
+
15
+ .headline
16
+ font-size 40px
17
+ line-height 48px
18
+ text-align left
19
+ width 100%
20
+ max-width 480px
21
+
22
+ .headlineParagraph
23
+ margin 0
24
+
25
+ .headlineCyan
26
+ color #27d1ef
@@ -0,0 +1,2 @@
1
+ const mod: { [cls: string]: string };
2
+ export default mod;
@@ -0,0 +1,18 @@
1
+ import S from './SybilionAuthHeadline.styl';
2
+
3
+ export function SybilionAuthHeadline() {
4
+ return (
5
+ <div className={S.root}>
6
+ <div className={S.headline}>
7
+ <p className={S.headlineParagraph}>
8
+ <span>External volatility</span>
9
+ <span></span>
10
+ </p>
11
+ <p className={S.headlineParagraph}>turned into</p>
12
+ <p className={S.headlineParagraph}>
13
+ <span className={S.headlineCyan}>confident decisions</span>
14
+ </p>
15
+ </div>
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,79 @@
1
+ .root
2
+ height 100vh
3
+ display flex
4
+ width 100%
5
+
6
+ .leftPanel
7
+ display none
8
+
9
+ @media (min-width: 768px)
10
+ display flex
11
+ width 50%
12
+ position relative
13
+ overflow hidden
14
+ background-color var(--secondary)
15
+ border-top-left-radius var(--p-6)
16
+ border-bottom-left-radius var(--p-6)
17
+ border-top-right-radius var(--p-1)
18
+ border-bottom-right-radius var(--p-1)
19
+
20
+ :global(.dark) &
21
+ background-color var(--page-color-alpha-800)
22
+
23
+ .bgImage
24
+ 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
32
+
33
+ .logoContainer
34
+ z-index 10
35
+ position absolute
36
+ top 0
37
+ left 0
38
+ display flex
39
+ align-items center
40
+ min-height 94px
41
+ padding 16px 48px
42
+
43
+ .logo
44
+ width 24px
45
+ height 24px
46
+
47
+ .rightPanel
48
+ flex 1
49
+ background-color var(--background)
50
+ display flex
51
+ flex-direction column
52
+ justify-content center
53
+ align-items center
54
+ padding 48px 64px
55
+
56
+ .formContainer
57
+ // position relative
58
+ max-width 480px
59
+ width 100%
60
+
61
+ .header
62
+ margin-bottom 36px
63
+
64
+ .title
65
+ font-family var(--font-family-heading)
66
+ font-weight normal
67
+ font-size 30px
68
+ line-height 42px
69
+ margin 0
70
+ margin-bottom 6px
71
+ color var(--foreground)
72
+
73
+ .subtitle
74
+ font-size 14px
75
+ color var(--muted-foreground)
76
+ line-height 16px
77
+ margin 0
78
+ font-family 'Manrope', sans-serif
79
+ font-weight 500
@@ -0,0 +1,2 @@
1
+ const mod: { [cls: string]: string };
2
+ export default mod;
@@ -0,0 +1,64 @@
1
+ import cn from 'classnames';
2
+ import type { ReactNode } from 'react';
3
+
4
+ import { Logo } from '#uilib/components/ui/Logo/Logo';
5
+ import type { LogoSize } from '#uilib/components/ui/Logo/Logo.types';
6
+
7
+ import { SybilionAuthHeadline } from './SybilionAuthHeadline';
8
+ import S from './SybilionAuthLayout.styl';
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
+ export type SybilionAuthLayoutProps = {
15
+ title: string;
16
+ subtitle?: string;
17
+ children: ReactNode;
18
+ logoSize?: LogoSize;
19
+ containerClassName?: string;
20
+ /** Public URL for the hero watermark SVG (default {@link SYBILION_STANDALONE_AUTH_HERO_BG_PUBLIC_URL}). */
21
+ heroBackgroundUrl?: string;
22
+ };
23
+
24
+ export function SybilionAuthLayout({
25
+ title,
26
+ subtitle,
27
+ children,
28
+ logoSize,
29
+ containerClassName,
30
+ heroBackgroundUrl = SYBILION_STANDALONE_AUTH_HERO_BG_PUBLIC_URL,
31
+ }: SybilionAuthLayoutProps) {
32
+ const bg = heroBackgroundUrl
33
+ ? `url(${JSON.stringify(heroBackgroundUrl)})`
34
+ : 'none';
35
+
36
+ return (
37
+ <div className={S.root}>
38
+ <div className={S.leftPanel}>
39
+ <div
40
+ className={S.bgImage}
41
+ style={{ backgroundImage: bg }}
42
+ aria-hidden
43
+ />
44
+
45
+ <div className={S.logoContainer}>
46
+ <Logo className={S.logo} size={logoSize} />
47
+ </div>
48
+
49
+ <SybilionAuthHeadline />
50
+ </div>
51
+
52
+ <div className={S.rightPanel}>
53
+ <div className={cn(S.formContainer, containerClassName)}>
54
+ <div className={S.header}>
55
+ <h1 className={S.title}>{title}</h1>
56
+ {subtitle && <p className={S.subtitle}>{subtitle}</p>}
57
+ </div>
58
+
59
+ {children}
60
+ </div>
61
+ </div>
62
+ </div>
63
+ );
64
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ SybilionAuthLayout,
3
+ SYBILION_STANDALONE_AUTH_HERO_BG_PUBLIC_URL,
4
+ type SybilionAuthLayoutProps,
5
+ } from './SybilionAuthLayout';
6
+ export { SybilionAuthHeadline } from './SybilionAuthHeadline';
@@ -0,0 +1,51 @@
1
+ .socialButtonContainer
2
+ display flex
3
+ width 100%
4
+ gap 10px
5
+ margin-bottom 10px
6
+
7
+ .socialButton
8
+ flex 1
9
+ height 48px
10
+ padding 14px 16px
11
+ font-size 14px
12
+ font-weight 600
13
+ border-radius 14px
14
+ border 1px solid var(--border)
15
+
16
+ .errorMessage
17
+ color red
18
+ padding 10px
19
+ font-size 14px
20
+ margin-bottom 10px
21
+
22
+ .forgotPassword
23
+ text-align right
24
+ margin-bottom 24px
25
+
26
+ .forgotPasswordLink
27
+ color var(--primary)
28
+ text-decoration none
29
+ font-size 14px
30
+ font-family 'Manrope', sans-serif
31
+ font-weight 500
32
+ transition opacity 0.2s
33
+
34
+ &:hover
35
+ opacity 0.8
36
+
37
+ .version
38
+ position absolute
39
+ bottom 0
40
+ right 0
41
+ display block
42
+ margin-top var(--p-8)
43
+ padding-bottom var(--p-2)
44
+ width 50%
45
+ box-sizing border-box
46
+ text-align center
47
+ font-size 14px
48
+ color var(--muted-foreground)
49
+ opacity 0.5
50
+ transition opacity 0.3s ease-out
51
+ white-space nowrap
@@ -0,0 +1,2 @@
1
+ const mod: { [cls: string]: string };
2
+ export default mod;
@@ -0,0 +1,59 @@
1
+ import { Link } from 'react-router-dom';
2
+
3
+ import { Button } from '#uilib/components/ui/Button';
4
+
5
+ import S from './SybilionSignInPanel.styl';
6
+
7
+ export type SybilionSignInPanelProps = {
8
+ onSignIn: () => void | Promise<void>;
9
+ isSigningIn?: boolean;
10
+ error?: string | null;
11
+ forgotPasswordTo: string;
12
+ releasesTo?: string;
13
+ versionLabel?: string;
14
+ primaryButtonLabel?: string;
15
+ connectingLabel?: string;
16
+ };
17
+
18
+ export function SybilionSignInPanel({
19
+ onSignIn,
20
+ isSigningIn = false,
21
+ error,
22
+ forgotPasswordTo,
23
+ releasesTo,
24
+ versionLabel,
25
+ primaryButtonLabel = 'Sign-in',
26
+ connectingLabel = 'Connecting...',
27
+ }: SybilionSignInPanelProps) {
28
+ const signingIn = Boolean(isSigningIn);
29
+
30
+ return (
31
+ <>
32
+ <div className={S.socialButtonContainer}>
33
+ <Button
34
+ variant="outline"
35
+ type="button"
36
+ className={S.socialButton}
37
+ onClick={() => void onSignIn()}
38
+ disabled={signingIn}
39
+ >
40
+ {signingIn ? connectingLabel : primaryButtonLabel}
41
+ </Button>
42
+ </div>
43
+
44
+ {error ? <div className={S.errorMessage}>{error}</div> : null}
45
+
46
+ <div className={S.forgotPassword}>
47
+ <Link to={forgotPasswordTo} className={S.forgotPasswordLink}>
48
+ Forgot password?
49
+ </Link>
50
+ </div>
51
+
52
+ {releasesTo && versionLabel ? (
53
+ <Link className={S.version} to={releasesTo}>
54
+ v{versionLabel}
55
+ </Link>
56
+ ) : null}
57
+ </>
58
+ );
59
+ }
@@ -0,0 +1,4 @@
1
+ export {
2
+ SybilionSignInPanel,
3
+ type SybilionSignInPanelProps,
4
+ } from './SybilionSignInPanel';
@@ -0,0 +1,106 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ } from 'react';
9
+
10
+ import { Theme as ThemeRoot } from '@homecode/ui';
11
+
12
+ import {
13
+ getThemeConfig as defaultGetThemeConfig,
14
+ type GetThemeConfigOptions,
15
+ } from '#uilib/docs/lib/theme';
16
+
17
+ export type ThemeMode = 'light' | 'dark';
18
+
19
+ export type GetThemeConfigFn = (
20
+ isDarkTheme: boolean,
21
+ options?: GetThemeConfigOptions,
22
+ ) => ReturnType<typeof defaultGetThemeConfig>;
23
+
24
+ export type { GetThemeConfigOptions };
25
+
26
+ const ThemeContext = createContext<{
27
+ theme: ThemeMode;
28
+ isDarkMode: boolean;
29
+ setTheme: (theme: ThemeMode) => void;
30
+ toggleTheme: () => void;
31
+ }>({
32
+ theme: 'light',
33
+ isDarkMode: false,
34
+ setTheme: () => {},
35
+ toggleTheme: () => {},
36
+ });
37
+
38
+ export type ThemeProviderProps = {
39
+ children: React.ReactNode;
40
+ /** When false, DOM classes still update but theme is not persisted to `localStorage`. */
41
+ allowLocalStorage?: boolean;
42
+ /** Override docs default active/accent token (`DEFAULT_THEME_ACTIVE_COLOR`). Passed into `getThemeConfig`. */
43
+ activeColor?: string;
44
+ /** Homecode theme config; defaults to uilib docs palette. */
45
+ getThemeConfig?: GetThemeConfigFn;
46
+ };
47
+
48
+ export function ThemeProvider({
49
+ children,
50
+ allowLocalStorage = true,
51
+ activeColor,
52
+ getThemeConfig: getThemeConfigProp,
53
+ }: ThemeProviderProps) {
54
+ const getThemeConfig = getThemeConfigProp ?? defaultGetThemeConfig;
55
+
56
+ const themeConfigOptions = useMemo<GetThemeConfigOptions>(
57
+ () => ({ activeColor }),
58
+ [activeColor],
59
+ );
60
+
61
+ const [theme, setTheme] = useState<ThemeMode>(() => {
62
+ return (localStorage.getItem('theme') as ThemeMode) || 'light';
63
+ });
64
+
65
+ const [currThemeConfig, setCurrThemeConfig] = useState(() =>
66
+ getThemeConfig(theme === 'dark', themeConfigOptions),
67
+ );
68
+
69
+ const toggleTheme = useCallback(() => {
70
+ setTheme(t => (t === 'dark' ? 'light' : 'dark'));
71
+ }, []);
72
+
73
+ useEffect(() => {
74
+ setCurrThemeConfig(getThemeConfig(theme === 'dark', themeConfigOptions));
75
+ }, [theme, getThemeConfig, themeConfigOptions]);
76
+
77
+ useEffect(() => {
78
+ const root = document.documentElement;
79
+ const effectiveTheme = theme;
80
+
81
+ root.classList.remove('light', 'dark');
82
+ root.classList.add(effectiveTheme);
83
+ if (allowLocalStorage) {
84
+ localStorage.setItem('theme', theme);
85
+ }
86
+ }, [theme, allowLocalStorage]);
87
+
88
+ const value = useMemo(
89
+ () => ({
90
+ theme,
91
+ isDarkMode: theme === 'dark',
92
+ setTheme,
93
+ toggleTheme,
94
+ }),
95
+ [theme, toggleTheme],
96
+ );
97
+
98
+ return (
99
+ <ThemeContext.Provider value={value}>
100
+ <ThemeRoot config={currThemeConfig} />
101
+ {children}
102
+ </ThemeContext.Provider>
103
+ );
104
+ }
105
+
106
+ export const useTheme = () => useContext(ThemeContext);
@@ -1,5 +1,5 @@
1
1
  import { Button } from '#uilib/components/ui/Button';
2
- import { useTheme } from '#uilib/docs/contexts/theme-context';
2
+ import { useTheme } from '#uilib/contexts/theme-context';
3
3
  import { MoonIcon, SunIcon } from '@phosphor-icons/react';
4
4
 
5
5
  export function ThemeToggle() {
@@ -1,68 +1,8 @@
1
- import {
2
- createContext,
3
- useCallback,
4
- useContext,
5
- useEffect,
6
- useState,
7
- } from 'react';
8
-
9
- import { Theme as ThemeRoot } from '@homecode/ui';
10
-
11
- import { getThemeConfig } from '../lib/theme';
12
-
13
- export type ThemeMode = 'light' | 'dark';
14
-
15
- const ThemeContext = createContext<{
16
- theme: ThemeMode;
17
- isDarkMode: boolean;
18
- setTheme: (theme: ThemeMode) => void;
19
- toggleTheme: () => void;
20
- }>({
21
- theme: 'light',
22
- isDarkMode: false,
23
- setTheme: () => {},
24
- toggleTheme: () => {},
25
- });
26
-
27
- export function ThemeProvider({ children }: { children: React.ReactNode }) {
28
- const [theme, setTheme] = useState<ThemeMode>(() => {
29
- return (localStorage.getItem('theme') as ThemeMode) || 'light';
30
- });
31
-
32
- const [currThemeConfig, setCurrThemeConfig] = useState(() =>
33
- getThemeConfig(theme === 'dark'),
34
- );
35
-
36
- const toggleTheme = useCallback(() => {
37
- setTheme(theme === 'dark' ? 'light' : 'dark');
38
- }, [theme, setTheme]);
39
-
40
- useEffect(() => {
41
- setCurrThemeConfig(getThemeConfig(theme === 'dark'));
42
- }, [theme]);
43
-
44
- useEffect(() => {
45
- const root = document.documentElement;
46
- const effectiveTheme = theme;
47
-
48
- root.classList.remove('light', 'dark');
49
- root.classList.add(effectiveTheme);
50
- localStorage.setItem('theme', theme);
51
- }, [theme]);
52
-
53
- return (
54
- <ThemeContext.Provider
55
- value={{
56
- theme,
57
- isDarkMode: theme === 'dark',
58
- setTheme,
59
- toggleTheme,
60
- }}
61
- >
62
- <ThemeRoot config={currThemeConfig} />
63
- {children}
64
- </ThemeContext.Provider>
65
- );
66
- }
67
-
68
- export const useTheme = () => useContext(ThemeContext);
1
+ export {
2
+ ThemeProvider,
3
+ useTheme,
4
+ type GetThemeConfigFn,
5
+ type GetThemeConfigOptions,
6
+ type ThemeMode,
7
+ type ThemeProviderProps,
8
+ } from '#uilib/contexts/theme-context';
@@ -9,7 +9,7 @@ const root = createRoot(elem);
9
9
 
10
10
  root.render(
11
11
  <BrowserRouter>
12
- <ThemeProvider>
12
+ <ThemeProvider activeColor="#">
13
13
  <App />
14
14
  </ThemeProvider>
15
15
  </BrowserRouter>,