Updated theme on sidebar

This commit is contained in:
Sakshi Gupta 2025-04-18 14:27:19 +05:30
parent f2d6bbf54e
commit b5976fb034
4 changed files with 103 additions and 33 deletions

View File

@ -8,9 +8,8 @@ import {
MenuList, MenuList,
MenuPopover, MenuPopover,
MenuTrigger, MenuTrigger,
mergeClasses,
shorthands, shorthands,
SplitButton, SplitButton
} from "@fluentui/react-components"; } from "@fluentui/react-components";
import { Add16Regular, ArrowSync12Regular, ChevronLeft12Regular, ChevronRight12Regular } from "@fluentui/react-icons"; import { Add16Regular, ArrowSync12Regular, ChevronLeft12Regular, ChevronRight12Regular } from "@fluentui/react-icons";
import { configContext, Platform } from "ConfigContext"; import { configContext, Platform } from "ConfigContext";
@ -18,7 +17,6 @@ import Explorer from "Explorer/Explorer";
import { AddDatabasePanel } from "Explorer/Panes/AddDatabasePanel/AddDatabasePanel"; import { AddDatabasePanel } from "Explorer/Panes/AddDatabasePanel/AddDatabasePanel";
import { Tabs } from "Explorer/Tabs/Tabs"; import { Tabs } from "Explorer/Tabs/Tabs";
import { CosmosFluentProvider, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil"; import { CosmosFluentProvider, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil";
import { ResourceTree } from "Explorer/Tree/ResourceTree";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { KeyboardAction, KeyboardActionGroup, KeyboardActionHandler, useKeyboardActionGroup } from "KeyboardShortcuts"; import { KeyboardAction, KeyboardActionGroup, KeyboardActionHandler, useKeyboardActionGroup } from "KeyboardShortcuts";
import { isFabric, isFabricMirrored, isFabricNative } from "Platform/Fabric/FabricUtil"; import { isFabric, isFabricMirrored, isFabricNative } from "Platform/Fabric/FabricUtil";
@ -26,8 +24,10 @@ import { userContext } from "UserContext";
import { getCollectionName, getDatabaseName } from "Utils/APITypeUtils"; import { getCollectionName, getDatabaseName } from "Utils/APITypeUtils";
import { Allotment, AllotmentHandle } from "allotment"; import { Allotment, AllotmentHandle } from "allotment";
import { useSidePanel } from "hooks/useSidePanel"; import { useSidePanel } from "hooks/useSidePanel";
import { useTheme } from "hooks/useTheme";
import { debounce } from "lodash"; import { debounce } from "lodash";
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { ResourceTree } from "./Tree/ResourceTree";
const useSidebarStyles = makeStyles({ const useSidebarStyles = makeStyles({
sidebar: { sidebar: {
@ -35,38 +35,67 @@ const useSidebarStyles = makeStyles({
}, },
sidebarContainer: { sidebarContainer: {
height: "100%", height: "100%",
width: "100%",
borderRight: `1px solid ${tokens.colorNeutralStroke1}`,
transition: "all 0.2s ease-in-out",
display: "flex",
flexDirection: "column",
backgroundColor: tokens.colorNeutralBackground1, backgroundColor: tokens.colorNeutralBackground1,
position: "relative",
}, },
expandedContent: { expandedContent: {
display: "grid", display: "grid",
height: "100%", height: "100%",
width: "100%",
gridTemplateRows: `calc(${tokens.layoutRowHeight} * 2) 1fr`, gridTemplateRows: `calc(${tokens.layoutRowHeight} * 2) 1fr`,
}, },
floatingControlsContainer: { floatingControlsContainer: {
position: "relative", position: "absolute",
top: 0,
right: 0,
zIndex: 1000, zIndex: 1000,
width: "100%", width: "auto",
padding: tokens.spacingHorizontalS,
}, },
floatingControls: { floatingControls: {
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
position: "absolute", gap: tokens.spacingHorizontalXS,
right: 0,
}, },
floatingControlButton: { floatingControlButton: {
...shorthands.border("none"), ...shorthands.border("none"),
backgroundColor: "transparent", backgroundColor: "transparent",
color: tokens.colorNeutralForeground1,
cursor: "pointer",
padding: tokens.spacingHorizontalXS,
borderRadius: tokens.borderRadiusMedium,
display: "flex",
alignItems: "center",
justifyContent: "center",
":hover": {
backgroundColor: tokens.colorNeutralBackground1Hover,
color: tokens.colorNeutralForeground1,
},
":active": {
backgroundColor: tokens.colorNeutralBackground1Pressed,
color: tokens.colorNeutralForeground1,
},
":disabled": {
color: tokens.colorNeutralForegroundDisabled,
cursor: "not-allowed",
},
}, },
globalCommandsContainer: { globalCommandsContainer: {
display: "grid", display: "grid",
alignItems: "center", alignItems: "center",
justifyItems: "center", justifyItems: "center",
width: "100%", width: "100%",
containerType: "size", // Use this container for "@container" queries below this. containerType: "size",
padding: tokens.spacingHorizontalS,
...cosmosShorthands.borderBottom(), ...cosmosShorthands.borderBottom(),
backgroundColor: tokens.colorNeutralBackground1,
}, },
loadingProgressBar: { loadingProgressBar: {
// Float above the content
position: "absolute", position: "absolute",
width: "100%", width: "100%",
height: "2px", height: "2px",
@ -76,7 +105,7 @@ const useSidebarStyles = makeStyles({
animationDuration: "3s", animationDuration: "3s",
animationName: { animationName: {
"0%": { "0%": {
opacity: ".2", // matches indeterminate bar width opacity: ".2",
}, },
"50%": { "50%": {
opacity: "1", opacity: "1",
@ -98,6 +127,13 @@ const useSidebarStyles = makeStyles({
display: "flex", display: "flex",
}, },
}, },
treeContainer: {
flex: 1,
overflow: "auto",
paddingLeft: tokens.spacingHorizontalM,
backgroundColor: tokens.colorNeutralBackground1,
color: tokens.colorNeutralForeground1,
},
}); });
interface GlobalCommandsProps { interface GlobalCommandsProps {
@ -250,6 +286,7 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
const [expandedSize, setExpandedSize] = React.useState(300); const [expandedSize, setExpandedSize] = React.useState(300);
const hasSidebar = userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo"; const hasSidebar = userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo";
const allotment = useRef<AllotmentHandle>(null); const allotment = useRef<AllotmentHandle>(null);
const { isDarkMode } = useTheme();
const expand = useCallback(() => { const expand = useCallback(() => {
if (!expanded) { if (!expanded) {
@ -304,7 +341,7 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
{hasSidebar && ( {hasSidebar && (
// When collapsed, we force the pane to 24 pixels wide and make it non-resizable. // When collapsed, we force the pane to 24 pixels wide and make it non-resizable.
<Allotment.Pane minSize={24} preferredSize={250}> <Allotment.Pane minSize={24} preferredSize={250}>
<CosmosFluentProvider className={mergeClasses(styles.sidebar)}> <CosmosFluentProvider>
<div className={styles.sidebarContainer}> <div className={styles.sidebarContainer}>
{loading && ( {loading && (
// The Fluent UI progress bar has some issues in reduced-motion environments so we use a simple CSS animation here. // The Fluent UI progress bar has some issues in reduced-motion environments so we use a simple CSS animation here.
@ -335,13 +372,12 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
</button> </button>
</div> </div>
</div> </div>
<div <div className={styles.expandedContent} style={!hasGlobalCommands ? { gridTemplateRows: "1fr" } : undefined}>
className={styles.expandedContent}
style={!hasGlobalCommands ? { gridTemplateRows: "1fr" } : undefined}
>
{hasGlobalCommands && <GlobalCommands explorer={explorer} />} {hasGlobalCommands && <GlobalCommands explorer={explorer} />}
<div className={styles.treeContainer}>
<ResourceTree explorer={explorer} /> <ResourceTree explorer={explorer} />
</div> </div>
</div>
</> </>
) : ( ) : (
<button <button

View File

@ -4,16 +4,19 @@ import {
FluentProvider, FluentProvider,
FluentProviderSlots, FluentProviderSlots,
Theme, Theme,
createDarkTheme,
createLightTheme, createLightTheme,
makeStyles, makeStyles,
mergeClasses, mergeClasses,
shorthands, shorthands,
themeToTokensObject, themeToTokensObject,
webLightTheme, webDarkTheme,
webLightTheme
} from "@fluentui/react-components"; } from "@fluentui/react-components";
import { Platform, configContext } from "ConfigContext"; import { Platform, configContext } from "ConfigContext";
import React from "react"; import React from "react";
import { appThemeFabricTealBrandRamp } from "../../Platform/Fabric/FabricTheme"; import { appThemeFabricTealBrandRamp } from "../../Platform/Fabric/FabricTheme";
import { useTheme } from "../../hooks/useTheme";
export const LayoutConstants = { export const LayoutConstants = {
rowHeight: 32, rowHeight: 32,
@ -47,6 +50,7 @@ export const CosmosFluentProvider: React.FC<CosmosFluentProviderProps> = ({ chil
// As we convert components to Fluent UI 9, if we end up with nested FluentProviders, the inner FluentProvider will be a no-op. // As we convert components to Fluent UI 9, if we end up with nested FluentProviders, the inner FluentProvider will be a no-op.
const { isInFluentProvider } = React.useContext(FluentProviderContext); const { isInFluentProvider } = React.useContext(FluentProviderContext);
const styles = useDefaultRootStyles(); const styles = useDefaultRootStyles();
const { isDarkMode } = useTheme();
if (isInFluentProvider) { if (isInFluentProvider) {
// We're already in a fluent context, don't create another. // We're already in a fluent context, don't create another.
@ -61,7 +65,7 @@ export const CosmosFluentProvider: React.FC<CosmosFluentProviderProps> = ({ chil
return ( return (
<FluentProviderContext.Provider value={{ isInFluentProvider: true }}> <FluentProviderContext.Provider value={{ isInFluentProvider: true }}>
<FluentProvider <FluentProvider
theme={getPlatformTheme(configContext.platform)} theme={getPlatformTheme(configContext.platform, isDarkMode)}
className={mergeClasses(styles.fluentProvider, className)} className={mergeClasses(styles.fluentProvider, className)}
{...props} {...props}
> >
@ -114,7 +118,16 @@ const cosmosTheme = {
sidebarInitialWidth: "300px", sidebarInitialWidth: "300px",
}; };
export const tokens = themeToTokensObject({ ...webLightTheme, ...cosmosTheme, ...sizeMappings[LayoutSize.Compact] }); // Get the current theme tokens based on the root theme
export const getThemeTokens = (isDarkMode: boolean) =>
themeToTokensObject({
...(isDarkMode ? webDarkTheme : webLightTheme),
...cosmosTheme,
...sizeMappings[LayoutSize.Compact]
});
// Initialize with light theme, will be updated by the provider
export const tokens = getThemeTokens(false);
export const cosmosShorthands = { export const cosmosShorthands = {
border: () => shorthands.border("1px", "solid", tokens.colorNeutralStroke2), border: () => shorthands.border("1px", "solid", tokens.colorNeutralStroke2),
@ -124,11 +137,12 @@ export const cosmosShorthands = {
borderLeft: () => shorthands.borderLeft("1px", "solid", tokens.colorNeutralStroke2), borderLeft: () => shorthands.borderLeft("1px", "solid", tokens.colorNeutralStroke2),
}; };
export function getPlatformTheme(platform: Platform): CosmosTheme { export function getPlatformTheme(platform: Platform, isDarkMode: boolean = false): CosmosTheme {
const createTheme = isDarkMode ? createDarkTheme : createLightTheme;
const baseTheme = const baseTheme =
platform === Platform.Fabric platform === Platform.Fabric
? createLightTheme(appThemeFabricTealBrandRamp) ? createTheme(appThemeFabricTealBrandRamp)
: createLightTheme(appThemePortalBrandRamp); : createTheme(appThemePortalBrandRamp);
return { return {
...baseTheme, ...baseTheme,

View File

@ -11,7 +11,7 @@ import { SQLQuickstartTutorial } from "Explorer/Quickstart/Tutorials/SQLQuicksta
import "allotment/dist/style.css"; import "allotment/dist/style.css";
import "bootstrap/dist/css/bootstrap.css"; import "bootstrap/dist/css/bootstrap.css";
import { useCarousel } from "hooks/useCarousel"; import { useCarousel } from "hooks/useCarousel";
import React from "react"; import React, { useEffect } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import "../externals/jquery-ui.min.css"; import "../externals/jquery-ui.min.css";
import "../externals/jquery-ui.min.js"; import "../externals/jquery-ui.min.js";
@ -82,7 +82,6 @@ const App = (): JSX.Element => {
const isCopilotCarouselOpen = useCarousel((state) => state.showCopilotCarousel); const isCopilotCarouselOpen = useCarousel((state) => state.showCopilotCarousel);
const styles = useStyles(); const styles = useStyles();
console.log("App - Current theme: Dark");
if (config?.platform === Platform.Fabric) { if (config?.platform === Platform.Fabric) {
loadTheme(appThemeFabric); loadTheme(appThemeFabric);
@ -140,13 +139,22 @@ const Root: React.FC = () => {
// Force dark theme // Force dark theme
const isDarkMode = true; const isDarkMode = true;
const currentTheme = isDarkMode ? webDarkTheme : webLightTheme; const currentTheme = isDarkMode ? webDarkTheme : webLightTheme;
const theme = "Dark";
console.log("Root component - Theme state:", { // Apply theme to body for Fluent UI v8 components
isDarkMode, useEffect(() => {
currentTheme, if (isDarkMode) {
theme document.body.classList.add("isDarkMode");
}); loadTheme(appThemeFabric);
} else {
document.body.classList.remove("isDarkMode");
loadTheme(appThemeFabric);
}
}, [isDarkMode]);
// console.log("Root component - Theme state:", {
// isDarkMode,
// currentTheme
// });
return ( return (
<ErrorBoundary> <ErrorBoundary>

View File

@ -20,15 +20,27 @@ export const CustomThemeProvider: FC<ThemeProviderProps> = ({ children, theme })
export const useTheme = () => { export const useTheme = () => {
const { targetDocument } = useFluent(); const { targetDocument } = useFluent();
const context = React.useContext(ThemeContext);
const [isDarkMode, setIsDarkMode] = useState(() => { const [isDarkMode, setIsDarkMode] = useState(() => {
const hasDarkMode = targetDocument?.body.classList.contains("isDarkMode") ?? true; // First check if we're in a theme context
return hasDarkMode; if (context) {
return context.isDarkMode;
}
// Fallback to checking body class
return targetDocument?.body.classList.contains("isDarkMode") ?? true;
}); });
useEffect(() => { useEffect(() => {
if (!targetDocument) return; if (!targetDocument) return;
const checkTheme = () => { const checkTheme = () => {
// First check if we're in a theme context
if (context) {
setIsDarkMode(context.isDarkMode);
return;
}
// Fallback to checking body class
const hasDarkMode = targetDocument.body.classList.contains("isDarkMode"); const hasDarkMode = targetDocument.body.classList.contains("isDarkMode");
setIsDarkMode(hasDarkMode); setIsDarkMode(hasDarkMode);
}; };
@ -43,7 +55,7 @@ export const useTheme = () => {
observer.observe(targetDocument.body, { attributes: true, attributeFilter: ["class"] }); observer.observe(targetDocument.body, { attributes: true, attributeFilter: ["class"] });
return () => observer.disconnect(); return () => observer.disconnect();
}, [targetDocument]); }, [targetDocument, context]);
return { return {
isDarkMode isDarkMode