@checkstack/theme-frontend 0.0.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,88 @@
1
+ # @checkstack/theme-frontend
2
+
3
+ ## 0.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - d20d274: Initial release of all @checkstack packages. Rebranded from Checkmate to Checkstack with new npm organization @checkstack and domain checkstack.dev.
8
+ - Updated dependencies [d20d274]
9
+ - @checkstack/auth-frontend@0.0.2
10
+ - @checkstack/common@0.0.2
11
+ - @checkstack/frontend-api@0.0.2
12
+ - @checkstack/theme-common@0.0.2
13
+ - @checkstack/ui@0.0.2
14
+
15
+ ## 0.1.4
16
+
17
+ ### Patch Changes
18
+
19
+ - ae33df2: Move command palette from dashboard to centered navbar position
20
+
21
+ - Converted `command-frontend` into a plugin with `NavbarCenterSlot` extension
22
+ - Added compact `NavbarSearch` component with responsive search trigger
23
+ - Moved `SearchDialog` from dashboard-frontend to command-frontend
24
+ - Keyboard shortcut (⌘K / Ctrl+K) now works on every page
25
+ - Renamed navbar slots for clarity:
26
+ - `NavbarSlot` → `NavbarRightSlot`
27
+ - `NavbarMainSlot` → `NavbarLeftSlot`
28
+ - Added new `NavbarCenterSlot` for centered content
29
+
30
+ - Updated dependencies [52231ef]
31
+ - Updated dependencies [b0124ef]
32
+ - Updated dependencies [54cc787]
33
+ - Updated dependencies [a65e002]
34
+ - Updated dependencies [ae33df2]
35
+ - Updated dependencies [a65e002]
36
+ - Updated dependencies [32ea706]
37
+ - @checkstack/auth-frontend@0.3.0
38
+ - @checkstack/ui@0.1.2
39
+ - @checkstack/common@0.2.0
40
+ - @checkstack/frontend-api@0.1.0
41
+ - @checkstack/theme-common@0.0.3
42
+
43
+ ## 0.1.3
44
+
45
+ ### Patch Changes
46
+
47
+ - Updated dependencies [1bf71bb]
48
+ - @checkstack/auth-frontend@0.2.1
49
+
50
+ ## 0.1.2
51
+
52
+ ### Patch Changes
53
+
54
+ - Updated dependencies [e26c08e]
55
+ - @checkstack/auth-frontend@0.2.0
56
+
57
+ ## 0.1.1
58
+
59
+ ### Patch Changes
60
+
61
+ - Updated dependencies [0f8cc7d]
62
+ - @checkstack/frontend-api@0.0.3
63
+ - @checkstack/auth-frontend@0.1.1
64
+ - @checkstack/ui@0.1.1
65
+
66
+ ## 0.1.0
67
+
68
+ ### Minor Changes
69
+
70
+ - d673ab4: Add theme persistence for non-logged-in users via local storage
71
+
72
+ - Added `NavbarThemeToggle` component that shows a Sun/Moon button in the navbar for non-logged-in users
73
+ - Added `ThemeSynchronizer` component that loads theme from backend for logged-in users on page load
74
+ - Theme is now applied immediately on page load for logged-in users (no need to open user menu first)
75
+ - Non-logged-in users can now toggle theme, which persists in local storage
76
+ - Logged-in user's backend-saved theme takes precedence over local storage
77
+
78
+ ### Patch Changes
79
+
80
+ - Updated dependencies [eff5b4e]
81
+ - Updated dependencies [ffc28f6]
82
+ - Updated dependencies [32f2535]
83
+ - Updated dependencies [b354ab3]
84
+ - @checkstack/ui@0.1.0
85
+ - @checkstack/common@0.1.0
86
+ - @checkstack/auth-frontend@0.1.0
87
+ - @checkstack/frontend-api@0.0.2
88
+ - @checkstack/theme-common@0.0.2
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@checkstack/theme-frontend",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "main": "src/index.tsx",
6
+ "scripts": {
7
+ "typecheck": "tsc --noEmit",
8
+ "lint": "bun run lint:code",
9
+ "lint:code": "eslint . --max-warnings 0"
10
+ },
11
+ "dependencies": {
12
+ "@checkstack/theme-common": "workspace:*",
13
+ "@checkstack/frontend-api": "workspace:*",
14
+ "@checkstack/auth-frontend": "workspace:*",
15
+ "@checkstack/common": "workspace:*",
16
+ "@checkstack/ui": "workspace:*",
17
+ "react": "^18.2.0",
18
+ "lucide-react": "^0.344.0"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "^5.0.0",
22
+ "@types/react": "^18.2.0",
23
+ "@checkstack/tsconfig": "workspace:*",
24
+ "@checkstack/scripts": "workspace:*"
25
+ }
26
+ }
@@ -0,0 +1,47 @@
1
+ import { Moon, Sun } from "lucide-react";
2
+ import { useApi } from "@checkstack/frontend-api";
3
+ import { authApiRef } from "@checkstack/auth-frontend/api";
4
+ import { Button, useTheme } from "@checkstack/ui";
5
+
6
+ /**
7
+ * Navbar theme toggle button for non-logged-in users.
8
+ *
9
+ * Shows a Sun/Moon icon button that toggles between light and dark themes.
10
+ * Only renders when user is NOT logged in (logged-in users use the toggle in UserMenu).
11
+ *
12
+ * Theme changes are saved to local storage via ThemeProvider.
13
+ */
14
+ export const NavbarThemeToggle = () => {
15
+ const { theme, setTheme } = useTheme();
16
+ const authApi = useApi(authApiRef);
17
+ const { data: session, isPending } = authApi.useSession();
18
+
19
+ // Don't render while loading session
20
+ if (isPending) {
21
+ return;
22
+ }
23
+
24
+ // Don't render for logged-in users (they use UserMenu toggle)
25
+ if (session?.user) {
26
+ return;
27
+ }
28
+
29
+ const isDark = theme === "dark";
30
+
31
+ const handleToggle = () => {
32
+ const newTheme = isDark ? "light" : "dark";
33
+ setTheme(newTheme);
34
+ };
35
+
36
+ return (
37
+ <Button
38
+ variant="ghost"
39
+ size="icon"
40
+ onClick={handleToggle}
41
+ aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"}
42
+ className="rounded-full"
43
+ >
44
+ {isDark ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
45
+ </Button>
46
+ );
47
+ };
@@ -0,0 +1,68 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { useApi, rpcApiRef } from "@checkstack/frontend-api";
3
+ import { authApiRef } from "@checkstack/auth-frontend/api";
4
+ import { useTheme } from "@checkstack/ui";
5
+ import { ThemeApi } from "@checkstack/theme-common";
6
+
7
+ /**
8
+ * Headless component that synchronizes theme on app initialization.
9
+ *
10
+ * - For logged-in users: fetches theme from backend and applies it
11
+ * - For non-logged-in users: theme is already read from local storage by ThemeProvider
12
+ * - Also syncs backend theme to local storage for continuity when logging out
13
+ *
14
+ * Must be rendered early in the app (e.g., via NavbarRightSlot) to ensure theme
15
+ * is applied before the user sees the page.
16
+ */
17
+ export const ThemeSynchronizer = () => {
18
+ const { setTheme } = useTheme();
19
+ const authApi = useApi(authApiRef);
20
+ const rpcApi = useApi(rpcApiRef);
21
+ const themeClient = rpcApi.forPlugin(ThemeApi);
22
+ const { data: session, isPending } = authApi.useSession();
23
+
24
+ // Track if we've already synced for this session to avoid repeated API calls
25
+ const hasSyncedRef = useRef(false);
26
+ const lastUserIdRef = useRef<string | undefined>(undefined);
27
+
28
+ useEffect(() => {
29
+ // Wait for session to load
30
+ if (isPending) {
31
+ return;
32
+ }
33
+
34
+ const currentUserId = session?.user?.id ?? undefined;
35
+
36
+ // Reset sync state when user changes (login/logout)
37
+ if (currentUserId !== lastUserIdRef.current) {
38
+ hasSyncedRef.current = false;
39
+ lastUserIdRef.current = currentUserId;
40
+ }
41
+
42
+ // Only sync once per session
43
+ if (hasSyncedRef.current) {
44
+ return;
45
+ }
46
+
47
+ // For logged-in users, fetch theme from backend
48
+ if (session?.user) {
49
+ themeClient
50
+ .getTheme()
51
+ .then(({ theme }) => {
52
+ setTheme(theme);
53
+ hasSyncedRef.current = true;
54
+ })
55
+ .catch((error) => {
56
+ console.error("Failed to sync theme from backend:", error);
57
+ hasSyncedRef.current = true; // Still mark as synced to prevent retry loops
58
+ });
59
+ } else {
60
+ // For non-logged-in users, local storage theme is already applied
61
+ // by ThemeProvider, so nothing to do
62
+ hasSyncedRef.current = true;
63
+ }
64
+ }, [session, isPending, setTheme, themeClient]);
65
+
66
+ // Headless component - renders nothing
67
+ return <></>;
68
+ };
@@ -0,0 +1,69 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Moon, Sun } from "lucide-react";
3
+ import { Toggle, useTheme, useToast } from "@checkstack/ui";
4
+ import { useApi, rpcApiRef } from "@checkstack/frontend-api";
5
+ import { ThemeApi } from "@checkstack/theme-common";
6
+
7
+ /**
8
+ * Theme toggle menu item for logged-in users (displayed in UserMenu).
9
+ *
10
+ * Saves theme to both backend (for persistence across devices) and
11
+ * local storage (for continuity when logging out).
12
+ *
13
+ * Theme initialization is handled by ThemeSynchronizer component.
14
+ */
15
+ export const ThemeToggleMenuItem = () => {
16
+ const { theme, setTheme } = useTheme();
17
+ const rpcApi = useApi(rpcApiRef);
18
+ const themeClient = rpcApi.forPlugin(ThemeApi);
19
+
20
+ const [saving, setSaving] = useState(false);
21
+ const [isDark, setIsDark] = useState(theme === "dark");
22
+ const toast = useToast();
23
+
24
+ // Update local state when theme changes (e.g., from ThemeSynchronizer)
25
+ useEffect(() => {
26
+ setIsDark(theme === "dark");
27
+ }, [theme]);
28
+
29
+ const handleToggle = async (checked: boolean) => {
30
+ const newTheme = checked ? "dark" : "light";
31
+
32
+ // Update UI immediately
33
+ setIsDark(checked);
34
+ setTheme(newTheme); // Also updates local storage via ThemeProvider
35
+
36
+ // Save to backend
37
+ setSaving(true);
38
+ try {
39
+ await themeClient.setTheme({ theme: newTheme });
40
+ } catch (error) {
41
+ const message =
42
+ error instanceof Error
43
+ ? error.message
44
+ : "Failed to save theme preference";
45
+ toast.error(message);
46
+ console.error("Failed to save theme preference:", error);
47
+ // Revert on error
48
+ setIsDark(!checked);
49
+ setTheme(checked ? "light" : "dark");
50
+ } finally {
51
+ setSaving(false);
52
+ }
53
+ };
54
+
55
+ return (
56
+ <div className="flex items-center justify-between w-full px-4 py-2 text-sm text-popover-foreground">
57
+ <div className="flex items-center gap-2">
58
+ {isDark ? <Moon className="h-4 w-4" /> : <Sun className="h-4 w-4" />}
59
+ <span>Dark Mode</span>
60
+ </div>
61
+ <Toggle
62
+ checked={isDark}
63
+ onCheckedChange={handleToggle}
64
+ disabled={saving}
65
+ aria-label="Toggle dark mode"
66
+ />
67
+ </div>
68
+ );
69
+ };
package/src/index.tsx ADDED
@@ -0,0 +1,34 @@
1
+ import {
2
+ createFrontendPlugin,
3
+ NavbarRightSlot,
4
+ UserMenuItemsBottomSlot,
5
+ } from "@checkstack/frontend-api";
6
+ import { pluginMetadata } from "@checkstack/theme-common";
7
+ import { ThemeToggleMenuItem } from "./components/ThemeToggleMenuItem";
8
+ import { ThemeSynchronizer } from "./components/ThemeSynchronizer";
9
+ import { NavbarThemeToggle } from "./components/NavbarThemeToggle";
10
+
11
+ export const themePlugin = createFrontendPlugin({
12
+ metadata: pluginMetadata,
13
+ routes: [],
14
+ extensions: [
15
+ // Theme toggle in user menu (for logged-in users)
16
+ {
17
+ id: "theme.user-menu.toggle",
18
+ slot: UserMenuItemsBottomSlot,
19
+ component: ThemeToggleMenuItem,
20
+ },
21
+ // Theme synchronizer - headless component that syncs theme from backend on load
22
+ {
23
+ id: "theme.navbar.synchronizer",
24
+ slot: NavbarRightSlot,
25
+ component: ThemeSynchronizer,
26
+ },
27
+ // Theme toggle button in navbar (for non-logged-in users)
28
+ {
29
+ id: "theme.navbar.toggle",
30
+ slot: NavbarRightSlot,
31
+ component: NavbarThemeToggle,
32
+ },
33
+ ],
34
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "@checkstack/tsconfig/frontend.json",
3
+ "include": [
4
+ "src"
5
+ ]
6
+ }