diff --git a/packages/code-studio/src/styleguide/constants.ts b/packages/code-studio/src/styleguide/constants.ts
new file mode 100644
index 0000000000..af5b342f11
--- /dev/null
+++ b/packages/code-studio/src/styleguide/constants.ts
@@ -0,0 +1,3 @@
+export const MENU_CATEGORY_DATA_ATTRIBUTE = 'data-menu-category';
+export const NO_MENU_DATA_ATTRIBUTE = 'data-no-menu';
+export const SPECTRUM_COMPONENT_SAMPLES_ID = 'spectrum-component-samples';
diff --git a/packages/components/src/SpectrumThemeDark.module.scss b/packages/components/src/SpectrumThemeDark.module.scss
deleted file mode 100644
index 2def9236d9..0000000000
--- a/packages/components/src/SpectrumThemeDark.module.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- This module contains overrides of React Spectrum css variables for the default
- `dark` Deephaven theme.
-*/
-@use '../scss/bootstrap_overrides' as bootstrap;
-@use '../scss/util' as *;
-
-// Doubling specificity to ensure this takes precedence over default Spectrum
-// styles.
-#{multiply-specificity-n('.dh-spectrum-theme--dark', 2)} {
- --spectrum-alias-background-color-default: #{bootstrap.$interfaceblack};
-}
diff --git a/packages/components/src/SpectrumThemeLight.module.scss b/packages/components/src/SpectrumThemeLight.module.scss
deleted file mode 100644
index 037ec68732..0000000000
--- a/packages/components/src/SpectrumThemeLight.module.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- This module contains overrides of React Spectrum css variables for the default
- `light` Deephaven theme.
-*/
-@use '../scss/bootstrap_overrides' as bootstrap;
-@use '../scss/util' as *;
-
-// Doubling specificity to ensure this takes precedence over default Spectrum
-// styles.
-#{multiply-specificity-n('.dh-spectrum-theme--light', 2)} {
- --spectrum-alias-background-color-default: #{bootstrap.$interfacewhite};
-}
diff --git a/packages/components/src/SpectrumUtils.test.ts b/packages/components/src/SpectrumUtils.test.ts
index c1e40405db..7f5ce71337 100644
--- a/packages/components/src/SpectrumUtils.test.ts
+++ b/packages/components/src/SpectrumUtils.test.ts
@@ -1,22 +1,7 @@
-import { theme } from '@react-spectrum/theme-default';
import { themeDHDefault } from './SpectrumUtils';
describe('themeDHDefault', () => {
it('should merge Spectrum default with DH custom styles', () => {
- const { global, light, dark, medium, large } = theme;
-
- expect(themeDHDefault).toEqual({
- global,
- light: {
- ...light,
- 'dh-spectrum-theme--light': 'mock.light',
- },
- dark: {
- ...dark,
- 'dh-spectrum-theme--dark': 'mock.dark',
- },
- medium,
- large,
- });
+ expect(themeDHDefault).toMatchSnapshot();
});
});
diff --git a/packages/components/src/SpectrumUtils.ts b/packages/components/src/SpectrumUtils.ts
index 70f676a363..95e6f7e995 100644
--- a/packages/components/src/SpectrumUtils.ts
+++ b/packages/components/src/SpectrumUtils.ts
@@ -1,6 +1,5 @@
import { theme } from '@react-spectrum/theme-default';
-import darkDH from './SpectrumThemeDark.module.scss';
-import lightDH from './SpectrumThemeLight.module.scss';
+import { themeSpectrumClassesCommon } from './theme/theme-spectrum';
const { global, light, dark, medium, large } = theme;
@@ -42,11 +41,11 @@ export const themeDHDefault = {
global,
light: {
...light,
- ...lightDH,
+ ...themeSpectrumClassesCommon,
},
dark: {
...dark,
- ...darkDH,
+ ...themeSpectrumClassesCommon,
},
// scales
medium,
diff --git a/packages/components/src/__snapshots__/SpectrumUtils.test.ts.snap b/packages/components/src/__snapshots__/SpectrumUtils.test.ts.snap
new file mode 100644
index 0000000000..f5973cdacc
--- /dev/null
+++ b/packages/components/src/__snapshots__/SpectrumUtils.test.ts.snap
@@ -0,0 +1,33 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`themeDHDefault should merge Spectrum default with DH custom styles 1`] = `
+{
+ "dark": {
+ "dh-spectrum-alias": "mock-dh-spectrum-alias",
+ "dh-spectrum-overrides": "mock-dh-spectrum-overrides",
+ "dh-spectrum-palette": "mock-dh-spectrum-palette",
+ "spectrum--darkest": "spectrum--darkest_256eeb",
+ },
+ "global": {
+ "spectrum": "spectrum_9e130c",
+ "spectrum--dark": "spectrum--dark_9e130c",
+ "spectrum--darkest": "spectrum--darkest_9e130c",
+ "spectrum--large": "spectrum--large_9e130c",
+ "spectrum--light": "spectrum--light_9e130c",
+ "spectrum--lightest": "spectrum--lightest_9e130c",
+ "spectrum--medium": "spectrum--medium_9e130c",
+ },
+ "large": {
+ "spectrum--large": "spectrum--large_c40598",
+ },
+ "light": {
+ "dh-spectrum-alias": "mock-dh-spectrum-alias",
+ "dh-spectrum-overrides": "mock-dh-spectrum-overrides",
+ "dh-spectrum-palette": "mock-dh-spectrum-palette",
+ "spectrum--light": "spectrum--light_a40724",
+ },
+ "medium": {
+ "spectrum--medium": "spectrum--medium_4b172c",
+ },
+}
+`;
diff --git a/packages/components/src/declaration.d.ts b/packages/components/src/declaration.d.ts
index fde32658a1..f1577432ee 100644
--- a/packages/components/src/declaration.d.ts
+++ b/packages/components/src/declaration.d.ts
@@ -1,3 +1,7 @@
+declare module '*.module.css' {
+ const content: Record
;
+ export default content;
+}
declare module '*.module.scss' {
const content: Record;
export default content;
diff --git a/packages/components/src/theme/SpectrumThemeProvider.tsx b/packages/components/src/theme/SpectrumThemeProvider.tsx
new file mode 100644
index 0000000000..6b190e1c32
--- /dev/null
+++ b/packages/components/src/theme/SpectrumThemeProvider.tsx
@@ -0,0 +1,37 @@
+import { ReactNode, useState } from 'react';
+import { Provider } from '@adobe/react-spectrum';
+import type { Theme } from '@react-types/provider';
+import shortid from 'shortid';
+import { themeDHDefault } from '../SpectrumUtils';
+
+export interface SpectrumThemeProviderProps {
+ children: ReactNode;
+ isPortal?: boolean;
+ theme?: Theme;
+ colorScheme?: 'light' | 'dark';
+}
+
+/**
+ * Wrapper around React Spectrum's theme Provider that provides DH mappings of
+ * Spectrum's theme variables to DH's theme variables. Also exposes an optional
+ * `isPortal` prop that if provided, adds a unique `data-unique-id` attribute to
+ * the Provider. This is needed to force the Provider to render the theme wrapper
+ * inside of portals.
+ */
+export function SpectrumThemeProvider({
+ children,
+ isPortal = false,
+ theme = themeDHDefault,
+ colorScheme,
+}: SpectrumThemeProviderProps): JSX.Element {
+ // a unique ID is used per provider to force it to render the theme wrapper element inside portals
+ // based on https://github.com/adobe/react-spectrum/issues/1697#issuecomment-999827266
+ // won't be needed if https://github.com/adobe/react-spectrum/pull/2669 is merged
+ const [id] = useState(isPortal ? shortid() : null);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/components/src/theme/ThemeProvider.test.tsx b/packages/components/src/theme/ThemeProvider.test.tsx
index 11856899fe..8fb489461a 100644
--- a/packages/components/src/theme/ThemeProvider.test.tsx
+++ b/packages/components/src/theme/ThemeProvider.test.tsx
@@ -75,18 +75,20 @@ describe('ThemeProvider', () => {
assertNotNull(themeContextValueRef.current);
- expect(themeContextValueRef.current.activeThemes).toEqual(
- themes == null
- ? null
- : getActiveThemes(preloadData?.themeKey ?? DEFAULT_DARK_THEME_KEY, {
- base: getDefaultBaseThemes(),
- custom: themes,
- })
- );
+ if (themes == null) {
+ expect(themeContextValueRef.current.activeThemes).toBeNull();
+ } else {
+ expect(themeContextValueRef.current.activeThemes).toEqual(
+ getActiveThemes(preloadData?.themeKey ?? DEFAULT_DARK_THEME_KEY, {
+ base: getDefaultBaseThemes(),
+ custom: themes,
+ })
+ );
- expect(themeContextValueRef.current.selectedThemeKey).toEqual(
- preloadData?.themeKey ?? DEFAULT_DARK_THEME_KEY
- );
+ expect(themeContextValueRef.current.selectedThemeKey).toEqual(
+ preloadData?.themeKey ?? DEFAULT_DARK_THEME_KEY
+ );
+ }
expect(component.baseElement).toMatchSnapshot();
}
@@ -108,10 +110,14 @@ describe('ThemeProvider', () => {
);
- expect(setThemePreloadData).toHaveBeenCalledWith({
- themeKey: preloadData?.themeKey ?? DEFAULT_DARK_THEME_KEY,
- preloadStyleContent: calculatePreloadStyleContent(),
- });
+ if (themes == null) {
+ expect(setThemePreloadData).not.toHaveBeenCalled();
+ } else {
+ expect(setThemePreloadData).toHaveBeenCalledWith({
+ themeKey: preloadData?.themeKey ?? DEFAULT_DARK_THEME_KEY,
+ preloadStyleContent: calculatePreloadStyleContent(),
+ });
+ }
}
);
@@ -127,20 +133,24 @@ describe('ThemeProvider', () => {
assertNotNull(themeContextValueRef.current);
- act(() => {
- themeContextValueRef.current!.setSelectedThemeKey(themeKey);
- });
+ if (themes == null) {
+ expect(themeContextValueRef.current.activeThemes).toBeNull();
+ } else {
+ act(() => {
+ themeContextValueRef.current!.setSelectedThemeKey(themeKey);
+ });
- expect(themeContextValueRef.current.activeThemes).toEqual(
- themes == null
- ? null
- : getActiveThemes(themeKey, {
- base: getDefaultBaseThemes(),
- custom: themes,
- })
- );
+ expect(themeContextValueRef.current.activeThemes).toEqual(
+ getActiveThemes(themeKey, {
+ base: getDefaultBaseThemes(),
+ custom: themes,
+ })
+ );
- expect(themeContextValueRef.current.selectedThemeKey).toEqual(themeKey);
+ expect(themeContextValueRef.current.selectedThemeKey).toEqual(
+ themeKey
+ );
+ }
expect(component.baseElement).toMatchSnapshot();
}
diff --git a/packages/components/src/theme/ThemeProvider.tsx b/packages/components/src/theme/ThemeProvider.tsx
index d3ff14d0ec..abf61a9800 100644
--- a/packages/components/src/theme/ThemeProvider.tsx
+++ b/packages/components/src/theme/ThemeProvider.tsx
@@ -8,6 +8,7 @@ import {
getThemePreloadData,
setThemePreloadData,
} from './ThemeUtils';
+import { SpectrumThemeProvider } from './SpectrumThemeProvider';
export interface ThemeContextValue {
activeThemes: ThemeData[] | null;
@@ -20,6 +21,11 @@ const log = Log.module('ThemeProvider');
export const ThemeContext = createContext(null);
export interface ThemeProviderProps {
+ /*
+ * Additional themes to load in addition to the base themes. If no additional
+ * themes are to be loaded, this must be set to an empty array in order to
+ * tell the provider to activate the base themes.
+ */
themes: ThemeData[] | null;
children: ReactNode;
}
@@ -34,11 +40,9 @@ export function ThemeProvider({
() => getThemePreloadData()?.themeKey ?? DEFAULT_DARK_THEME_KEY
);
+ // Calculate active themes once a non-null themes array is provided.
const activeThemes = useMemo(
() =>
- // Themes remain inactive until a non-null themes array is provided. This
- // avoids the default base theme overriding the preload if we are waiting
- // on additional themes to be available.
themes == null
? null
: getActiveThemes(selectedThemeKey, {
@@ -50,14 +54,26 @@ export function ThemeProvider({
useEffect(
function updateThemePreloadData() {
- log.debug('Active themes:', activeThemes?.map(theme => theme.themeKey));
+ // Don't update preload data until themes have been loaded and activated
+ if (activeThemes == null || themes == null) {
+ return;
+ }
+
+ const preloadStyleContent = calculatePreloadStyleContent();
+
+ log.debug2('updateThemePreloadData:', {
+ active: activeThemes.map(theme => theme.themeKey),
+ all: themes.map(theme => theme.themeKey),
+ preloadStyleContent,
+ selectedThemeKey,
+ });
setThemePreloadData({
themeKey: selectedThemeKey,
- preloadStyleContent: calculatePreloadStyleContent(),
+ preloadStyleContent,
});
},
- [activeThemes, selectedThemeKey]
+ [activeThemes, selectedThemeKey, themes]
);
const value = useMemo(
@@ -71,12 +87,16 @@ export function ThemeProvider({
return (
- {activeThemes?.map(theme => (
-
- ))}
- {children}
+ {activeThemes == null ? null : (
+ <>
+ {activeThemes.map(theme => (
+
+ ))}
+ >
+ )}
+ {children}
);
}
diff --git a/packages/components/src/theme/ThemeUtils.test.ts b/packages/components/src/theme/ThemeUtils.test.ts
index ba34a9b329..bf53d632d8 100644
--- a/packages/components/src/theme/ThemeUtils.test.ts
+++ b/packages/components/src/theme/ThemeUtils.test.ts
@@ -191,13 +191,18 @@ describe('getDefaultBaseThemes', () => {
{
name: 'Default Dark',
themeKey: 'default-dark',
- styleContent:
- 'test-file-stub\ntest-file-stub\ntest-file-stub\ntest-file-stub',
+ styleContent: [
+ 'mock-theme-dark-palette',
+ 'mock-theme-dark-semantic',
+ 'mock-theme-dark-semantic-editor',
+ 'mock-theme-dark-semantic-grid',
+ 'mock-theme-dark-components',
+ ].join('\n'),
},
{
name: 'Default Light',
themeKey: 'default-light',
- styleContent: 'test-file-stub',
+ styleContent: 'mock-theme-light-palette',
},
]);
});
diff --git a/packages/components/src/theme/__snapshots__/ThemeProvider.test.tsx.snap b/packages/components/src/theme/__snapshots__/ThemeProvider.test.tsx.snap
index 33d1ae08e2..30808714c6 100644
--- a/packages/components/src/theme/__snapshots__/ThemeProvider.test.tsx.snap
+++ b/packages/components/src/theme/__snapshots__/ThemeProvider.test.tsx.snap
@@ -6,10 +6,17 @@ exports[`ThemeProvider setSelectedThemeKey: [ [Object] ] should change selected
-