@taruvi/navkit 0.0.9 → 0.0.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.
- package/.claude/settings.local.json +4 -1
- package/package.json +18 -5
- package/src/App.styles.ts +71 -1
- package/src/App.tsx +21 -15
- package/src/NavkitContext.tsx +28 -2
- package/src/components/AppLauncher/AppLauncher.styles.ts +63 -1
- package/src/components/AppLauncher/AppLauncher.tsx +10 -9
- package/src/components/Profile/Profile.styles.ts +63 -1
- package/src/components/Profile/Profile.tsx +7 -4
- package/src/components/Profile/ProfileMenu.tsx +10 -9
- package/src/components/Search/Search.styles.ts +31 -0
- package/src/components/Search/Search.tsx +6 -3
- package/src/components/Shortucts/Shortcuts.styles.ts +18 -1
- package/src/components/Shortucts/Shortcuts.tsx +13 -6
- package/src/components/Shortucts/ShortcutsMenu.styles.ts +42 -1
- package/src/components/Shortucts/ShortcutsMenu.tsx +26 -13
- package/src/styles/variables.ts +24 -1
- package/src/types.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taruvi/navkit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"main": "src/App.tsx",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -16,22 +16,35 @@
|
|
|
16
16
|
"@fortawesome/free-regular-svg-icons": ">=6 <7",
|
|
17
17
|
"@fortawesome/free-solid-svg-icons": ">=6 <7",
|
|
18
18
|
"@fortawesome/react-fontawesome": ">=0.2 <1",
|
|
19
|
+
"@mui/icons-material": ">=5 <8",
|
|
20
|
+
"@mui/material": ">=5 <8",
|
|
19
21
|
"@taruvi/sdk": ">=1 <2",
|
|
20
|
-
"react": ">=18 <20"
|
|
21
|
-
"@mui/material": ">=5 <8"
|
|
22
|
+
"react": ">=18 <20"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
25
|
+
"@emotion/react": "^11.14.0",
|
|
26
|
+
"@emotion/styled": "^11.14.1",
|
|
24
27
|
"@eslint/js": "^9.36.0",
|
|
28
|
+
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
|
29
|
+
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
|
30
|
+
"@fortawesome/free-solid-svg-icons": "^7.1.0",
|
|
31
|
+
"@fortawesome/react-fontawesome": "^3.1.1",
|
|
32
|
+
"@mui/material": "^7.3.6",
|
|
33
|
+
"@taruvi/sdk": "^1.1.7",
|
|
25
34
|
"@types/node": "^24.6.0",
|
|
26
35
|
"@types/react": "^19.1.16",
|
|
27
36
|
"@vitejs/plugin-react": "^5.0.4",
|
|
37
|
+
"axios": "^1.13.2",
|
|
28
38
|
"babel-plugin-react-compiler": "^19.1.0-rc.3",
|
|
29
39
|
"eslint": "^9.36.0",
|
|
30
40
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
31
41
|
"eslint-plugin-react-refresh": "^0.4.22",
|
|
32
42
|
"globals": "^16.4.0",
|
|
43
|
+
"react": "^19.2.3",
|
|
44
|
+
"react-dom": "^19.2.3",
|
|
33
45
|
"typescript": "~5.9.3",
|
|
34
46
|
"typescript-eslint": "^8.45.0",
|
|
35
|
-
"vite": "^7.1.7"
|
|
47
|
+
"vite": "^7.1.7",
|
|
48
|
+
"@mui/icons-material": "^7.3.6"
|
|
36
49
|
}
|
|
37
|
-
}
|
|
50
|
+
}
|
package/src/App.styles.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { colours, dimensions } from './styles/variables'
|
|
1
|
+
import { colours, dimensions, getColors } from './styles/variables'
|
|
2
|
+
import type { ThemeMode } from './styles/variables'
|
|
2
3
|
|
|
3
4
|
export const appStyles = {
|
|
4
5
|
appBar: {
|
|
@@ -63,3 +64,72 @@ export const appStyles = {
|
|
|
63
64
|
zIndex: 10,
|
|
64
65
|
},
|
|
65
66
|
}
|
|
67
|
+
|
|
68
|
+
export const getAppStyles = (mode: ThemeMode) => {
|
|
69
|
+
const colors = getColors(mode)
|
|
70
|
+
return {
|
|
71
|
+
appBar: {
|
|
72
|
+
height: dimensions.navHeight,
|
|
73
|
+
},
|
|
74
|
+
toolbar: {
|
|
75
|
+
backgroundColor: colors.bg.white,
|
|
76
|
+
},
|
|
77
|
+
leftSection: {
|
|
78
|
+
display: 'flex',
|
|
79
|
+
flexGrow: 1,
|
|
80
|
+
alignItems: 'center',
|
|
81
|
+
justifyContent: 'start',
|
|
82
|
+
gap: '10px',
|
|
83
|
+
},
|
|
84
|
+
logo: {
|
|
85
|
+
maxHeight: '40px',
|
|
86
|
+
cursor: 'pointer',
|
|
87
|
+
},
|
|
88
|
+
defaultLogo: {
|
|
89
|
+
maxHeight: '22px',
|
|
90
|
+
cursor: 'pointer',
|
|
91
|
+
},
|
|
92
|
+
rightSection: {
|
|
93
|
+
display: 'flex',
|
|
94
|
+
gap: '10px',
|
|
95
|
+
alignItems: 'center',
|
|
96
|
+
},
|
|
97
|
+
modal: {
|
|
98
|
+
display: 'flex',
|
|
99
|
+
alignItems: 'center',
|
|
100
|
+
justifyContent: 'center',
|
|
101
|
+
},
|
|
102
|
+
chatCard: {
|
|
103
|
+
width: '90vw',
|
|
104
|
+
maxWidth: '1200px',
|
|
105
|
+
height: '80vh',
|
|
106
|
+
outline: 'none',
|
|
107
|
+
overflow: 'hidden',
|
|
108
|
+
position: 'relative' as const,
|
|
109
|
+
backgroundColor: colors.bg.white,
|
|
110
|
+
},
|
|
111
|
+
chatHeader: {
|
|
112
|
+
position: 'absolute' as const,
|
|
113
|
+
top: 0,
|
|
114
|
+
right: 0,
|
|
115
|
+
zIndex: 1400,
|
|
116
|
+
padding: '8px',
|
|
117
|
+
},
|
|
118
|
+
chatOpenButton: {
|
|
119
|
+
backgroundColor: colors.bg.white,
|
|
120
|
+
'&:hover': {
|
|
121
|
+
backgroundColor: colors.bg.light,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
iconStyle: {
|
|
125
|
+
cursor: 'pointer',
|
|
126
|
+
fontSize: '20px',
|
|
127
|
+
color: colors.text.secondary,
|
|
128
|
+
},
|
|
129
|
+
backdrop: {
|
|
130
|
+
position: "absolute" as const,
|
|
131
|
+
inset: 0,
|
|
132
|
+
zIndex: 10,
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
}
|
package/src/App.tsx
CHANGED
|
@@ -5,13 +5,14 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
|
|
5
5
|
import { useState } from "react"
|
|
6
6
|
import { AppLauncher, Profile, ProfileMenu, Shortcuts, ShortcutsMenu, MattermostChat } from "./components"
|
|
7
7
|
import { Box, Modal, Card } from "@mui/material"
|
|
8
|
-
import {
|
|
8
|
+
import { getAppStyles } from "./App.styles"
|
|
9
9
|
import { NavkitProvider, useNavigation } from "./NavkitContext"
|
|
10
10
|
import defaultLogo from "./assets/taruvi-logo.png"
|
|
11
11
|
import type { Client } from "@taruvi/sdk"
|
|
12
12
|
|
|
13
13
|
const NavkitContent = () => {
|
|
14
|
-
const { isUserAuthenticated, siteSettings, userData, appsList, jwtToken } = useNavigation()
|
|
14
|
+
const { isUserAuthenticated, siteSettings, userData, appsList, jwtToken, themeMode } = useNavigation()
|
|
15
|
+
const styles = getAppStyles(themeMode)
|
|
15
16
|
const [showAppLauncher, setShowAppLauncher] = useState<boolean>(false)
|
|
16
17
|
const [showProfileMenu, setShowProfileMenu] = useState<boolean>(false)
|
|
17
18
|
const [showShortcutsMenu, setShowShortcutsMenu] = useState<boolean>(false)
|
|
@@ -21,7 +22,7 @@ const NavkitContent = () => {
|
|
|
21
22
|
<>
|
|
22
23
|
{(showAppLauncher || showProfileMenu || showShortcutsMenu) && (
|
|
23
24
|
<div
|
|
24
|
-
style={
|
|
25
|
+
style={styles.backdrop}
|
|
25
26
|
onClick={() => {
|
|
26
27
|
setShowAppLauncher(false)
|
|
27
28
|
setShowProfileMenu(false)
|
|
@@ -29,21 +30,21 @@ const NavkitContent = () => {
|
|
|
29
30
|
}}
|
|
30
31
|
/>
|
|
31
32
|
)}
|
|
32
|
-
<AppBar position="static" sx={
|
|
33
|
-
<Toolbar sx={
|
|
34
|
-
<Box component={"div"} sx={
|
|
33
|
+
<AppBar position="static" sx={styles.appBar}>
|
|
34
|
+
<Toolbar sx={styles.toolbar}>
|
|
35
|
+
<Box component={"div"} sx={styles.leftSection}>
|
|
35
36
|
{isUserAuthenticated && appsList.length > 0 &&
|
|
36
37
|
<IconButton onClick={() => setShowAppLauncher(!showAppLauncher)}>
|
|
37
|
-
<FontAwesomeIcon icon={["fas", "th"]} style={
|
|
38
|
+
<FontAwesomeIcon icon={["fas", "th"]} style={styles.iconStyle} />
|
|
38
39
|
</IconButton>
|
|
39
40
|
}
|
|
40
41
|
<img
|
|
41
|
-
style={(siteSettings?.logo && siteSettings.logo !== '') ?
|
|
42
|
+
style={(siteSettings?.logo && siteSettings.logo !== '') ? styles.logo : styles.defaultLogo}
|
|
42
43
|
src={(siteSettings?.logo && siteSettings.logo !== '') ? siteSettings.logo : defaultLogo}
|
|
43
44
|
onClick={() => window.location.href = siteSettings.frontendUrl || '/desk'}
|
|
44
45
|
/>
|
|
45
46
|
</Box>
|
|
46
|
-
<Box component={'div'} sx={
|
|
47
|
+
<Box component={'div'} sx={styles.rightSection}>
|
|
47
48
|
<Shortcuts
|
|
48
49
|
showChat={setShowChat}
|
|
49
50
|
onMenuToggle={() => setShowShortcutsMenu(!showShortcutsMenu)}
|
|
@@ -77,13 +78,13 @@ const NavkitContent = () => {
|
|
|
77
78
|
<Modal
|
|
78
79
|
open={showChat}
|
|
79
80
|
onClose={() => setShowChat(false)}
|
|
80
|
-
sx={
|
|
81
|
+
sx={styles.modal}
|
|
81
82
|
>
|
|
82
|
-
<Card sx={
|
|
83
|
-
<Box sx={
|
|
83
|
+
<Card sx={styles.chatCard}>
|
|
84
|
+
<Box sx={styles.chatHeader}>
|
|
84
85
|
<IconButton
|
|
85
86
|
onClick={() => window.open(siteSettings.mattermostUrl, '_blank')}
|
|
86
|
-
sx={
|
|
87
|
+
sx={styles.chatOpenButton}
|
|
87
88
|
>
|
|
88
89
|
<FontAwesomeIcon icon={["fas", "external-link-alt"]} style={{ fontSize: '10px' }} />
|
|
89
90
|
</IconButton>
|
|
@@ -101,9 +102,14 @@ const NavkitContent = () => {
|
|
|
101
102
|
)
|
|
102
103
|
}
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
interface NavkitProps {
|
|
106
|
+
client: Client
|
|
107
|
+
getTheme?: (theme: 'light' | 'dark') => void
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const Navkit = ({ client, getTheme }: NavkitProps) => {
|
|
105
111
|
return (
|
|
106
|
-
<NavkitProvider client={client}>
|
|
112
|
+
<NavkitProvider client={client} onThemeChange={getTheme}>
|
|
107
113
|
<NavkitContent />
|
|
108
114
|
</NavkitProvider>
|
|
109
115
|
)
|
package/src/NavkitContext.tsx
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import { createContext, startTransition, useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from 'react'
|
|
2
2
|
import type { AppData, NavigationContextType, NavkitProviderProps, SiteSettings, UserData } from './types'
|
|
3
|
+
import type { ThemeMode } from './styles/variables'
|
|
3
4
|
import { User, Settings, Auth } from "@taruvi/sdk"
|
|
4
5
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
|
5
6
|
import { fas } from '@fortawesome/free-solid-svg-icons'
|
|
6
7
|
import { far } from '@fortawesome/free-regular-svg-icons'
|
|
7
8
|
|
|
9
|
+
const THEME_STORAGE_KEY = 'navkit-theme-mode'
|
|
10
|
+
|
|
11
|
+
const getInitialTheme = (): ThemeMode => {
|
|
12
|
+
if (typeof window !== 'undefined') {
|
|
13
|
+
const stored = localStorage.getItem(THEME_STORAGE_KEY)
|
|
14
|
+
if (stored === 'dark' || stored === 'light') return stored
|
|
15
|
+
if (window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark'
|
|
16
|
+
}
|
|
17
|
+
return 'light'
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
export const NavigationContext = createContext<NavigationContextType | undefined>(undefined)
|
|
9
21
|
|
|
10
|
-
export const NavkitProvider = ({ children, client }: NavkitProviderProps) => {
|
|
22
|
+
export const NavkitProvider = ({ children, client, onThemeChange }: NavkitProviderProps) => {
|
|
11
23
|
const auth = new Auth(client)
|
|
12
24
|
|
|
13
25
|
const user = useRef<any>(null)
|
|
@@ -25,6 +37,15 @@ export const NavkitProvider = ({ children, client }: NavkitProviderProps) => {
|
|
|
25
37
|
const [userData, setUserData] = useState<UserData | null>(null)
|
|
26
38
|
const [jwtToken, setJwtToken] = useState<string>('')
|
|
27
39
|
const [isUserAuthenticated, setIsUserAuthenticated] = useState<boolean>(false)
|
|
40
|
+
const [themeMode, setThemeMode] = useState<ThemeMode>(getInitialTheme)
|
|
41
|
+
|
|
42
|
+
const toggleTheme = useCallback(() => {
|
|
43
|
+
setThemeMode(prev => {
|
|
44
|
+
const newMode = prev === 'light' ? 'dark' : 'light'
|
|
45
|
+
localStorage.setItem(THEME_STORAGE_KEY, newMode)
|
|
46
|
+
return newMode
|
|
47
|
+
})
|
|
48
|
+
}, [])
|
|
28
49
|
|
|
29
50
|
const checkIfDesk = async () => {
|
|
30
51
|
const fetchedSettings = await settings?.current?.get?.()
|
|
@@ -119,8 +140,13 @@ export const NavkitProvider = ({ children, client }: NavkitProviderProps) => {
|
|
|
119
140
|
}
|
|
120
141
|
}, [getData])
|
|
121
142
|
|
|
143
|
+
// Notify parent app of theme changes
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
onThemeChange?.(themeMode)
|
|
146
|
+
}, [themeMode, onThemeChange])
|
|
147
|
+
|
|
122
148
|
return (
|
|
123
|
-
<NavigationContext.Provider value={{ navigateToUrl, isDesk, appsList, userData, siteSettings: siteSettings.current, jwtToken, isUserAuthenticated, client, auth }}>
|
|
149
|
+
<NavigationContext.Provider value={{ navigateToUrl, isDesk, appsList, userData, siteSettings: siteSettings.current, jwtToken, isUserAuthenticated, client, auth, themeMode, toggleTheme }}>
|
|
124
150
|
{children}
|
|
125
151
|
</NavigationContext.Provider>
|
|
126
152
|
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { colours, spacing, shadows, borderRadius, typography, zIndex, dimensions } from '../../styles/variables'
|
|
1
|
+
import { colours, spacing, shadows, borderRadius, typography, zIndex, dimensions, getColors } from '../../styles/variables'
|
|
2
|
+
import type { ThemeMode } from '../../styles/variables'
|
|
2
3
|
|
|
3
4
|
export const appLauncherStyles = {
|
|
4
5
|
container: {
|
|
@@ -57,3 +58,64 @@ export const appLauncherStyles = {
|
|
|
57
58
|
letterSpacing: typography.letterSpacing.default,
|
|
58
59
|
},
|
|
59
60
|
}
|
|
61
|
+
|
|
62
|
+
export const getAppLauncherStyles = (mode: ThemeMode) => {
|
|
63
|
+
const colors = getColors(mode)
|
|
64
|
+
return {
|
|
65
|
+
container: {
|
|
66
|
+
position: 'absolute' as const,
|
|
67
|
+
top: 60,
|
|
68
|
+
left: { xs: '2.5vw', sm: '10vw', md: '15vw', lg: 40 },
|
|
69
|
+
width: { xs: '95vw', sm: '80vw', md: '70vw', lg: '560px' },
|
|
70
|
+
maxWidth: '800px',
|
|
71
|
+
maxHeight: { xs: 'calc(100vh - 80px)', md: '335px' },
|
|
72
|
+
borderRadius: borderRadius.default,
|
|
73
|
+
boxShadow: shadows.dropdown,
|
|
74
|
+
p: spacing.lg,
|
|
75
|
+
backgroundColor: colors.bg.white,
|
|
76
|
+
zIndex: zIndex.overlay,
|
|
77
|
+
},
|
|
78
|
+
scrollContainer: {
|
|
79
|
+
mt: spacing.md,
|
|
80
|
+
maxHeight: { xs: '300px', md: '220px' },
|
|
81
|
+
overflowY: 'auto' as const,
|
|
82
|
+
},
|
|
83
|
+
appItem: {
|
|
84
|
+
display: 'flex',
|
|
85
|
+
flexDirection: 'column' as const,
|
|
86
|
+
alignItems: 'center',
|
|
87
|
+
justifyContent: 'center',
|
|
88
|
+
p: spacing.xs,
|
|
89
|
+
borderRadius: borderRadius.default,
|
|
90
|
+
cursor: 'pointer',
|
|
91
|
+
transition: 'all 0.2s ease',
|
|
92
|
+
'&:hover': {
|
|
93
|
+
backgroundColor: colors.bg.light,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
iconContainer: {
|
|
97
|
+
width: 48,
|
|
98
|
+
height: 48,
|
|
99
|
+
display: 'flex',
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
justifyContent: 'center',
|
|
102
|
+
borderRadius: borderRadius.default,
|
|
103
|
+
border: `1px solid ${colors.border.light}`,
|
|
104
|
+
mb: spacing.sm,
|
|
105
|
+
},
|
|
106
|
+
iconStyle: {
|
|
107
|
+
fontSize: dimensions.iconSize.md,
|
|
108
|
+
color: colors.text.secondary,
|
|
109
|
+
},
|
|
110
|
+
appName: {
|
|
111
|
+
fontWeight: typography.weights.regular,
|
|
112
|
+
color: colors.text.secondary,
|
|
113
|
+
fontSize: typography.sizes.xs,
|
|
114
|
+
textAlign: 'center' as const,
|
|
115
|
+
lineHeight: 1.3,
|
|
116
|
+
maxWidth: '100%',
|
|
117
|
+
wordWrap: 'break-word' as const,
|
|
118
|
+
letterSpacing: typography.letterSpacing.default,
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -5,11 +5,12 @@ import { Card, Box, Grid, Typography } from "@mui/material"
|
|
|
5
5
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
|
6
6
|
import type { IconName } from "@fortawesome/fontawesome-svg-core"
|
|
7
7
|
import { useNavigation } from "../../NavkitContext"
|
|
8
|
-
import {
|
|
8
|
+
import { getAppLauncherStyles } from "./AppLauncher.styles"
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
const AppLauncher = () => {
|
|
12
|
-
const { navigateToUrl, appsList } = useNavigation()
|
|
12
|
+
const { navigateToUrl, appsList, themeMode } = useNavigation()
|
|
13
|
+
const styles = getAppLauncherStyles(themeMode)
|
|
13
14
|
const [filteredApps, setFilteredApps] = useState<AppData[]>(appsList || [])
|
|
14
15
|
|
|
15
16
|
const handleAppClick = (app: AppData) => {
|
|
@@ -25,29 +26,29 @@ const AppLauncher = () => {
|
|
|
25
26
|
}, [appsList])
|
|
26
27
|
|
|
27
28
|
return (
|
|
28
|
-
<Card sx={
|
|
29
|
+
<Card sx={styles.container}>
|
|
29
30
|
<Search appsList={appsList} onSearchChange={handleSearchChange} />
|
|
30
31
|
{filteredApps && filteredApps.length > 0 && (
|
|
31
|
-
<Box sx={
|
|
32
|
+
<Box sx={styles.scrollContainer}>
|
|
32
33
|
<Grid container spacing={1}>
|
|
33
34
|
{filteredApps.map((app) => {
|
|
34
35
|
return (
|
|
35
36
|
<Grid item xs={3} key={app.id}>
|
|
36
37
|
<Box
|
|
37
|
-
sx={
|
|
38
|
+
sx={styles.appItem}
|
|
38
39
|
onClick={() => handleAppClick(app)}
|
|
39
40
|
>
|
|
40
41
|
<Box
|
|
41
|
-
sx={
|
|
42
|
+
sx={styles.iconContainer}
|
|
42
43
|
>
|
|
43
44
|
<FontAwesomeIcon
|
|
44
|
-
icon={["
|
|
45
|
-
style={
|
|
45
|
+
icon={["fas", app.icon.replace('fa-', '') as IconName]}
|
|
46
|
+
style={styles.iconStyle}
|
|
46
47
|
/>
|
|
47
48
|
</Box>
|
|
48
49
|
<Typography
|
|
49
50
|
variant="body2"
|
|
50
|
-
sx={
|
|
51
|
+
sx={styles.appName}
|
|
51
52
|
>
|
|
52
53
|
{app.appname}
|
|
53
54
|
</Typography>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { colours, spacing, shadows, borderRadius, typography, zIndex, dimensions } from '../../styles/variables'
|
|
1
|
+
import { colours, spacing, shadows, borderRadius, typography, zIndex, dimensions, getColors } from '../../styles/variables'
|
|
2
|
+
import type { ThemeMode } from '../../styles/variables'
|
|
2
3
|
|
|
3
4
|
export const profileStyles = {
|
|
4
5
|
container: {
|
|
@@ -57,3 +58,64 @@ export const profileStyles = {
|
|
|
57
58
|
color: colours.text.secondary,
|
|
58
59
|
},
|
|
59
60
|
}
|
|
61
|
+
|
|
62
|
+
export const getProfileStyles = (mode: ThemeMode) => {
|
|
63
|
+
const colors = getColors(mode)
|
|
64
|
+
return {
|
|
65
|
+
container: {
|
|
66
|
+
position: 'relative' as const,
|
|
67
|
+
},
|
|
68
|
+
trigger: {
|
|
69
|
+
display: 'flex',
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
gap: spacing.sm,
|
|
72
|
+
ml: spacing.md,
|
|
73
|
+
cursor: 'pointer',
|
|
74
|
+
},
|
|
75
|
+
avatar: {
|
|
76
|
+
width: dimensions.avatarSize,
|
|
77
|
+
height: dimensions.avatarSize,
|
|
78
|
+
bgcolor: colors.bg.avatar,
|
|
79
|
+
color: colors.text.primary,
|
|
80
|
+
fontWeight: typography.weights.semibold,
|
|
81
|
+
fontSize: typography.sizes.sm,
|
|
82
|
+
},
|
|
83
|
+
name: {
|
|
84
|
+
color: colors.text.primary,
|
|
85
|
+
fontSize: typography.sizes.sm,
|
|
86
|
+
fontWeight: typography.weights.regular,
|
|
87
|
+
letterSpacing: typography.letterSpacing.default,
|
|
88
|
+
display: { xs: 'none', md: 'block' },
|
|
89
|
+
},
|
|
90
|
+
menu: {
|
|
91
|
+
position: 'absolute' as const,
|
|
92
|
+
top: 60,
|
|
93
|
+
right: 0,
|
|
94
|
+
width: 200,
|
|
95
|
+
borderRadius: borderRadius.default,
|
|
96
|
+
boxShadow: shadows.dropdown,
|
|
97
|
+
backgroundColor: colors.bg.white,
|
|
98
|
+
zIndex: zIndex.overlay,
|
|
99
|
+
py: 1,
|
|
100
|
+
},
|
|
101
|
+
menuItem: {
|
|
102
|
+
display: 'flex',
|
|
103
|
+
alignItems: 'center',
|
|
104
|
+
gap: spacing.md,
|
|
105
|
+
px: spacing.md,
|
|
106
|
+
py: spacing.sm,
|
|
107
|
+
'&:hover': {
|
|
108
|
+
backgroundColor: colors.bg.light,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
menuItemText: {
|
|
112
|
+
color: colors.text.secondary,
|
|
113
|
+
fontSize: typography.sizes.sm,
|
|
114
|
+
fontWeight: typography.weights.regular,
|
|
115
|
+
},
|
|
116
|
+
iconStyle: {
|
|
117
|
+
fontSize: dimensions.iconSize.sm,
|
|
118
|
+
color: colors.text.secondary,
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Box, Avatar, Typography } from '@mui/material'
|
|
2
|
-
import {
|
|
2
|
+
import { getProfileStyles } from './Profile.styles'
|
|
3
|
+
import { useNavigation } from '../../NavkitContext'
|
|
3
4
|
import nopfp from "../../assets/nopfp.png"
|
|
4
5
|
|
|
5
6
|
interface UserData {
|
|
@@ -18,15 +19,17 @@ interface ProfileProps {
|
|
|
18
19
|
|
|
19
20
|
const Profile = (props: ProfileProps) => {
|
|
20
21
|
const { userData, onClick } = props
|
|
22
|
+
const { themeMode } = useNavigation()
|
|
23
|
+
const styles = getProfileStyles(themeMode)
|
|
21
24
|
|
|
22
25
|
return (
|
|
23
|
-
<Box sx={
|
|
26
|
+
<Box sx={styles.trigger} onClick={onClick}>
|
|
24
27
|
<Avatar
|
|
25
28
|
src={userData.pfp || nopfp}
|
|
26
|
-
sx={
|
|
29
|
+
sx={styles.avatar}
|
|
27
30
|
>
|
|
28
31
|
</Avatar>
|
|
29
|
-
<Typography sx={
|
|
32
|
+
<Typography sx={styles.name}>
|
|
30
33
|
{userData.first_name} {userData.last_name}
|
|
31
34
|
</Typography>
|
|
32
35
|
</Box>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Card, MenuItem, Typography } from '@mui/material'
|
|
2
2
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
3
|
-
import {
|
|
3
|
+
import { getProfileStyles } from './Profile.styles'
|
|
4
4
|
import { useNavigation } from '../../NavkitContext'
|
|
5
5
|
|
|
6
6
|
const ProfileMenu = () => {
|
|
7
|
-
const { navigateToUrl, auth } = useNavigation()
|
|
7
|
+
const { navigateToUrl, auth, themeMode } = useNavigation()
|
|
8
|
+
const styles = getProfileStyles(themeMode)
|
|
8
9
|
|
|
9
10
|
const handlePreferences = () => {
|
|
10
11
|
navigateToUrl('preferences', true)
|
|
@@ -15,23 +16,23 @@ const ProfileMenu = () => {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
return (
|
|
18
|
-
<Card sx={
|
|
19
|
-
<MenuItem onClick={handlePreferences} sx={
|
|
19
|
+
<Card sx={styles.menu}>
|
|
20
|
+
<MenuItem onClick={handlePreferences} sx={styles.menuItem}>
|
|
20
21
|
<FontAwesomeIcon
|
|
21
22
|
icon={["fas", "gear"]}
|
|
22
|
-
style={
|
|
23
|
+
style={styles.iconStyle}
|
|
23
24
|
/>
|
|
24
|
-
<Typography sx={
|
|
25
|
+
<Typography sx={styles.menuItemText}>
|
|
25
26
|
Preferences
|
|
26
27
|
</Typography>
|
|
27
28
|
</MenuItem>
|
|
28
29
|
|
|
29
|
-
<MenuItem onClick={handleLogout} sx={
|
|
30
|
+
<MenuItem onClick={handleLogout} sx={styles.menuItem}>
|
|
30
31
|
<FontAwesomeIcon
|
|
31
32
|
icon={["fas", "right-from-bracket"]}
|
|
32
|
-
style={
|
|
33
|
+
style={styles.iconStyle}
|
|
33
34
|
/>
|
|
34
|
-
<Typography sx={
|
|
35
|
+
<Typography sx={styles.menuItemText}>
|
|
35
36
|
Logout
|
|
36
37
|
</Typography>
|
|
37
38
|
</MenuItem>
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { getColors } from '../../styles/variables'
|
|
2
|
+
import type { ThemeMode } from '../../styles/variables'
|
|
3
|
+
|
|
1
4
|
export const searchStyles = {
|
|
2
5
|
textField: {
|
|
3
6
|
'& .MuiOutlinedInput-root': {
|
|
@@ -5,3 +8,31 @@ export const searchStyles = {
|
|
|
5
8
|
},
|
|
6
9
|
},
|
|
7
10
|
}
|
|
11
|
+
|
|
12
|
+
export const getSearchStyles = (mode: ThemeMode) => {
|
|
13
|
+
const colors = getColors(mode)
|
|
14
|
+
return {
|
|
15
|
+
textField: {
|
|
16
|
+
'& .MuiOutlinedInput-root': {
|
|
17
|
+
height: '40px',
|
|
18
|
+
backgroundColor: colors.bg.light,
|
|
19
|
+
'& fieldset': {
|
|
20
|
+
borderColor: colors.border.light,
|
|
21
|
+
},
|
|
22
|
+
'&:hover fieldset': {
|
|
23
|
+
borderColor: colors.text.tertiary,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
'& .MuiOutlinedInput-input': {
|
|
27
|
+
color: colors.text.primary,
|
|
28
|
+
},
|
|
29
|
+
'& .MuiInputBase-input::placeholder': {
|
|
30
|
+
color: colors.text.tertiary,
|
|
31
|
+
opacity: 1,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
iconStyle: {
|
|
35
|
+
color: colors.text.tertiary,
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
2
2
|
import { TextField, InputAdornment } from '@mui/material'
|
|
3
3
|
import { useState } from 'react'
|
|
4
|
-
import {
|
|
4
|
+
import { getSearchStyles } from './Search.styles'
|
|
5
|
+
import { useNavigation } from '../../NavkitContext'
|
|
5
6
|
import type { AppData } from '../../types'
|
|
6
7
|
|
|
7
8
|
interface SearchProps {
|
|
@@ -11,6 +12,8 @@ interface SearchProps {
|
|
|
11
12
|
|
|
12
13
|
const Search = ({ appsList, onSearchChange }: SearchProps) => {
|
|
13
14
|
const [searchTerm, setSearchTerm] = useState('')
|
|
15
|
+
const { themeMode } = useNavigation()
|
|
16
|
+
const styles = getSearchStyles(themeMode)
|
|
14
17
|
|
|
15
18
|
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
16
19
|
const value = event.target.value
|
|
@@ -36,11 +39,11 @@ const Search = ({ appsList, onSearchChange }: SearchProps) => {
|
|
|
36
39
|
variant="outlined"
|
|
37
40
|
value={searchTerm}
|
|
38
41
|
onChange={handleSearchChange}
|
|
39
|
-
sx={
|
|
42
|
+
sx={styles.textField}
|
|
40
43
|
InputProps={{
|
|
41
44
|
startAdornment: (
|
|
42
45
|
<InputAdornment position="start">
|
|
43
|
-
<FontAwesomeIcon icon={["fas", "magnifying-glass"]} style={
|
|
46
|
+
<FontAwesomeIcon icon={["fas", "magnifying-glass"]} style={styles.iconStyle} />
|
|
44
47
|
</InputAdornment>
|
|
45
48
|
),
|
|
46
49
|
}}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { colours, spacing, dimensions } from '../../styles/variables'
|
|
1
|
+
import { colours, spacing, dimensions, getColors } from '../../styles/variables'
|
|
2
|
+
import type { ThemeMode } from '../../styles/variables'
|
|
2
3
|
|
|
3
4
|
export const shortcutsStyles = {
|
|
4
5
|
desktopContainer: {
|
|
@@ -27,3 +28,19 @@ export const shortcutsStyles = {
|
|
|
27
28
|
color: colours.text.secondary,
|
|
28
29
|
},
|
|
29
30
|
}
|
|
31
|
+
|
|
32
|
+
export const getThemeToggleStyles = (mode: ThemeMode) => {
|
|
33
|
+
const colors = getColors(mode)
|
|
34
|
+
return {
|
|
35
|
+
button: {
|
|
36
|
+
color: colors.text.secondary,
|
|
37
|
+
'&:hover': {
|
|
38
|
+
backgroundColor: colors.bg.light,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
icon: {
|
|
42
|
+
fontSize: dimensions.iconSize.lg,
|
|
43
|
+
color: colors.text.secondary,
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { AppData } from '../../types'
|
|
2
2
|
import { Box, Link, IconButton } from '@mui/material'
|
|
3
3
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
4
|
-
import
|
|
4
|
+
import DarkModeOutlined from '@mui/icons-material/DarkModeOutlined'
|
|
5
|
+
import LightModeOutlined from '@mui/icons-material/LightModeOutlined'
|
|
6
|
+
import { shortcutsStyles, getThemeToggleStyles } from './Shortcuts.styles'
|
|
5
7
|
import { useNavigation } from '../../NavkitContext'
|
|
6
8
|
|
|
7
9
|
interface ShortcutsProps {
|
|
@@ -13,18 +15,16 @@ interface ShortcutsProps {
|
|
|
13
15
|
const Shortcuts = (props: ShortcutsProps) => {
|
|
14
16
|
|
|
15
17
|
const { showChat, onMenuToggle, triggerRef } = props
|
|
16
|
-
const { navigateToUrl, siteSettings, isUserAuthenticated } = useNavigation()
|
|
18
|
+
const { navigateToUrl, siteSettings, isUserAuthenticated, themeMode, toggleTheme } = useNavigation()
|
|
17
19
|
const shortcuts = siteSettings.shortcuts
|
|
20
|
+
const themeToggleStyles = getThemeToggleStyles(themeMode)
|
|
18
21
|
|
|
19
22
|
const handleShortcutClick = (shortcut: AppData) => {
|
|
20
23
|
if (shortcut.url) {
|
|
21
24
|
navigateToUrl(shortcut.url, false)
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
|
-
|
|
25
|
-
return null
|
|
26
|
-
}
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
return (
|
|
29
29
|
<>
|
|
30
30
|
{/* Desktop view - inline shortcuts */}
|
|
@@ -52,6 +52,13 @@ const Shortcuts = (props: ShortcutsProps) => {
|
|
|
52
52
|
/>
|
|
53
53
|
</Link>
|
|
54
54
|
}
|
|
55
|
+
<IconButton onClick={toggleTheme} sx={themeToggleStyles.button}>
|
|
56
|
+
{themeMode === 'dark' ? (
|
|
57
|
+
<LightModeOutlined sx={themeToggleStyles.icon} />
|
|
58
|
+
) : (
|
|
59
|
+
<DarkModeOutlined sx={themeToggleStyles.icon} />
|
|
60
|
+
)}
|
|
61
|
+
</IconButton>
|
|
55
62
|
</Box>
|
|
56
63
|
|
|
57
64
|
{/* Mobile view - trigger button */}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { colours, spacing, shadows, borderRadius, typography, zIndex, dimensions } from '../../styles/variables'
|
|
1
|
+
import { colours, spacing, shadows, borderRadius, typography, zIndex, dimensions, getColors } from '../../styles/variables'
|
|
2
|
+
import type { ThemeMode } from '../../styles/variables'
|
|
2
3
|
|
|
3
4
|
export const shortcutsMenuStyles = {
|
|
4
5
|
menu: {
|
|
@@ -32,3 +33,43 @@ export const shortcutsMenuStyles = {
|
|
|
32
33
|
color: colours.text.secondary,
|
|
33
34
|
},
|
|
34
35
|
}
|
|
36
|
+
|
|
37
|
+
export const getShortcutsMenuStyles = (mode: ThemeMode) => {
|
|
38
|
+
const colors = getColors(mode)
|
|
39
|
+
return {
|
|
40
|
+
menu: {
|
|
41
|
+
position: 'absolute' as const,
|
|
42
|
+
top: 60,
|
|
43
|
+
right: 0,
|
|
44
|
+
width: 200,
|
|
45
|
+
borderRadius: borderRadius.default,
|
|
46
|
+
boxShadow: shadows.dropdown,
|
|
47
|
+
backgroundColor: colors.bg.white,
|
|
48
|
+
zIndex: zIndex.overlay,
|
|
49
|
+
py: 1,
|
|
50
|
+
},
|
|
51
|
+
menuItem: {
|
|
52
|
+
display: 'flex',
|
|
53
|
+
alignItems: 'center',
|
|
54
|
+
gap: spacing.md,
|
|
55
|
+
px: spacing.md,
|
|
56
|
+
py: spacing.sm,
|
|
57
|
+
'&:hover': {
|
|
58
|
+
backgroundColor: colors.bg.light,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
menuItemText: {
|
|
62
|
+
color: colors.text.secondary,
|
|
63
|
+
fontSize: typography.sizes.sm,
|
|
64
|
+
fontWeight: typography.weights.regular,
|
|
65
|
+
},
|
|
66
|
+
iconStyle: {
|
|
67
|
+
fontSize: dimensions.iconSize.sm,
|
|
68
|
+
color: colors.text.secondary,
|
|
69
|
+
},
|
|
70
|
+
themeIcon: {
|
|
71
|
+
fontSize: dimensions.iconSize.sm,
|
|
72
|
+
color: colors.text.secondary,
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Card, MenuItem, Typography } from '@mui/material'
|
|
2
2
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
3
|
+
import DarkModeOutlined from '@mui/icons-material/DarkModeOutlined'
|
|
4
|
+
import LightModeOutlined from '@mui/icons-material/LightModeOutlined'
|
|
3
5
|
import type { AppData } from '../../types'
|
|
4
|
-
import {
|
|
6
|
+
import { getShortcutsMenuStyles } from './ShortcutsMenu.styles'
|
|
5
7
|
import { useNavigation } from '../../NavkitContext'
|
|
6
8
|
|
|
7
9
|
interface ShortcutsMenuProps {
|
|
@@ -10,8 +12,9 @@ interface ShortcutsMenuProps {
|
|
|
10
12
|
|
|
11
13
|
const ShortcutsMenu = (props: ShortcutsMenuProps) => {
|
|
12
14
|
const { showChat } = props
|
|
13
|
-
const { navigateToUrl, siteSettings, isUserAuthenticated } = useNavigation()
|
|
15
|
+
const { navigateToUrl, siteSettings, isUserAuthenticated, themeMode, toggleTheme } = useNavigation()
|
|
14
16
|
const shortcuts = siteSettings.shortcuts
|
|
17
|
+
const themedStyles = getShortcutsMenuStyles(themeMode)
|
|
15
18
|
|
|
16
19
|
const handleShortcutClick = (shortcut: AppData) => {
|
|
17
20
|
if (shortcut.url) {
|
|
@@ -19,23 +22,19 @@ const ShortcutsMenu = (props: ShortcutsMenuProps) => {
|
|
|
19
22
|
}
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
if ((!shortcuts || shortcuts.length === 0) && !siteSettings.showChat) {
|
|
23
|
-
return null
|
|
24
|
-
}
|
|
25
|
-
|
|
26
25
|
return (
|
|
27
|
-
<Card sx={
|
|
26
|
+
<Card sx={themedStyles.menu}>
|
|
28
27
|
{siteSettings.showChat && isUserAuthenticated && (
|
|
29
28
|
<MenuItem
|
|
30
29
|
key="chat"
|
|
31
30
|
onClick={() => showChat(true)}
|
|
32
|
-
sx={
|
|
31
|
+
sx={themedStyles.menuItem}
|
|
33
32
|
>
|
|
34
33
|
<FontAwesomeIcon
|
|
35
34
|
icon={["far", "comment"] as any}
|
|
36
|
-
style={
|
|
35
|
+
style={themedStyles.iconStyle}
|
|
37
36
|
/>
|
|
38
|
-
<Typography sx={
|
|
37
|
+
<Typography sx={themedStyles.menuItemText}>
|
|
39
38
|
Chat
|
|
40
39
|
</Typography>
|
|
41
40
|
</MenuItem>
|
|
@@ -44,17 +43,31 @@ const ShortcutsMenu = (props: ShortcutsMenuProps) => {
|
|
|
44
43
|
<MenuItem
|
|
45
44
|
key={shortcut.id}
|
|
46
45
|
onClick={() => handleShortcutClick(shortcut)}
|
|
47
|
-
sx={
|
|
46
|
+
sx={themedStyles.menuItem}
|
|
48
47
|
>
|
|
49
48
|
<FontAwesomeIcon
|
|
50
49
|
icon={["far", shortcut.icon] as any}
|
|
51
|
-
style={
|
|
50
|
+
style={themedStyles.iconStyle}
|
|
52
51
|
/>
|
|
53
|
-
<Typography sx={
|
|
52
|
+
<Typography sx={themedStyles.menuItemText}>
|
|
54
53
|
{shortcut.appname}
|
|
55
54
|
</Typography>
|
|
56
55
|
</MenuItem>
|
|
57
56
|
))}
|
|
57
|
+
<MenuItem
|
|
58
|
+
key="theme-toggle"
|
|
59
|
+
onClick={toggleTheme}
|
|
60
|
+
sx={themedStyles.menuItem}
|
|
61
|
+
>
|
|
62
|
+
{themeMode === 'dark' ? (
|
|
63
|
+
<LightModeOutlined sx={themedStyles.themeIcon} />
|
|
64
|
+
) : (
|
|
65
|
+
<DarkModeOutlined sx={themedStyles.themeIcon} />
|
|
66
|
+
)}
|
|
67
|
+
<Typography sx={themedStyles.menuItemText}>
|
|
68
|
+
{themeMode === 'dark' ? 'Light Mode' : 'Dark Mode'}
|
|
69
|
+
</Typography>
|
|
70
|
+
</MenuItem>
|
|
58
71
|
</Card>
|
|
59
72
|
)
|
|
60
73
|
}
|
package/src/styles/variables.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Design Tokens - Centralized design values for consistent theming
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export type ThemeMode = 'light' | 'dark'
|
|
4
|
+
|
|
5
|
+
export const lightColors = {
|
|
4
6
|
text: {
|
|
5
7
|
primary: '#333333',
|
|
6
8
|
secondary: '#424242',
|
|
@@ -16,6 +18,27 @@ export const colours = {
|
|
|
16
18
|
},
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
export const darkColors = {
|
|
22
|
+
text: {
|
|
23
|
+
primary: '#f5f5f5',
|
|
24
|
+
secondary: '#e0e0e0',
|
|
25
|
+
tertiary: '#9e9e9e',
|
|
26
|
+
},
|
|
27
|
+
bg: {
|
|
28
|
+
white: '#1e1e1e',
|
|
29
|
+
light: '#2d2d2d',
|
|
30
|
+
avatar: '#424242',
|
|
31
|
+
},
|
|
32
|
+
border: {
|
|
33
|
+
light: '#424242',
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const getColors = (mode: ThemeMode) => mode === 'dark' ? darkColors : lightColors
|
|
38
|
+
|
|
39
|
+
// Default colors (light mode) for backwards compatibility
|
|
40
|
+
export const colours = lightColors
|
|
41
|
+
|
|
19
42
|
export const spacing = {
|
|
20
43
|
xs: '10px',
|
|
21
44
|
sm: 1.5,
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react"
|
|
2
2
|
import type { Client, Auth } from "@taruvi/sdk"
|
|
3
|
+
import type { ThemeMode } from "./styles/variables"
|
|
3
4
|
|
|
4
5
|
export interface AppData {
|
|
5
6
|
appname: string,
|
|
@@ -33,9 +34,12 @@ export interface NavigationContextType {
|
|
|
33
34
|
isUserAuthenticated: boolean
|
|
34
35
|
client: Client
|
|
35
36
|
auth: Auth
|
|
37
|
+
themeMode: ThemeMode
|
|
38
|
+
toggleTheme: () => void
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
export interface NavkitProviderProps {
|
|
39
42
|
children: ReactNode
|
|
40
43
|
client: Client
|
|
44
|
+
onThemeChange?: (theme: ThemeMode) => void
|
|
41
45
|
}
|