Skip to content

Commit 1f9c2ed

Browse files
authored
Merge pull request #4 from vipinnair22/vipnai/ThemeChanges
Vipnai/theme changes
2 parents fe50ea9 + ca9f166 commit 1f9c2ed

File tree

9 files changed

+254
-42
lines changed

9 files changed

+254
-42
lines changed

src/api/templates/index_react.html

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,10 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1">
66
<meta name="description" content="">
77
<title>Azure AI Agents Demo (React Version)</title>
8-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
9-
integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
10-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css"
11-
integrity="sha256-4RctOgogjPAdwGbwq+rxfwAmSpZhWaafcZR9btzUk18=" crossorigin="anonymous">
12-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/cosmo/bootstrap.min.css"
13-
integrity="sha256-axRDISYf7Hht1KhcMnfDV2nq7hD/8Q9Rxa0YlW/o3NU=" crossorigin="anonymous">
14-
<link href="/static/styles.css" rel="stylesheet" type="text/css">
158
<link href="/static/react/assets/main-react-app.css " rel="stylesheet" type="text/css">
16-
<style>
17-
.message-content {
18-
text-align: left;
19-
}
20-
</style>
219
</head>
22-
<body>
23-
<div class="container-fluid h-100 d-flex flex-row">
24-
<div class="row flex-grow-1 h-100">
25-
<!-- React App Mount Point -->
26-
<div id="react-root" class="col-full d-flex flex-column h-100"></div>
27-
28-
<!-- Document Viewer Section -->
29-
<div class="col-6 d-flex flex-column h-100 p-3 bg-light border-left" id="document-viewer-section" style="display: none !important;">
30-
<div class="d-flex justify-content-between align-items-center mb-2">
31-
<h5 class="mb-0">Document Viewer</h5>
32-
<button id="close-button" class="btn bi-x"></button>
33-
</div>
34-
<iframe id="document-viewer" class="flex-grow-1 border-0 rounded" style="background-color: white;"></iframe>
35-
</div>
36-
</div>
37-
</div>
38-
10+
<body style="margin: 0;">
11+
<div id="react-root"></div>
3912
<!-- Load React app with fixed filename -->
4013
<script type="module" src="/static/react/assets/main-react-app.js"></script>
4114
</body>

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: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ReactNode, useState } from "react";
1+
import { ReactNode, useState, useMemo } from "react";
22
import { Body1, Button, Caption1, Title2 } from "@fluentui/react-components";
33
import { ChatRegular, MoreHorizontalRegular } from "@fluentui/react-icons";
44

@@ -47,7 +47,7 @@ export function AgentPreview({ agentDetails }: IAgentPreviewProps): ReactNode {
4747
setMessageList((prev) => [...prev, userMessage]);
4848
setIsResponding(true);
4949

50-
// Simulate agent response after a delay
50+
// Simulate agent response after a delay. Can be removed when integrating with a real API.
5151
setTimeout(() => {
5252
const botMessage: IChatItem = {
5353
id: `bot-${Date.now()}`,
@@ -104,11 +104,14 @@ export function AgentPreview({ agentDetails }: IAgentPreviewProps): ReactNode {
104104
},
105105
];
106106

107-
const chatContext = {
108-
messageList,
109-
isResponding,
110-
onSend,
111-
};
107+
const chatContext = useMemo(
108+
() => ({
109+
messageList,
110+
isResponding,
111+
onSend,
112+
}),
113+
[messageList, isResponding]
114+
);
112115

113116
return (
114117
<div className={styles.container}>

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",
20+
},
21+
{
22+
key: "Dark",
23+
value: "Dark",
24+
text: "Dark",
25+
},
26+
{
27+
key: "System",
28+
value: "System",
29+
text: "System",
30+
},
31+
],
32+
[]
33+
);
34+
35+
const selectedThemeText = useMemo(
36+
() =>
37+
options.find((opt) => opt.key === (savedTheme ?? "Light"))?.text ??
38+
"Light",
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 = "get-started-with-agents-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)