Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(@deephaven/components): DH-14630 Custom React Spectrum Provider #1211

Merged
merged 13 commits into from
Apr 18, 2023
3 changes: 3 additions & 0 deletions __mocks__/spectrumThemeDarkMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
'dh-spectrum-theme--dark': 'mock.dark',
};
3 changes: 3 additions & 0 deletions __mocks__/spectrumThemeLightMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
'dh-spectrum-theme--light': 'mock.light',
};
8 changes: 7 additions & 1 deletion jest.config.base.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ module.exports = {
'.(ts|tsx|js|jsx)': ['babel-jest', { rootMode: 'upward' }],
},
// Makes jest transform monaco, but continue ignoring other node_modules. Used for MonacoUtils test
transformIgnorePatterns: ['node_modules/(?!(monaco-editor|d3-interpolate|d3-color)/)'],
transformIgnorePatterns: [
'node_modules/(?!(monaco-editor|d3-interpolate|d3-color)/)',
],
moduleNameMapper: {
'SpectrumTheme([^.]+)\\.module\\.scss$': path.join(
__dirname,
'./__mocks__/spectrumTheme$1Mock.js'
),
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': path.join(
__dirname,
Expand Down
5,078 changes: 5,078 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
},
"repository": "https://github.com/deephaven/web-client-ui",
"devDependencies": {
"@adobe/react-spectrum": "^3.26.0",
"@babel/cli": "7.20.7",
"@babel/core": "7.20.12",
"@deephaven/components": "file:../components",
Expand Down
1 change: 1 addition & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"shortid": "^2.2.16"
},
"peerDependencies": {
"@adobe/react-spectrum": "^3.x",
mofojed marked this conversation as resolved.
Show resolved Hide resolved
"react": "^17.x",
"react-dom": "^17.x"
},
Expand Down
28 changes: 28 additions & 0 deletions packages/components/scss/util.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/// Utilty for increasing specificity by repeating a given selector n number of
/// times.
///
/// It should work for selectors that can be concatenated without delimiters
/// such as '.some-selector.some-selector', '#some-id#some-id', or '&&'. A
/// case where it would not work would be with an element selector e.g. 'divdiv'.
///
/// Example usage:
///
/// #{multiply-specificity-n('.some-selector', 2)} {
/// }
///
/// would produce
///
/// .some-selector.some-selector {
/// }
///
/// @param {string} $selector the selector to duplicate
/// @param {number} $n number of times to duplicate
/// @return {string} duplicated selector
@function multiply-specificity-n($selector, $n) {
$result: $selector;
@for $i from 2 through $n {
$result: selector-append($result, $selector);
}

@return $result;
}
17 changes: 17 additions & 0 deletions packages/components/src/SpectrumProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import { render } from '@testing-library/react';
import SpectrumProvider from './SpectrumProvider';

it.each([
[true, 'transparent'],
[false, ''],
] as const)(
'should have transparent background when "transparentBackground" is true: %s',
(transparentBackground, expected) => {
const { container } = render(
<SpectrumProvider transparentBackground={transparentBackground} />
);
const firstChild = container.firstChild as HTMLDivElement;
expect(firstChild.style.backgroundColor).toEqual(expected);
}
);
52 changes: 52 additions & 0 deletions packages/components/src/SpectrumProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import { Provider, ProviderProps } from '@adobe/react-spectrum';
import Log from '@deephaven/log';
import { themeDHDefault } from './SpectrumUtils';

const log = Log.module('SpectrumProvider');

export interface SpectrumProviderProps {
colorScheme?: 'dark' | 'light';
theme?: ProviderProps['theme'];
/**
* Since this provider may be used to wrap components that are not at the root
* of the application, we may want to leave the background color alone so that
* components can "inherit" parent component background colors.
*/
transparentBackground?: boolean;
bmingles marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Custom React Spectrum provider with theme customization overrides.
*/
function SpectrumProvider({
children,
colorScheme = 'dark',
theme: themeCustom,
transparentBackground = false,
}: React.PropsWithChildren<SpectrumProviderProps>): JSX.Element {
const style = React.useMemo(
() => ({
backgroundColor: transparentBackground ? 'transparent' : undefined,
}),
[transparentBackground]
);

const theme = React.useMemo(
() => ({
...themeDHDefault,
...themeCustom,
}),
[themeCustom]
);

log.debug('Theme', theme);

return (
<Provider UNSAFE_style={style} colorScheme={colorScheme} theme={theme}>
{children}
</Provider>
);
}

export default SpectrumProvider;
12 changes: 12 additions & 0 deletions packages/components/src/SpectrumThemeDark.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
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};
}
12 changes: 12 additions & 0 deletions packages/components/src/SpectrumThemeLight.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
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};
}
22 changes: 22 additions & 0 deletions packages/components/src/SpectrumUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { defaultTheme } from '@adobe/react-spectrum';
import { themeDHDefault } from './SpectrumUtils';

describe('themeDHDefault', () => {
it('should merge Spectrum default with DH custom styles', () => {
const { global, light, dark, medium, large } = defaultTheme;

expect(themeDHDefault).toEqual({
global,
light: {
...light,
'dh-spectrum-theme--light': 'mock.light',
bmingles marked this conversation as resolved.
Show resolved Hide resolved
},
dark: {
...dark,
'dh-spectrum-theme--dark': 'mock.dark',
},
medium,
large,
});
});
});
54 changes: 54 additions & 0 deletions packages/components/src/SpectrumUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { defaultTheme } from '@adobe/react-spectrum';
bmingles marked this conversation as resolved.
Show resolved Hide resolved
import darkDH from './SpectrumThemeDark.module.scss';
import lightDH from './SpectrumThemeLight.module.scss';

const { global, light, dark, medium, large } = defaultTheme;

/**
* Extend light + dark theme variables with DH defaults.
*
* A theme is just a mapped collection of css class names that are generated
* from a collection of css modules.
*
* e.g.
* {
* global: {
* spectrum: 'spectrum_9e130c',
* 'spectrum--medium': 'spectrum--medium_9e130c',
* 'spectrum--large': 'spectrum--large_9e130c',
* 'spectrum--darkest': 'spectrum--darkest_9e130c',
* 'spectrum--dark': 'spectrum--dark_9e130c',
* 'spectrum--light': 'spectrum--light_9e130c',
* 'spectrum--lightest': 'spectrum--lightest_9e130c',
* },
* light: {
* 'spectrum--light': 'spectrum--light_a40724',
* 'dh-spectrum-theme--light': '_dh-spectrum-theme--light_1hblg_22',
* },
* dark: {
* 'spectrum--darkest': 'spectrum--darkest_256eeb',
* 'dh-spectrum-theme--dark': '_dh-spectrum-theme--dark_f7kge_22',
* },
* medium: {
* 'spectrum--medium': 'spectrum--medium_4b172c',
* },
* large: {
* 'spectrum--large': 'spectrum--large_c40598',
* },
* }
*/
/* eslint-disable import/prefer-default-export */
export const themeDHDefault = {
global,
light: {
...light,
...lightDH,
},
bmingles marked this conversation as resolved.
Show resolved Hide resolved
dark: {
...dark,
...darkDH,
},
// scales
medium,
large,
};
2 changes: 2 additions & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export { default as SelectValueList } from './SelectValueList';
export * from './SelectValueList';
export * from './shortcuts';
export { default as SocketedButton } from './SocketedButton';
export { default as SpectrumProvider } from './SpectrumProvider';
export * from './SpectrumUtils';
export { default as ThemeExport } from './ThemeExport';
export { default as TimeInput } from './TimeInput';
export { default as TimeSlider } from './TimeSlider';
Expand Down