Skip to content

Commit eabb977

Browse files
committed
Changes to add support for theme
1 parent 53c9f9d commit eabb977

File tree

8 files changed

+256
-20
lines changed

8 files changed

+256
-20
lines changed

src/frontend/src/components/App.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from "react";
2-
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
32
import { AgentPreview } from "./agents/AgentPreview";
3+
import { ThemeProvider } from "./core/theme/ThemeProvider";
44

55
const App: React.FC = () => {
66
// Sample agent details - in a real application, this would come from an API
@@ -12,14 +12,14 @@ const App: React.FC = () => {
1212
};
1313

1414
return (
15-
<FluentProvider theme={webLightTheme}>
15+
<ThemeProvider>
1616
<div className="app-container">
1717
<AgentPreview
1818
resourceId="sample-resource-id"
1919
agentDetails={mockAgentDetails}
2020
/>
2121
</div>
22-
</FluentProvider>
22+
</ThemeProvider>
2323
);
2424
};
2525

src/frontend/src/components/agents/AgentPreview.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Body1, Button, Caption1, Title2 } from "@fluentui/react-components";
33
import { ChatRegular, MoreHorizontalRegular } from "@fluentui/react-icons";
44

55
import { AgentIcon } from "./AgentIcon";
6-
import { SettingsPanel } from "../core/SettingsPanel";
6+
// import { SettingsPanel } from "../core/SettingsPanel";
77
import { AgentPreviewChatBot } from "./AgentPreviewChatBot";
88
import { MenuButton } from "../core/MenuButton/MenuButton";
99
import { IChatItem } from "./chatbot/types";
@@ -23,13 +23,13 @@ interface IAgentPreviewProps {
2323
}
2424

2525
export function AgentPreview({ agentDetails }: IAgentPreviewProps): ReactNode {
26-
const [isSettingsPanelOpen, setIsSettingsPanelOpen] = useState(false);
26+
// const [isSettingsPanelOpen, setIsSettingsPanelOpen] = useState(false);
2727
const [messageList, setMessageList] = useState<IChatItem[]>([]);
2828
const [isResponding, setIsResponding] = useState(false);
2929

30-
const handleSettingsPanelOpenChange = (isOpen: boolean) => {
31-
setIsSettingsPanelOpen(isOpen);
32-
};
30+
// const handleSettingsPanelOpenChange = (isOpen: boolean) => {
31+
// setIsSettingsPanelOpen(isOpen);
32+
// };
3333

3434
const newThread = () => {
3535
setMessageList([]);
@@ -61,13 +61,13 @@ export function AgentPreview({ agentDetails }: IAgentPreviewProps): ReactNode {
6161
}, 1000);
6262
};
6363
const menuItems = [
64-
{
65-
key: "settings",
66-
children: "Settings",
67-
onClick: () => {
68-
setIsSettingsPanelOpen(true);
69-
},
70-
},
64+
// {
65+
// key: "settings",
66+
// children: "Settings",
67+
// onClick: () => {
68+
// setIsSettingsPanelOpen(true);
69+
// },
70+
// },
7171
{
7272
key: "terms",
7373
children: (
@@ -167,10 +167,10 @@ export function AgentPreview({ agentDetails }: IAgentPreviewProps): ReactNode {
167167
</div>
168168

169169
{/* Settings Panel */}
170-
<SettingsPanel
170+
{/* <SettingsPanel
171171
isOpen={isSettingsPanelOpen}
172172
onOpenChange={handleSettingsPanelOpenChange}
173-
/>
173+
/> */}
174174
</div>
175175
);
176176
}

src/frontend/src/components/core/SettingsPanel.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { Dismiss24Regular } from "@fluentui/react-icons";
1010

1111
import styles from "./SettingsPanel.module.css";
12+
import { ThemePicker } from "./theme/ThemePicker";
1213

1314
export interface ISettingsPanelProps {
1415
isOpen: boolean;
@@ -44,10 +45,9 @@ export function SettingsPanel({
4445
>
4546
Settings
4647
</DrawerHeaderTitle>
47-
</DrawerHeader>
48+
</DrawerHeader>{" "}
4849
<DrawerBody className={styles.content}>
49-
{/* Content will go here */}
50-
<p>Settings panel content</p>
50+
<ThemePicker />
5151
</DrawerBody>
5252
</Drawer>
5353
);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { createContext, useContext } from "react";
2+
import { Theme as FluentTheme } from "@fluentui/react-components";
3+
import { lightTheme } from "./themes";
4+
5+
export type Theme = "Light" | "Dark" | "System";
6+
7+
export interface IThemeContextValue {
8+
theme: Theme;
9+
savedTheme: Theme;
10+
currentTheme: "Light" | "Dark";
11+
themeStyles: FluentTheme;
12+
setTheme: (theme: Theme) => void;
13+
isDarkMode: boolean;
14+
}
15+
16+
export const ThemeContext = createContext<IThemeContextValue>({
17+
theme: "Light",
18+
savedTheme: "Light",
19+
currentTheme: "Light",
20+
themeStyles: lightTheme,
21+
setTheme(theme: Theme) {
22+
console.log(`Theme set to ${theme}`);
23+
},
24+
isDarkMode: false,
25+
});
26+
27+
export function useThemeContext(): IThemeContextValue {
28+
const context = useContext(ThemeContext);
29+
if (!context) {
30+
throw new Error("useThemeContext must be used within a ThemeProvider");
31+
}
32+
return context;
33+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React, { useMemo } from "react";
2+
import { Dropdown, Option, Label } from "@fluentui/react-components";
3+
import { Theme, useThemeContext } from "./ThemeContext";
4+
5+
interface IDropdownItem {
6+
key: Theme;
7+
value: Theme;
8+
text: string;
9+
}
10+
11+
export const ThemePicker: React.FC = () => {
12+
const { savedTheme, setTheme } = useThemeContext();
13+
14+
const options: IDropdownItem[] = useMemo(
15+
() => [
16+
{
17+
key: "Light",
18+
value: "Light",
19+
text: "Light theme",
20+
},
21+
{
22+
key: "Dark",
23+
value: "Dark",
24+
text: "Dark theme",
25+
},
26+
{
27+
key: "System",
28+
value: "System",
29+
text: "System theme",
30+
},
31+
],
32+
[]
33+
);
34+
35+
const selectedThemeText = useMemo(
36+
() =>
37+
options.find((opt) => opt.key === (savedTheme ?? "Light"))?.text ??
38+
"Light theme",
39+
[savedTheme, options]
40+
);
41+
42+
const selectedOptions = useMemo(
43+
() => (savedTheme ? [savedTheme] : []),
44+
[savedTheme]
45+
);
46+
return (
47+
<>
48+
<Label htmlFor="ThemePickerDropdown">Theme</Label>
49+
<Dropdown
50+
id="ThemePickerDropdown"
51+
onOptionSelect={(_, { optionValue }) => {
52+
if (optionValue !== undefined) {
53+
setTheme(optionValue as Theme);
54+
}
55+
}}
56+
selectedOptions={selectedOptions}
57+
value={selectedThemeText}
58+
>
59+
{options.map((option) => (
60+
<Option key={option.key} value={option.value}>
61+
{option.text}
62+
</Option>
63+
))}
64+
</Dropdown>
65+
</>
66+
);
67+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { PropsWithChildren } from "react";
2+
import { FluentProvider } from "@fluentui/react-components";
3+
import { ThemeContext } from "./ThemeContext";
4+
import { useThemeProvider } from "./useThemeProvider";
5+
6+
export function ThemeProvider({ children }: PropsWithChildren): JSX.Element {
7+
const themeContext = useThemeProvider();
8+
9+
return (
10+
<ThemeContext.Provider value={themeContext}>
11+
<FluentProvider theme={themeContext.themeStyles}>
12+
{children}
13+
</FluentProvider>
14+
</ThemeContext.Provider>
15+
);
16+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {
2+
BrandVariants,
3+
createDarkTheme,
4+
createLightTheme,
5+
Theme,
6+
} from "@fluentui/react-components";
7+
8+
// Define our brand colors for the theme
9+
const brandColors: BrandVariants = {
10+
10: "#010306",
11+
20: "#071926",
12+
30: "#002A41",
13+
40: "#003653",
14+
50: "#004365",
15+
60: "#005078",
16+
70: "#005E8B",
17+
80: "#007BB4",
18+
90: "#007BB4",
19+
100: "#008AC9",
20+
110: "#0099DE",
21+
120: "#00A8F4",
22+
130: "#3FB6FF",
23+
140: "#73C3FF",
24+
150: "#98D0FF",
25+
160: "#B8DEFF",
26+
};
27+
28+
export const lightTheme: Theme = {
29+
...createLightTheme(brandColors),
30+
};
31+
32+
export const darkTheme: Theme = {
33+
...createDarkTheme(brandColors),
34+
colorBrandForeground1: brandColors[110],
35+
colorBrandForeground2: brandColors[120],
36+
colorBrandForegroundLink: brandColors[140],
37+
};
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { useCallback, useEffect, useState } from "react";
2+
import { IThemeContextValue, Theme } from "./ThemeContext";
3+
import { darkTheme, lightTheme } from "./themes";
4+
5+
const LOCAL_STORAGE_THEME_KEY = "app-theme-preference";
6+
7+
export function useMediaQuery(query: string): boolean {
8+
const [matches, setMatches] = useState(() => {
9+
if (typeof window !== "undefined") {
10+
return window.matchMedia(query).matches;
11+
}
12+
return false;
13+
});
14+
15+
useEffect(() => {
16+
if (typeof window !== "undefined") {
17+
const mediaQuery = window.matchMedia(query);
18+
const updateMatches = () => {
19+
setMatches(mediaQuery.matches);
20+
};
21+
22+
mediaQuery.addEventListener("change", updateMatches);
23+
24+
updateMatches();
25+
return () => {
26+
mediaQuery.removeEventListener("change", updateMatches);
27+
};
28+
}
29+
return () => {
30+
// Cleanup if needed
31+
};
32+
}, [query]);
33+
34+
return matches;
35+
}
36+
37+
export const useThemeProvider = (): IThemeContextValue => {
38+
const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
39+
40+
const [savedTheme, setSavedTheme] = useState<Theme>(() => {
41+
if (typeof localStorage !== "undefined") {
42+
const storedTheme = localStorage.getItem(
43+
LOCAL_STORAGE_THEME_KEY
44+
) as Theme;
45+
if (storedTheme && ["Light", "Dark", "System"].includes(storedTheme)) {
46+
return storedTheme;
47+
}
48+
}
49+
return "System";
50+
});
51+
52+
const isDarkMode =
53+
savedTheme === "System" ? prefersDark : savedTheme === "Dark";
54+
55+
const currentTheme = isDarkMode ? "Dark" : "Light";
56+
57+
const themeStyles = isDarkMode ? darkTheme : lightTheme;
58+
59+
const setTheme = useCallback((newTheme: Theme) => {
60+
setSavedTheme(newTheme);
61+
if (typeof localStorage !== "undefined") {
62+
localStorage.setItem(LOCAL_STORAGE_THEME_KEY, newTheme);
63+
}
64+
}, []);
65+
66+
useEffect(() => {
67+
// Update document color scheme for browser UI
68+
if (typeof document !== "undefined") {
69+
document.documentElement.style.colorScheme = isDarkMode
70+
? "dark"
71+
: "light";
72+
}
73+
}, [isDarkMode]);
74+
75+
return {
76+
theme: savedTheme,
77+
savedTheme,
78+
currentTheme,
79+
themeStyles,
80+
setTheme,
81+
isDarkMode,
82+
};
83+
};

0 commit comments

Comments
 (0)