@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 +88 -0
- package/package.json +26 -0
- package/src/components/NavbarThemeToggle.tsx +47 -0
- package/src/components/ThemeSynchronizer.tsx +68 -0
- package/src/components/ThemeToggleMenuItem.tsx +69 -0
- package/src/index.tsx +34 -0
- package/tsconfig.json +6 -0
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
|
+
});
|