diff --git a/package-lock.json b/package-lock.json index 7d2031361d..dc50048fe1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5966,6 +5966,23 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@storybook/addon-themes": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@storybook/addon-themes/-/addon-themes-9.0.2.tgz", + "integrity": "sha512-f9MYge4YWGxz3DTf61i9z47iWdOu4klzsCxQEVlEjf5kAQYxsIRMoZS8RqAegVpt7d4bPR++cY481FUtDnMbFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^9.0.2" + } + }, "node_modules/@storybook/addon-webpack5-compiler-babel": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@storybook/addon-webpack5-compiler-babel/-/addon-webpack5-compiler-babel-3.0.6.tgz", @@ -29642,6 +29659,7 @@ "@bundle-stats/utils": "4.21.7", "ariakit": "2.0.0-next.44", "d3": "7.9.0", + "js-cookie": "2", "modern-css-reset": "1.4.0", "react-use": "17.6.0", "snarkdown": "2.0.0", @@ -29651,6 +29669,7 @@ "@babel/preset-env": "7.28.5", "@babel/preset-react": "7.28.5", "@babel/preset-typescript": "7.28.5", + "@storybook/addon-themes": "9.0.2", "@storybook/addon-webpack5-compiler-babel": "3.0.6", "@storybook/react": "9.1.15", "@storybook/react-webpack5": "9.1.15", @@ -29685,6 +29704,15 @@ "react-router-dom": "^5.0.0" } }, + "packages/ui/node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "packages/utils": { "name": "@bundle-stats/utils", "version": "4.21.7", diff --git a/packages/ui/build/storybook/main.js b/packages/ui/build/storybook/main.js index 6cf6143fb3..1598ef6691 100644 --- a/packages/ui/build/storybook/main.js +++ b/packages/ui/build/storybook/main.js @@ -7,8 +7,10 @@ function getAbsolutePath(value) { module.exports = { framework: getAbsolutePath('@storybook/react-webpack5'), stories: ['../../src/**/*.stories.@(jsx|tsx|mdx)'], - addons: [getAbsolutePath('@storybook/addon-webpack5-compiler-babel')], - + addons: [ + getAbsolutePath('@storybook/addon-webpack5-compiler-babel'), + getAbsolutePath('@storybook/addon-themes'), + ], docs: { autodocs: true, }, diff --git a/packages/ui/build/storybook/preview.tsx b/packages/ui/build/storybook/preview.tsx index d630d99f5f..e107a40d8b 100644 --- a/packages/ui/build/storybook/preview.tsx +++ b/packages/ui/build/storybook/preview.tsx @@ -1,5 +1,6 @@ import React from 'react'; import type { Preview } from '@storybook/react'; +import { withThemeByClassName } from '@storybook/addon-themes'; import '../../src/css/variables.css'; import '../../src/css/default.css'; @@ -7,6 +8,13 @@ import { SvgIcons } from '../../src/assets/icons.svg.jsx'; const preview: Preview = { decorators: [ + withThemeByClassName({ + themes: { + light: 'light-theme', + dark: 'dark-theme', + }, + defaultTheme: 'light', + }), (Story) => (
diff --git a/packages/ui/package.json b/packages/ui/package.json index 4a0ebd842a..3b575a14ad 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -39,6 +39,7 @@ "@babel/preset-env": "7.28.5", "@babel/preset-react": "7.28.5", "@babel/preset-typescript": "7.28.5", + "@storybook/addon-themes": "9.0.2", "@storybook/addon-webpack5-compiler-babel": "3.0.6", "@storybook/react": "9.1.15", "@storybook/react-webpack5": "9.1.15", @@ -76,6 +77,7 @@ "@bundle-stats/utils": "4.21.7", "ariakit": "2.0.0-next.44", "d3": "7.9.0", + "js-cookie": "2.2.1", "modern-css-reset": "1.4.0", "react-use": "17.6.0", "snarkdown": "2.0.0", diff --git a/packages/ui/src/app/app.jsx b/packages/ui/src/app/app.jsx index 081b798658..e057c0388c 100644 --- a/packages/ui/src/app/app.jsx +++ b/packages/ui/src/app/app.jsx @@ -5,6 +5,7 @@ import { HashRouter, NavLink, Route, Switch, useLocation } from 'react-router-do import { COMPONENT } from '@bundle-stats/utils'; import { URLS } from '../constants'; +import { ThemeProvider } from '../context/theme'; import { BundleAssets } from '../components/bundle-assets'; import { BundleAssetsTotals } from '../components/bundle-assets-totals'; import { BundleModules } from '../components/bundle-modules'; @@ -205,10 +206,12 @@ AppComponent.propTypes = { }; export const App = (props) => ( - - - - - - + + + + + + + + ); diff --git a/packages/ui/src/app/app.module.css b/packages/ui/src/app/app.module.css index 970894f3b1..4e6904b61b 100644 --- a/packages/ui/src/app/app.module.css +++ b/packages/ui/src/app/app.module.css @@ -24,7 +24,7 @@ .tabsContainer { border-bottom: 1px solid var(--color-outline); - background: var(--color-light); + background: var(--color-background); } .tabs { diff --git a/packages/ui/src/app/header/header.module.css b/packages/ui/src/app/header/header.module.css index d2b153b3e7..a5ecfc0cb8 100644 --- a/packages/ui/src/app/header/header.module.css +++ b/packages/ui/src/app/header/header.module.css @@ -6,23 +6,7 @@ flex: 0 0 auto; } -.toolsGitHub { - display: block; - border-radius: 50%; - opacity: 0.8; - color: var(--color-text-ultra-dark); - transition: var(--transition-out); -} - -.toolsGitHub:hover, -.toolsGitHub:focus, -.toolsGitHub:active { - opacity: 1; - transition: var(--transition-in); -} - -.toolsGitHubIcon { - display: block; +.toolsButton { } @media (min-width: 1140px) { diff --git a/packages/ui/src/app/header/header.tsx b/packages/ui/src/app/header/header.tsx index 15cec26869..0da2d0ef26 100644 --- a/packages/ui/src/app/header/header.tsx +++ b/packages/ui/src/app/header/header.tsx @@ -3,32 +3,60 @@ import cx from 'classnames'; import config from '../../config.json'; import I18N from '../../i18n'; +import { useTheme } from '../../context/theme'; import { Container } from '../../ui/container'; import { Icon } from '../../ui/icon'; import { FlexStack } from '../../layout/flex-stack'; import { JobsHeader } from '../../components/jobs-header'; import css from './header.module.css'; +import { Button } from '../../ui/button'; interface HeaderProps { jobs: React.ComponentProps['jobs']; } -export const Header = ({ className = '', jobs }: HeaderProps & React.ComponentProps<'header'>) => ( - - - -
- - - -
-
-
-); +export const Header = ({ className = '', jobs }: HeaderProps & React.ComponentProps<'header'>) => { + const theme = useTheme(); + + return ( + + + + + {theme.name === 'dark' ? ( + + ) : ( + + )} + + + + + ); +}; diff --git a/packages/ui/src/assets/icons.svg b/packages/ui/src/assets/icons.svg index 1b0e11fb8f..a66bd03459 100644 --- a/packages/ui/src/assets/icons.svg +++ b/packages/ui/src/assets/icons.svg @@ -64,6 +64,13 @@ + + + + + + + @@ -73,6 +80,10 @@ + + + + diff --git a/packages/ui/src/assets/icons.svg.jsx b/packages/ui/src/assets/icons.svg.jsx index 8e34f82032..8769312b7c 100644 --- a/packages/ui/src/assets/icons.svg.jsx +++ b/packages/ui/src/assets/icons.svg.jsx @@ -9,6 +9,7 @@ export const SvgIcons = (props) => ( strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" + className="feather feather-arrow-down" id="arrow" xmlns="http://www.w3.org/2000/svg" > @@ -20,6 +21,7 @@ export const SvgIcons = (props) => ( strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" + className="feather feather-arrow-right-circle" viewBox="0 0 24 24" id="arrow-right-circle" xmlns="http://www.w3.org/2000/svg" @@ -45,6 +47,7 @@ export const SvgIcons = (props) => ( strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" + className="lucide lucide-chevron-down" viewBox="0 0 24 24" id="chevron-down" xmlns="http://www.w3.org/2000/svg" @@ -57,6 +60,7 @@ export const SvgIcons = (props) => ( strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" + className="lucide lucide-chevron-up" viewBox="0 0 24 24" id="chevron-up" xmlns="http://www.w3.org/2000/svg" @@ -97,6 +101,7 @@ export const SvgIcons = (props) => ( strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" + className="feather feather-clock" id="clock" xmlns="http://www.w3.org/2000/svg" > @@ -109,6 +114,7 @@ export const SvgIcons = (props) => ( strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" + className="feather feather-x-circle" viewBox="0 0 24 24" id="close" xmlns="http://www.w3.org/2000/svg" @@ -123,6 +129,7 @@ export const SvgIcons = (props) => ( strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" + className="feather feather-git-commit" id="commit" xmlns="http://www.w3.org/2000/svg" > @@ -157,6 +164,7 @@ export const SvgIcons = (props) => ( strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" + className="feather feather-external-link" viewBox="0 0 24 24" id="external-link" xmlns="http://www.w3.org/2000/svg" @@ -170,6 +178,7 @@ export const SvgIcons = (props) => ( strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" + className="feather feather-bar-chart" id="filter" xmlns="http://www.w3.org/2000/svg" > @@ -190,6 +199,7 @@ export const SvgIcons = (props) => ( strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" + className="feather feather-help-circle" id="help" xmlns="http://www.w3.org/2000/svg" > @@ -216,11 +226,37 @@ export const SvgIcons = (props) => ( strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" + className="feather feather-menu" id="menu" xmlns="http://www.w3.org/2000/svg" > + + + + + + + ( + + + + ( strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" + className="feather feather-alert-triangle" id="warning" xmlns="http://www.w3.org/2000/svg" > diff --git a/packages/ui/src/assets/icons/monitor.svg b/packages/ui/src/assets/icons/monitor.svg new file mode 100644 index 0000000000..2cf5563d33 --- /dev/null +++ b/packages/ui/src/assets/icons/monitor.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/ui/src/assets/icons/moon.svg b/packages/ui/src/assets/icons/moon.svg new file mode 100644 index 0000000000..3e1e2d6350 --- /dev/null +++ b/packages/ui/src/assets/icons/moon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/src/assets/icons/sun.svg b/packages/ui/src/assets/icons/sun.svg new file mode 100644 index 0000000000..548abb4fe8 --- /dev/null +++ b/packages/ui/src/assets/icons/sun.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/ui/src/components/asset-info/asset-info.module.css b/packages/ui/src/components/asset-info/asset-info.module.css index 57a859417c..74d14dc2e3 100644 --- a/packages/ui/src/components/asset-info/asset-info.module.css +++ b/packages/ui/src/components/asset-info/asset-info.module.css @@ -9,7 +9,7 @@ } .assetNameTagEntry { - background: var(--color-info-dark); + background: var(--color-info-intense); } .assetNameTagInitial { @@ -17,7 +17,7 @@ } .assetNameTagChunk { - background: var(--color-info-light); + background: var(--color-info-muted); } .label { diff --git a/packages/ui/src/components/asset-meta-tag/asset-meta-tag.module.css b/packages/ui/src/components/asset-meta-tag/asset-meta-tag.module.css index f8fb49d437..6c4fffd838 100644 --- a/packages/ui/src/components/asset-meta-tag/asset-meta-tag.module.css +++ b/packages/ui/src/components/asset-meta-tag/asset-meta-tag.module.css @@ -22,7 +22,7 @@ /* tag */ .entry { - --background-color: var(--color-info-dark); + --background-color: var(--color-info-intense); } .entry:empty::before { @@ -38,7 +38,7 @@ } .chunk { - --background-color: var(--color-info-light); + --background-color: var(--color-info-muted); } .chunk:empty::before { @@ -47,10 +47,10 @@ /* status */ .added { - background: linear-gradient(-45deg, var(--color-danger-light) 50%, var(--background-color) 50%) !important; + background: linear-gradient(-45deg, var(--color-danger-muted) 50%, var(--background-color) 50%) !important; } .removed { - background: linear-gradient(135deg, var(--color-danger-light) 50%, var(--background-color) 50%) !important; + background: linear-gradient(135deg, var(--color-danger-muted) 50%, var(--background-color) 50%) !important; } diff --git a/packages/ui/src/components/asset-name/asset-name.module.css b/packages/ui/src/components/asset-name/asset-name.module.css index a7a983123c..85fab8d971 100644 --- a/packages/ui/src/components/asset-name/asset-name.module.css +++ b/packages/ui/src/components/asset-name/asset-name.module.css @@ -6,7 +6,7 @@ } .notPredictiveIcon { - color: var(--color-warning-dark); + color: var(--color-warning-intense); } .notPredictiveHoverCard { diff --git a/packages/ui/src/components/bundle-modules/bundle-modules.module.css b/packages/ui/src/components/bundle-modules/bundle-modules.module.css index f063d03c7c..2aa9d2791f 100644 --- a/packages/ui/src/components/bundle-modules/bundle-modules.module.css +++ b/packages/ui/src/components/bundle-modules/bundle-modules.module.css @@ -1,5 +1,5 @@ .title { - color: var(--color-text-light); + color: var(--color-text-muted); } .toolbarSearch { diff --git a/packages/ui/src/components/bundle-packages/bundle-packages.module.css b/packages/ui/src/components/bundle-packages/bundle-packages.module.css index f5d63a7cb4..78ecac2709 100644 --- a/packages/ui/src/components/bundle-packages/bundle-packages.module.css +++ b/packages/ui/src/components/bundle-packages/bundle-packages.module.css @@ -1,5 +1,5 @@ .title { - color: var(--color-text-light); + color: var(--color-text-muted); } .toolbarSearch { @@ -13,7 +13,7 @@ .packageName { display: inline-block; - color: var(--color-text-light); + color: var(--color-text-muted); } .packageName:last-child { @@ -24,7 +24,7 @@ display: inline-block; margin: 0 var(--space-xxxsmall); content: '/'; - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); } .packageNameTagDuplicate { diff --git a/packages/ui/src/components/delta/delta.module.css b/packages/ui/src/components/delta/delta.module.css index 2d9a12e75e..1624dafd61 100644 --- a/packages/ui/src/components/delta/delta.module.css +++ b/packages/ui/src/components/delta/delta.module.css @@ -1,5 +1,5 @@ .root { - color: var(--color-gray-ultra-light); + color: var(--color-text-ultra-muted); font-family: var(--font-family-fixed); white-space: nowrap; } @@ -9,60 +9,60 @@ } .HIGH_NEGATIVE { - color: var(--color-red-dark); + color: var(--color-danger-intense); } .NEGATIVE { - color: var(--color-red-dark); + color: var(--color-danger-intense); } .LOW_NEGATIVE { - color: var(--color-red-light); + color: var(--color-danger-muted); } .LOW_POSITIVE { - color: var(--color-green-light); + color: var(--color-success-muted); } .POSITIVE { - color: var(--color-green-dark); + color: var(--color-success-intense); } .HIGH_POSITIVE { - color: var(--color-green-dark); + color: var(--color-success-intense); } .inverted { padding: 0 2px; border-radius: var(--radius-xsmall); - background-color: var(--color-gray-ultra-light); - color: var(--color-light); + background-color: var(--color-text-ultra-muted); + color: var(--color-background); } .inverted.CHANGE { - background: var(--color-info-light); + background: var(--color-info-muted); } .inverted.HIGH_NEGATIVE { - background: var(--color-red-dark); + background: var(--color-danger-intense); } .inverted.NEGATIVE { - background: var(--color-red-dark); + background: var(--color-danger-intense); } .inverted.LOW_NEGATIVE { - background: var(--color-red-light); + background: var(--color-danger-muted); } .inverted.LOW_POSITIVE { - background: var(--color-green-light); + background: var(--color-success-muted); } .inverted.POSITIVE { - background: var(--color-green-dark); + background: var(--color-success-intense); } .inverted.HIGH_POSITIVE { - background: var(--color-green-dark); + background: var(--color-success-intense); } diff --git a/packages/ui/src/components/entry-info/entry-info.module.css b/packages/ui/src/components/entry-info/entry-info.module.css index 1dc4d76ff4..6f32965709 100644 --- a/packages/ui/src/components/entry-info/entry-info.module.css +++ b/packages/ui/src/components/entry-info/entry-info.module.css @@ -5,7 +5,7 @@ bottom: 0; width: 100vw; max-width: var(--entry-info-width); - z-index: var(--layer-high); + z-index: calc(var(--layer-high) - 1); background: var(--color-background); box-shadow: var(--shadow-layer-high); } @@ -36,13 +36,13 @@ position: absolute; right: calc(var(--space-small) - var(--space-xxxsmall)); top: calc(var(--space-small) - var(--space-xxxsmall)); - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); } .headerClose:hover, .headerClose:active, .headerClose:focus { - color: var(--color-text-light); + color: var(--color-text-muted); } /** comon components */ @@ -88,12 +88,12 @@ margin-right: var(--space-xxxsmall); min-width: 8em; flex: 0 1 6em; - color: var(--color-text-light); + color: var(--color-text-muted); } .metaLabelTooltip { margin-left: var(--space-xxxsmall); - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); } .metaContent { @@ -126,7 +126,7 @@ .metaLink:active, .metaLink:focus { background: var(--color-outline); - color: var(--color-primary-dark); + color: var(--color-primary-intense); } @media (min-width: 640px) { diff --git a/packages/ui/src/components/insight-icon/insight-icon.module.css b/packages/ui/src/components/insight-icon/insight-icon.module.css index 04e693d398..fd939e3432 100644 --- a/packages/ui/src/components/insight-icon/insight-icon.module.css +++ b/packages/ui/src/components/insight-icon/insight-icon.module.css @@ -7,14 +7,14 @@ } .error { - background: var(--color-red-50); + background: var(--color-highlight-danger); color: var(--color-danger); } .warning { padding-top: 1px; - background: var(--color-yellow-100); - color: var(--color-yellow-900); + background: var(--color-highlight-warning); + color: var(--color-warning); } .info { diff --git a/packages/ui/src/components/jobs-header/jobs-header.module.css b/packages/ui/src/components/jobs-header/jobs-header.module.css index 14f4b19d40..4ef0f28057 100644 --- a/packages/ui/src/components/jobs-header/jobs-header.module.css +++ b/packages/ui/src/components/jobs-header/jobs-header.module.css @@ -41,17 +41,17 @@ vertical-align: top; outline: 1px solid var(--color-outline); background: var(--color-highlight); - color: var(--color-text-light); + color: var(--color-text-muted); } .itemMeta { - color: var(--color-text-light); + color: var(--color-text-muted); font-size: var(--size-small); } .itemMetaIcon { flex: 0 0 auto; - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); } .itemMetaText { diff --git a/packages/ui/src/components/metric-run-info/metric-run-info.module.css b/packages/ui/src/components/metric-run-info/metric-run-info.module.css index 702d8c400f..1f70ee00b2 100644 --- a/packages/ui/src/components/metric-run-info/metric-run-info.module.css +++ b/packages/ui/src/components/metric-run-info/metric-run-info.module.css @@ -12,6 +12,6 @@ .readMoreLink:hover, .readMoreLink:focus, .readMoreLink:active { - color: var(--color-primary-dark); + color: var(--color-primary-intense); text-decoration: none; } diff --git a/packages/ui/src/components/metrics-display-selector/metrics-display-selector.module.css b/packages/ui/src/components/metrics-display-selector/metrics-display-selector.module.css index ad8c0e71ab..cdb2b71bdd 100644 --- a/packages/ui/src/components/metrics-display-selector/metrics-display-selector.module.css +++ b/packages/ui/src/components/metrics-display-selector/metrics-display-selector.module.css @@ -4,7 +4,7 @@ } .dropdownGroupButton { - padding: calc(var(--space-xxsmall) - 1px); + padding: calc(var(--space-2x) - 1px); } .dropdownGroupAnchor { @@ -17,11 +17,11 @@ content: ''; display: block; width: 1px; - background: var(--color-outline-dark); + background: var(--color-outline); position: absolute; left: 0; - top: var(--space-xxxsmall); - bottom: var(--space-xxxsmall); + top: var(--space); + bottom: var(--space); } .dropdownGroupAnchor:hover, @@ -33,7 +33,7 @@ } .dropdownGroup:hover { - border-color: var(--color-outline-dark); + border-color: var(--color-outline-intense); transition: var(--transition-in); } diff --git a/packages/ui/src/components/metrics-table-title/metrics-table-title.module.css b/packages/ui/src/components/metrics-table-title/metrics-table-title.module.css index c3c167e6a4..0d7d83b462 100644 --- a/packages/ui/src/components/metrics-table-title/metrics-table-title.module.css +++ b/packages/ui/src/components/metrics-table-title/metrics-table-title.module.css @@ -1,5 +1,5 @@ .root { - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); } .title { diff --git a/packages/ui/src/components/metrics-table/metrics-table.module.css b/packages/ui/src/components/metrics-table/metrics-table.module.css index 72322f784a..3f84b063dd 100644 --- a/packages/ui/src/components/metrics-table/metrics-table.module.css +++ b/packages/ui/src/components/metrics-table/metrics-table.module.css @@ -18,7 +18,7 @@ /** rows */ .multipleRuns .unchanged th, .multipleRuns .unchanged td { - color: var(--color-text-light); + color: var(--color-text-muted); } .root .empty { @@ -29,7 +29,7 @@ } .emptyIcon { - color: var(--color-outline-dark); + color: var(--color-outline-intense); width: var(--space-large); height: var(--space-large); } diff --git a/packages/ui/src/components/metrics-treemap/metrics-treemap.module.css b/packages/ui/src/components/metrics-treemap/metrics-treemap.module.css index eeff481d1f..049282ea07 100644 --- a/packages/ui/src/components/metrics-treemap/metrics-treemap.module.css +++ b/packages/ui/src/components/metrics-treemap/metrics-treemap.module.css @@ -50,7 +50,7 @@ .tileGroupTitleTotal { margin-left: var(--space-xxsmall); - color: var(--color-text-light); + color: var(--color-text-muted); } .tileGroupTitle { @@ -86,41 +86,41 @@ /** Tile group nested variation */ .nested .tileGroup { - background-color: rgba(27, 26, 33, 0.03); - outline: 1px solid rgba(255, 255, 255, 0.8); + background-color: rgba(var(--color-background-rgba), 0.1); + outline: 1px solid rgba(var(--color-background-rgba), 0.8); } .nested .tileGroup:has(> .tileGroupTitle:hover), .nested .tileGroup:has(> .tile:hover) { - background-color: rgba(27, 26, 33, 0.06); - outline: 1px solid rgba(255, 255, 255, 1); + background-color: rgba(var(--color-background-rgba), 0.06); + outline: 1px solid rgba(var(--color-background-rgba), 1); transition: var(--transition-in); transition-property: background-color, outline; } .nested .tileGroup--NEGATIVE { - background-color: rgba(194, 31, 37, 0.04); + background-color: rgba(240, 0, 0, 0.04); } .nested .tileGroup--NEGATIVE:has(> .tileGroupTitle:hover), .nested .tileGroup--NEGATIVE:has(> .tile:hover) { - background-color: rgba(194, 31, 37, 0.1); + background-color: rgba(240, 0, 0, 0.1); } .nested .tileGroup--POSITIVE { - background-color: rgba(42, 147, 39, 0.04); + background-color: rgba(0, 240, 0, 0.04); } .nested .tileGroup--POSITIVE:has(> .tileGroupTitle:hover), .nested .tileGroup--POSITIVE:has(> .tile:hover) { - background-color: rgba(42, 147, 39, 0.1); + background-color: rgba(0, 240, 0, 0.1); } /* reset bacground color when having a not changed group */ .nested .tileGroup--POSITIVE > .tileGroup--NO_CHANGE, .nested .tileGroup--NEGATIVE > .tileGroup--NO_CHANGE { background-color: var(--color-background); - background-image: linear-gradient(rgba(27, 26, 33, 0.1), rgba(27, 26, 33, 0.1)); + background-image: linear-gradient(var(--color-background-rgba), 0.1); } .nested .tileGroup--POSITIVE > .tileGroup--NO_CHANGE:has(> .tileGroupTitle:hover), @@ -128,7 +128,7 @@ .nested .tileGroup--NEGATIVE > .tileGroup--NO_CHANGE:has(> .tileGroupTitle:hover), .nested .tileGroup--NEGATIVE > .tileGroup--NEGATIVE > .tileGroup--NO_CHANGE:has(> .tile:hover) { background-color: var(--color-background); - background-image: linear-gradient(rgba(27, 26, 33, 0.1), rgba(27, 26, 33, 0.1)); + background-image: linear-gradient(var(--color-background-rgba), 0.1); } /** Leaf wrapper button */ @@ -222,7 +222,7 @@ /** Tile diff variation */ .tile { - outline: 1px solid rgba(255, 255, 255, 1); + outline: 1px solid rgba(var(--color-background-rgba), 1); color: var(--color-text); } @@ -244,11 +244,11 @@ } .tile-NO_CHANGE { - color: var(--color-text-light); + color: var(--color-text-muted); } .tile-NO_CHANGE::before { - color: var(--color-gray-50); + color: var(--gray-3); opacity: 0.4; } @@ -257,27 +257,27 @@ } .tile-HIGH_NEGATIVE::before { - color: var(--color-red-100); + color: var(--red-5); } .tile-NEGATIVE::before { - color: var(--color-red-50); + color: var(--red-4); } .tile-LOW_NEGATIVE::before { - color: var(--color-red-50); + color: var(--red-3); } .tile-LOW_POSITIVE::before { - color: var(--color-green-50); + color: var(--green-3); } .tile-POSITIVE::before { - color: var(--color-green-50); + color: var(--green-4); } .tile-HIGH_POSITIVE::before { - color: var(--color-green-100); + color: var(--green-5); } /** empty message */ @@ -312,7 +312,7 @@ outline: none; filter: drop-shadow(var(--shadow-layer-high)); will-change: filter; - background: var(--color-light); + background: var(--color-background); font-size: var(--size-small); pointer-events: none; } diff --git a/packages/ui/src/components/module-info/module-info.module.css b/packages/ui/src/components/module-info/module-info.module.css index 9e23e52bae..e0fc56e5ec 100644 --- a/packages/ui/src/components/module-info/module-info.module.css +++ b/packages/ui/src/components/module-info/module-info.module.css @@ -13,7 +13,7 @@ .chunksItem-info:hover, .chunksItem-info:active, .chunksItem-info:focus { - color: var(--color-info-dark); + color: var(--color-info-intense); } .chunksItem-danger { @@ -23,11 +23,11 @@ .chunksItem-danger:hover, .chunksItem-danger:active, .chunksItem-danger:focus { - color: var(--color-danger-dark); + color: var(--color-danger-intense); } .chunksItem-default { - color: var(--color-text-light); + color: var(--color-text-muted); } .chunksItem-default:hover, diff --git a/packages/ui/src/components/package-info/package-info.module.css b/packages/ui/src/components/package-info/package-info.module.css index 7cadb01564..6510863c27 100644 --- a/packages/ui/src/components/package-info/package-info.module.css +++ b/packages/ui/src/components/package-info/package-info.module.css @@ -9,5 +9,5 @@ } .packageTitleId { - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); } diff --git a/packages/ui/src/components/run-info/run-info.module.css b/packages/ui/src/components/run-info/run-info.module.css index c0f73602aa..fd4c38133f 100644 --- a/packages/ui/src/components/run-info/run-info.module.css +++ b/packages/ui/src/components/run-info/run-info.module.css @@ -1,5 +1,5 @@ .title { - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); font-weight: bold; font-size: var(--size-xsmall); text-transform: uppercase; @@ -40,11 +40,11 @@ margin-left: calc(var(--space-xxxsmall) / 2); margin-right: calc(var(--space-xxxsmall) / 2); content: '/'; - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); } .baselineMetric { - color: var(--color-text-ultra-light); + color: var(--color-text-muted); font-size: var(--size-small); line-height: var(--line-height-number); height: var(--line-height-number); diff --git a/packages/ui/src/components/sort-button/sort-button.module.css b/packages/ui/src/components/sort-button/sort-button.module.css index 1702051235..8ff21d38ff 100644 --- a/packages/ui/src/components/sort-button/sort-button.module.css +++ b/packages/ui/src/components/sort-button/sort-button.module.css @@ -3,7 +3,7 @@ display: inline-block; border-radius: var(--radius-small); position: relative; - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); } .toggle { @@ -17,7 +17,7 @@ display: block; overflow: hidden; right: -2px; /* compensate for the button padding */ - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); transition: var(--transition-out); } @@ -35,7 +35,7 @@ .direction:hover, .direction:active, .direction:focus { - color: var(--color-text-dark); + color: var(--color-text-intense); transition: var(--transition-in); } diff --git a/packages/ui/src/context/index.ts b/packages/ui/src/context/index.ts new file mode 100644 index 0000000000..7b1f54ecf9 --- /dev/null +++ b/packages/ui/src/context/index.ts @@ -0,0 +1 @@ +export * from './theme'; diff --git a/packages/ui/src/context/theme.tsx b/packages/ui/src/context/theme.tsx new file mode 100644 index 0000000000..f209d33d01 --- /dev/null +++ b/packages/ui/src/context/theme.tsx @@ -0,0 +1,52 @@ +import type { ReactNode } from 'react'; +import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; + +import { getCurrentTheme, switchTheme, useCookieTheme, type ThemeName } from '../utils/theme'; + +type ThemeContextProps = { + name: ThemeName; + update: (newTheme: ThemeName) => void; +}; + +const ThemeContext = createContext({ + name: 'light', + update: () => {}, +}); + +export type ThemeProviderProps = { + children: ReactNode; +}; + +export const ThemeProvider = (props: ThemeProviderProps) => { + const { children } = props; + + const [cookieValue, setCookieValue] = useCookieTheme(); + const [theme, setTheme] = useState((cookieValue as ThemeName) || getCurrentTheme()); + + const updateTheme = useCallback( + (nextTheme: ThemeName) => { + setCookieValue(nextTheme); + switchTheme(nextTheme); + }, + [setTheme, setCookieValue], + ); + + // Sync classNames on load + useEffect(() => { + switchTheme(theme); + }, [theme]); + + const value = useMemo( + () => ({ + name: theme, + update: updateTheme, + }), + [theme, updateTheme], + ); + + return {children}; +}; + +export const useTheme = (): ThemeContextProps => { + return useContext(ThemeContext); +}; diff --git a/packages/ui/src/css/default.css b/packages/ui/src/css/default.css index fd5cb6750d..92fbb8fc05 100644 --- a/packages/ui/src/css/default.css +++ b/packages/ui/src/css/default.css @@ -60,6 +60,6 @@ a { a:hover, a:active, a:focus { - color: var(--color-primary-dark); + color: var(--color-primary-intense); outline: none; } diff --git a/packages/ui/src/css/variables.css b/packages/ui/src/css/variables.css index 3439061246..bb463b1d87 100644 --- a/packages/ui/src/css/variables.css +++ b/packages/ui/src/css/variables.css @@ -1,4 +1,231 @@ +/* + * Color scheme: + * https://coolors.co/287acc-da444e-55b950-efcc1a-192339 + * https://hihayk.github.io/scale/#4/5/50/90/-51/0/0/14/353138/53/49/56/white + * https://www.radix-ui.com/colors/custom + */ +:root, .light-theme { + --branding-1: #fafdff; + --branding-2: #f2f9ff; + --branding-3: #e3f4ff; + --branding-4: #d3ecff; + --branding-5: #c2e2ff; + --branding-6: #aad5ff; + --branding-7: #8cc3ff; + --branding-8: #5fa9ff; + --branding-9: #1a477f; + --branding-10: #2b5892; + --branding-11: #1f6cc7; + --branding-12: #08376e; + + --gray-1: #fcfcfe; + --gray-2: #f9f9fc; + --gray-3: #f0eff5; + --gray-4: #e8e7ef; + --gray-5: #e0e0ea; + --gray-6: #d9d8e3; + --gray-7: #cecddb; + --gray-8: #bbb9cc; + --gray-9: #8d8c9d; + --gray-10: #838192; + --gray-11: #646371; + --gray-12: #201f29; + + --red-1: #fffcfc; + --red-2: #fff8f7; + --red-3: #feebea; + --red-4: #ffdcdb; + --red-5: #ffcecc; + --red-6: #fbbfbc; + --red-7: #f2aba9; + --red-8: #e8918f; + --red-9: #da444e; + --red-10: #cc3341; + --red-11: #c93340; + --red-12: #631a1f; + + --blue-1: #fcfdff; + --blue-2: #f5f9fe; + --blue-3: #ebf3fc; + --blue-4: #ddecfe; + --blue-5: #cde2fc; + --blue-6: #b9d5f5; + --blue-7: #a1c4ed; + --blue-8: #7dade4; + --blue-9: #287acc; + --blue-10: #166cbd; + --blue-11: #2275c7; + --blue-12: #123559; + + --green-1: #fbfefa; + --green-2: #f5fbf5; + --green-3: #e7f7e5; + --green-4: #d8f2d5; + --green-5: #c6eac2; + --green-6: #b0dfac; + --green-7: #93cf8e; + --green-8: #67bb62; + --green-9: #55b950; + --green-10: #4aad46; + --green-11: #2e7f2b; + --green-12: #223e20; + + --yellow-1: #fefdfb; + --yellow-2: #fffbe9; + --yellow-3: #fff5c0; + --yellow-4: #ffec9b; + --yellow-5: #ffe278; + --yellow-6: #f5d674; + --yellow-7: #e2c568; + --yellow-8: #d0ae3b; + --yellow-9: #ffd00c; + --yellow-10: #f2c724; + --yellow-11: #947300; + --yellow-12: #443b1f; + + --color-background-rgba: 255, 255, 255; + --color-background: rgba(var(--color-background-rgba), 1); + --color-backdrop: rgba(0, 0, 0, 0.05); + --color-shadow: rgba(0, 0, 0, 0.25); +} + +.dark-theme { + --branding-1: #0a111c; + --branding-2: #0f1925; + --branding-3: #0f2746; + --branding-4: #0b3160; + --branding-5: #133d72; + --branding-6: #1e4b83; + --branding-7: #295a99; + --branding-8: #306bb6; + --branding-9: #3984e1; + --branding-10: #2b77d3; + --branding-11: #7fb7ff; + --branding-12: #cee3ff; + + --gray-1: #111015; + --gray-2: #19191e; + --gray-3: #222229; + --gray-4: #2a2933; + --gray-5: #31303c; + --gray-6: #3a3947; + --gray-7: #474656; + --gray-8: #605f70; + --gray-9: #6e6c7e; + --gray-10: #7b7a8c; + --gray-11: #b3b2c5; + --gray-12: #eeeef2; + + --red-1: #160f0f; + --red-2: #1f1313; + --red-3: #3a1314; + --red-4: #4f1116; + --red-5: #60191e; + --red-6: #712629; + --red-7: #8a3638; + --red-8: #b3474b; + --red-9: #da444e; + --red-10: #b94c50; + --red-11: #ff8e8e; + --red-12: #ffd2d0; + + --blue-1: #08121d; + --blue-2: #0d1927; + --blue-3: #072848; + --blue-4: #003265; + --blue-5: #003e78; + --blue-6: #094d8a; + --blue-7: #175ea2; + --blue-8: #1c71c2; + --blue-9: #287acc; + --blue-10: #166cbd; + --blue-11: #6db9ff; + --blue-12: #c8e4ff; + + --green-1: #0d130c; + --green-2: #141a13; + --green-3: #1b2a1a; + --green-4: #1e3a1c; + --green-5: #264824; + --green-6: #2e582b; + --green-7: #366833; + --green-8: #3e7a3a; + --green-9: #55b950; + --green-10: #48ad44; + --green-11: #71d26b; + --green-12: #bef2ba; + + --yellow-1: #13110b; + --yellow-2: #1b1810; + --yellow-3: #2a230a; + --yellow-4: #382b00; + --yellow-5: #453500; + --yellow-6: #52430c; + --yellow-7: #65551e; + --yellow-8: #806c29; + --yellow-9: #ebc013; + --yellow-10: #e0b500; + --yellow-11: #fbd13d; + --yellow-12: #f9e9b8; + + --color-background-rgba: 17, 17, 17; + --color-background: rgba(var(--color-background-rgba), 1); + --color-backdrop: rgba(255, 255, 255, 0.05); + --color-shadow: rgba(0, 0, 0, 0.6); +} + :root { + /* Functional color variables */ + --color-text-ultra-muted: var(--gray-8); + --color-text-muted: var(--gray-9); + --color-text: var(--gray-10); + --color-text-intense: var(--gray-11); + --color-heading: var(--gray-12); + + --color-branding-muted: var(--branding-8); + --color-branding: var(--branding-10); + --color-branding-intense: var(--branding-12); + + --color-success-muted: var(--green-8); + --color-success: var(--green-9); + --color-success-intense: var(--green-10); + + --color-info-muted: var(--blue-8); + --color-info: var(--blue-10); + --color-info-intense: var(--blue-12); + + --color-warning-muted: var(--yellow-9); + --color-warning: var(--yellow-10); + --color-warning-intense: var(--yellow-11); + + --color-danger-muted: var(--red-8); + --color-danger: var(--red-9); + --color-danger-intense: var(--red-11); + + --color-primary-muted: var(--color-info-muted); + --color-primary: var(--color-info); + --color-primary-intense: var(--color-info-intense); + + --color-secondary-muted: var(--color-success-muted); + --color-secondary: var(--color-success); + --color-secondary-intense: var(--color-success-intense); + + --color-highlight: var(--gray-2); + --color-highlight-primary: var(--blue-2); + --color-highlight-secondary: var(--green-2); + --color-highlight-success: var(--green-2); + --color-highlight-info: var(--blue-3); + --color-highlight-warning: var(--yellow-2); + --color-highlight-danger: var(--red-3); + + --color-outline: var(--gray-4); + --color-outline-intense: var(--gray-5); + --color-outline-success: var(--green-4); + --color-outline-info: var(--blue-4); + --color-outline-warning: var(--yellow-5); + --color-outline-danger: var(--red-4); + + /** Font family */ --font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; --font-family-fixed: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; @@ -28,158 +255,6 @@ --line-height-heading: 1.33; --line-height-number: 1.2em; - /* - * Color scheme: - * https://coolors.co/287acc-da444e-55b950-efcc1a-192339 - * http://mcg.mbitson.com/#!?mcgpalette0=%23da444e&mcgpalette1=%2355b950&mcgpalette2=%23ebc013&mcgpalette3=%232a2933&mcgpalette4=%23287acc&themename=bundle-stats - * https://hihayk.github.io/scale/#4/5/50/90/-51/0/0/14/353138/53/49/56/white - */ - --color-gray-25: #f9f9f9; - --color-gray-50: #e5e5e7; - --color-gray-100: #d2d2d4; - --color-gray-200: #959499; - --color-gray-300: #6a6970; - --color-gray-400: #4a4952; - --color-gray-500: #2a2933; - --color-gray-600: #25242e; - --color-gray-700: #1f1f27; - --color-gray-800: #191920; - --color-gray-900: #0f0f14; - - --color-gray-ultra-light: var(--color-gray-200); - --color-gray-light: var(--color-gray-300); - --color-gray: var(--color-gray-500); - --color-gray-dark: var(--color-gray-700); - --color-gray-ultra-dark: var(--color-gray-900); - - --color-red-25: #fdf5f6; - --color-red-50: #fbe9ea; - --color-red-100: #f4c7ca; - --color-red-200: #eda2a7; - --color-red-300: #e57c83; - --color-red-400: #e06069; - --color-red-500: #da444e; - --color-red-600: #d63e47; - --color-red-700: #d0353d; - --color-red-800: #cb2d35; - --color-red-900: #c21f25; - --color-red-ultra-light: var(--color-red-100); - --color-red-light: var(--color-red-300); - --color-red: var(--color-red-500); - --color-red-dark: var(--color-red-700); - --color-red-ultra-dark: var(--color-red-900); - - --color-blue-25: #f3f8fd; - --color-blue-50: #e5eff9; - --color-blue-100: #bfd7f0; - --color-blue-200: #94bde6; - --color-blue-300: #69a2db; - --color-blue-400: #488ed4; - --color-blue-500: #287acc; - --color-blue-600: #2472c7; - --color-blue-700: #1e67c0; - --color-blue-800: #185db9; - --color-blue-900: #0f4aad; - - --color-blue-ultra-light: var(--color-blue-100); - --color-blue-light: var(--color-blue-300); - --color-blue: var(--color-blue-500); - --color-blue-dark: var(--color-blue-700); - --color-blue-ultra-dark: var(--color-blue-900); - - --color-green-25: #f6fbf6; - --color-green-50: #ebf7ea; - --color-green-100: #cceacb; - --color-green-200: #aadca8; - --color-green-300: #88ce85; - --color-green-400: #6fc46a; - --color-green-500: #55b950; - --color-green-600: #4eb249; - --color-green-700: #44aa40; - --color-green-800: #3ba237; - --color-green-900: #2a9327; - - --color-green-ultra-light: var(--color-green-100); - --color-green-light: var(--color-green-300); - --color-green: var(--color-green-500); - --color-green-dark: var(--color-green-700); - --color-green-ultra-dark: var(--color-green-900); - - --color-yellow-25: #fffdf2; - --color-yellow-50: #fdf7e3; - --color-yellow-100: #f9ecb8; - --color-yellow-200: #f5e089; - --color-yellow-300: #f1d35a; - --color-yellow-400: #eec936; - --color-yellow-500: #ebc013; - --color-yellow-600: #e9ba11; - --color-yellow-700: #e5b20e; - --color-yellow-800: #e2aa0b; - --color-yellow-900: #dd9c06; - - --color-yellow-light-background: var(--color-yellow-25); - --color-yellow-ultra-light: var(--color-yellow-100); - --color-yellow-light: var(--color-yellow-300); - --color-yellow: var(--color-yellow-500); - --color-yellow-ultra-dark: var(--color-yellow-700); - --color-yellow-dark: var(--color-yellow-900); - - --color-dark: #111; - --color-light: #fff; - - /* Functional color variables */ - --color-branding-light: #2B76D4; - --color-branding: #1a477f; - --color-branding-dark: #09182A; - - --color-text-ultra-light: var(--color-gray-ultra-light); - --color-text-light: var(--color-gray-light); - --color-text: var(--color-gray); - --color-text-dark: var(--color-gray-dark); - --color-text-ultra-dark: var(--color-gray-ultra-dark); - --color-heading: var(--color-gray-ultra-dark); - - --color-success-light: var(--color-green-light); - --color-success: var(--color-green); - --color-success-dark: var(--color-green-dark); - --color-info-light: var(--color-blue-light); - --color-info: var(--color-blue); - --color-info-dark: var(--color-blue-dark); - --color-warning-light: var(--color-yellow-light); - --color-warning: var(--color-yellow); - --color-warning-dark: var(--color-yellow-dark); - --color-danger-light: var(--color-red-light); - --color-danger: var(--color-red); - --color-danger-dark: var(--color-red-dark); - - --color-primary-light: var(--color-blue-light); - --color-primary: var(--color-blue); - --color-primary-dark: var(--color-blue-dark); - - --color-secondary-light: var(--color-green-light); - --color-secondary: var(--color-green); - --color-secondary-dark: var(--color-green-dark); - - --color-background: #fff; - --color-highlight: var(--color-gray-25); - --color-highlight-primary: var(--color-blue-25); - --color-highlight-secondary: var(--color-green-25); - --color-highlight-success: var(--color-green-25); - --color-highlight-info: var(--color-blue-25); - --color-highlight-warning: var(--color-yellow-25); - --color-highlight-danger: var(--color-red-25); - - --color-outline: var(--color-gray-50); - --color-outline-dark: var(--color-gray-100); - - --color-outline-success: var(--color-green-100); - --color-outline-info: var(--color-blue-100); - --color-outline-warning: var(--color-yellow-200); - --color-outline-danger: var(--color-red-100); - - --color-backdrop: rgba(0, 0, 0, 0.05); - --color-shadow: rgba(0, 0, 0, 0.25); - --transition-duration-in: 0.2s; --transition-duration-out: 0.17s; --transition-in: all var(--transition-duration-in) ease-in; @@ -211,3 +286,8 @@ --entry-info-width: 64rem; --entry-info-top: var(--header-height); } + +.no-motion * { + transition: none !important; + animation: none !important; +} diff --git a/packages/ui/src/index.js b/packages/ui/src/index.js index eb3b4cb0ef..42305df59b 100644 --- a/packages/ui/src/index.js +++ b/packages/ui/src/index.js @@ -1,5 +1,6 @@ export * from './ui'; export * from './layout'; +export * from './context'; export * from './components'; export * from './utils'; export * from './app'; diff --git a/packages/ui/src/layout/box/box.module.css b/packages/ui/src/layout/box/box.module.css index 45a70f4fa5..9ace28804f 100644 --- a/packages/ui/src/layout/box/box.module.css +++ b/packages/ui/src/layout/box/box.module.css @@ -24,7 +24,7 @@ .outlineHover:focus, .outlineHover:active, .outlineHover:hover { - border-color: var(--color-outline-dark); + border-color: var(--color-outline-intense); transition: var(--transition-in); } diff --git a/packages/ui/src/layout/footer/footer.module.css b/packages/ui/src/layout/footer/footer.module.css index fd4d868a78..5a929b4161 100644 --- a/packages/ui/src/layout/footer/footer.module.css +++ b/packages/ui/src/layout/footer/footer.module.css @@ -14,6 +14,6 @@ .navItem + .navItem::before { content: '•'; margin-right: var(--space-xxsmall); - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); font-size: var(--size-large); } diff --git a/packages/ui/src/prototypes/typography.module.css b/packages/ui/src/prototypes/typography.module.css index e06d43fc13..5b3851bf29 100644 --- a/packages/ui/src/prototypes/typography.module.css +++ b/packages/ui/src/prototypes/typography.module.css @@ -8,7 +8,7 @@ align-items: center; font-size: var(--size-xlarge); font-weight: 700; - color: var(--color-light); + color: var(--color-background); } .headerLogo { diff --git a/packages/ui/src/prototypes/typography.stories.jsx b/packages/ui/src/prototypes/typography.stories.jsx index fbdfb93b9d..5d3dec1679 100644 --- a/packages/ui/src/prototypes/typography.stories.jsx +++ b/packages/ui/src/prototypes/typography.stories.jsx @@ -12,22 +12,20 @@ export default { }; export const Typography = () => ( -
- -
+
+ +
); // eslint-disable-next-line react/prop-types const Item = ({ colorName, valueName = 'normal' }) => { - const colorFullName = valueName === 'normal' - ? colorName - : [colorName, valueName].join('-'); + const colorFullName = valueName === 'normal' ? colorName : [colorName, valueName].join('-'); return (
(
); -const COLORS = [ - 'blue', - 'red', - 'green', - 'yellow', - 'gray', -]; +const COLORS = ['branding', 'primary', 'secondary', 'success', 'info', 'warning', 'danger']; -const NAMES = [ - 'ultra-light', - 'light', - 'normal', - 'dark', - 'ultra-dark', -]; +const NAMES = ['muted', 'normal', 'intense']; export const ColorScheme = () => ( - - - - -
- - +
+ + + +

{COLORS.map((colorName) => (
{NAMES.map((valueName) => ( - + ))}
))} @@ -96,10 +76,7 @@ export const ColorScheme = () => (

Chart colors

{CHART_COLORS.map((color) => ( - + ))}
diff --git a/packages/ui/src/ui/button/button.module.css b/packages/ui/src/ui/button/button.module.css index 1d27662ab7..70eab4c1d8 100644 --- a/packages/ui/src/ui/button/button.module.css +++ b/packages/ui/src/ui/button/button.module.css @@ -13,7 +13,7 @@ border: var(--border-width) solid transparent; background: none; transition: var(--transition-out); - color: var(--color-text-ultra-light); + color: var(--color-text-muted); font-family: var(--font-family); white-space: nowrap; } @@ -26,7 +26,7 @@ a.root { .root:active, .root:focus { outline: none; - color: var(--color-text-light); + color: var(--color-text); transition: var(--transition-in); } @@ -49,7 +49,7 @@ a.root { .primary:hover, .primary:active, .primary:focus { - color: var(--color-primary-dark); + color: var(--color-primary-intense); } .secondary { @@ -59,7 +59,7 @@ a.root { .secondary:hover, .secondary:active, .secondary:focus { - color: var(--color-secondary-dark); + color: var(--color-secondary-intense); } .danger { @@ -69,7 +69,7 @@ a.root { .danger:hover, .danger:active, .danger:focus { - color: var(--color-danger-dark); + color: var(--color-danger-intense); } .warning { @@ -79,7 +79,7 @@ a.root { .warning:hover, .warning:active, .warning:focus { - color: var(--color-warning-dark); + color: var(--color-warning-intense); } .info { @@ -89,7 +89,7 @@ a.root { .info:hover, .info:active, .info:focus { - color: var(--color-info-dark); + color: var(--color-info-intense); } .success { @@ -99,21 +99,21 @@ a.root { .success:hover, .success:active, .success:focus { - color: var(--color-success-dark); + color: var(--color-success-intense); } /** * Solid Kind variation */ .solid { - background: var(--color-text-ultra-light); + background: var(--color-text-ultra-muted); color: var(--color-background); } .solid:hover, .solid:active, .solid:focus { - background: var(--color-text-light); + background: var(--color-text-muted); color: var(--color-background); } @@ -124,7 +124,7 @@ a.root { .solid--primary:hover, .solid--primary:active, .solid--primary:focus { - background: var(--color-primary-dark); + background: var(--color-primary-intense); } .solid--secondary { @@ -134,7 +134,7 @@ a.root { .solid--secondary:hover, .solid--secondary:active, .solid--secondary:focus { - background: var(--color-secondary-dark); + background: var(--color-secondary-intense); } .solid--danger { @@ -144,7 +144,7 @@ a.root { .solid--danger:hover, .solid--danger:active, .solid--danger:focus { - background: var(--color-danger-dark); + background: var(--color-danger-intense); } .solid--warning { @@ -154,7 +154,7 @@ a.root { .solid--warning:hover, .solid--warning:active, .solid--warning:focus { - background: var(--color-warning-dark); + background: var(--color-warning-intense); } .solid--info { @@ -164,7 +164,7 @@ a.root { .solid--info:hover, .solid--info:active, .solid--info:focus { - background: var(--color-info-dark); + background: var(--color-info-intense); } .solid--success { @@ -174,7 +174,7 @@ a.root { .solid--success:hover, .solid--success:active, .solid--success:focus { - background: var(--color-success-dark); + background: var(--color-success-intense); } /** @@ -227,7 +227,7 @@ a.root { .outline:hover, .outline:active, .outline:focus { - border-color: var(--color-outline-dark); + border-color: var(--color-outline-intense); } .outline--primary { @@ -237,7 +237,7 @@ a.root { .outline--primary:hover, .outline--primary:active, .outline--primary:focus { - border-color: var(--color-outline-primary-dark); + border-color: var(--color-outline-primary-intense); } .outline--secondary { @@ -247,7 +247,7 @@ a.root { .outline--secondary:hover, .outline--secondary:active, .outline--secondary:focus { - border-color: var(--color-outline-secondary-dark); + border-color: var(--color-outline-secondary-intense); } .outline--danger { @@ -257,7 +257,7 @@ a.root { .outline--danger:hover, .outline--danger:active, .outline--danger:focus { - border-color: var(--color-outline-danger-dark); + border-color: var(--color-outline-danger-intense); } .outline--warning { @@ -267,7 +267,7 @@ a.root { .outline--warning:hover, .outline--warning:active, .outline--warning:focus { - border-color: var(--color-outline-warning-dark); + border-color: var(--color-outline-warning-intense); } .outline--info { @@ -277,7 +277,7 @@ a.root { .outline--info:hover, .outline--info:active, .outline--info:focus { - border-color: var(--color-outline-info-dark); + border-color: var(--color-outline-info-intense); } .outline--success { @@ -287,7 +287,7 @@ a.root { .outline--success:hover, .outline--success:active, .outline--success:focus { - border-color: var(--color-outline-success-dark); + border-color: var(--color-outline-success-intense); } diff --git a/packages/ui/src/ui/copy-to-clipboard/copy-to-clipboard.module.css b/packages/ui/src/ui/copy-to-clipboard/copy-to-clipboard.module.css index 549b7c205d..9861eeeaff 100644 --- a/packages/ui/src/ui/copy-to-clipboard/copy-to-clipboard.module.css +++ b/packages/ui/src/ui/copy-to-clipboard/copy-to-clipboard.module.css @@ -1,7 +1,7 @@ .root { white-space: nowrap; /** override tooltip styles */ - color: var(--color-text-ultra-light) !important; + color: var(--color-text-ultra-muted) !important; } /** done state */ diff --git a/packages/ui/src/ui/dialog/dialog.module.css b/packages/ui/src/ui/dialog/dialog.module.css index 91f9bd890d..eb83037d2b 100644 --- a/packages/ui/src/ui/dialog/dialog.module.css +++ b/packages/ui/src/ui/dialog/dialog.module.css @@ -47,13 +47,13 @@ top: var(--space-xxsmall); padding: var(--space-xxxsmall); flex: 0 0 auto; - color: var(--color-outline-dark); + color: var(--color-outline-intense); } .headerClose:hover, .headerClose:focus, .headerClose:active { - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); } .content { diff --git a/packages/ui/src/ui/dropdown/dropdown.module.css b/packages/ui/src/ui/dropdown/dropdown.module.css index 43ce8e2379..71c0c80281 100644 --- a/packages/ui/src/ui/dropdown/dropdown.module.css +++ b/packages/ui/src/ui/dropdown/dropdown.module.css @@ -3,7 +3,7 @@ } .button[disabled] { - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); } .dropdown { @@ -32,7 +32,7 @@ padding: calc(var(--space-xxxsmall) + var(--space-xxxsmall) / 2); background: none; text-align: left; - color: var(--color-text-light); + color: var(--color-text-muted); font-size: var(--size-small); line-height: var(--line-height); text-decoration: none; @@ -56,5 +56,5 @@ .itemActive:focus, .itemActive:active { background: var(--color-highlight-info); - color: var(--color-primary-dark); + color: var(--color-primary-intense); } diff --git a/packages/ui/src/ui/empty-set/empty-set.module.css b/packages/ui/src/ui/empty-set/empty-set.module.css index 95a169861d..c2b63d373b 100644 --- a/packages/ui/src/ui/empty-set/empty-set.module.css +++ b/packages/ui/src/ui/empty-set/empty-set.module.css @@ -1,3 +1,3 @@ .root { - color: var(--color-text-light); + color: var(--color-text-muted); } diff --git a/packages/ui/src/ui/filters/filters.module.css b/packages/ui/src/ui/filters/filters.module.css index 22b3c0d1a4..d4d399d10d 100644 --- a/packages/ui/src/ui/filters/filters.module.css +++ b/packages/ui/src/ui/filters/filters.module.css @@ -9,13 +9,14 @@ position: relative; width: 100%; max-width: 240px; - color: var(--color-text); + color: var(--color-text-muted); } .filterControl { margin: 0; flex: 0 0 auto; height: 1em; + background: var(--color-background); } .filterLabel { @@ -27,11 +28,11 @@ } .filterControl[disabled] + .filterLabel { - color: var(--color-text-light); + color: var(--color-text-ultra-muted); } .filter:hover { - color: var(--color-dark); + color: var(--color-text); } /** Filter single */ @@ -41,14 +42,15 @@ border-radius: var(--radius-small); font-size: var(--size-small); line-height: var(--space-small); - color: var(--color-text-ultra-light); + color: var(--color-text-muted); transition: var(--transition-out); } .filterSingle:hover, .filterSingle:active, .filterSingle:focus { - border-color: var(--color-outline-dark); + border-color: var(--color-outline-intense); + color: var(--color-text); transition: var(--transition-in); } @@ -87,7 +89,7 @@ right: 100%; bottom: 0; width: var(--space-small); - background: linear-gradient(90deg, rgba(255, 255, 255, 0), var(--color-highlight) 75%); + background: linear-gradient(90deg, rgba(var(--color-background-rgb), 0), var(--color-highlight) 75%); } /** filter hover state */ @@ -111,7 +113,7 @@ padding: var(--space-xxsmall) 0; text-align: center; font-size: var(--size-small); - color: var(--color-text-light); + color: var(--color-text-muted); } .filterGroupActions { diff --git a/packages/ui/src/ui/hover-card/hover-card.module.css b/packages/ui/src/ui/hover-card/hover-card.module.css index e5ecc95b17..0fde739352 100644 --- a/packages/ui/src/ui/hover-card/hover-card.module.css +++ b/packages/ui/src/ui/hover-card/hover-card.module.css @@ -25,6 +25,6 @@ outline: none; filter: drop-shadow(var(--shadow-layer-high)); will-change: filter; - background: var(--color-light); + background: var(--color-background); font-size: var(--size-small); } diff --git a/packages/ui/src/ui/icon/icon.tsx b/packages/ui/src/ui/icon/icon.tsx index c25805358b..edfeff537b 100644 --- a/packages/ui/src/ui/icon/icon.tsx +++ b/packages/ui/src/ui/icon/icon.tsx @@ -7,26 +7,29 @@ const ICONS = { ARROW: 'arrow', ARROW_RIGHT_CIRCLE: 'arrow-right-circle', CANCEL: 'close', + CHECK: 'check', CHEVRON_DOWN: 'chevron-down', CHEVRON_UP: 'chevron-up', - CHECK: 'check', - CLOSE: 'close', - CLOCK: 'clock', - COMMIT: 'commit', CLIPBOARD: 'clipboard', CLIPBOARD_CHECK: 'clipboard-check', + CLOCK: 'clock', + CLOSE: 'close', + COMMIT: 'commit', DOWNLOAD: 'download', ERROR: 'error', EXTERNAL_LINK: 'external-link', FILTER: 'filter', GITHUB: 'github', - INFO: 'info', HELP: 'help', - TABLE: 'table', - TREEMAP: 'treemap', + INFO: 'info', MENU: 'menu', + MONITOR: 'monitor', + MOON: 'moon', MORE_VERTICAL: 'more-vertical', SORT: 'sort', + SUN: 'sun', + TABLE: 'table', + TREEMAP: 'treemap', WARNING: 'warning', } as const; diff --git a/packages/ui/src/ui/input/input.module.css b/packages/ui/src/ui/input/input.module.css index 7c74ecdfd1..ff21b2454f 100644 --- a/packages/ui/src/ui/input/input.module.css +++ b/packages/ui/src/ui/input/input.module.css @@ -1,7 +1,8 @@ .root { border: 1px solid var(--color-outline); border-radius: var(--radius-small); - color: var(--color-text-light); + color: var(--color-text-muted); + background: var(--color-background); width: 100%; transition: var(--transition-in); appearance: none; @@ -9,23 +10,23 @@ } .root::-webkit-input-placeholder { - color: var(--color-text-light); + color: var(--color-text-muted); } .root::-moz-placeholder { - color: var(--color-text-light); + color: var(--color-text-muted); } .root:-ms-input-placeholder { - color: var(--color-text-light); + color: var(--color-text-muted); } .root:-moz-placeholder { - color: var(--color-text-light); + color: var(--color-text-muted); } .root:focus { - border-color: var(--color-outline-dark); + border-color: var(--color-outline-intense); color: inherit; transition: var(--transition-out); } diff --git a/packages/ui/src/ui/tabs/tabs.module.css b/packages/ui/src/ui/tabs/tabs.module.css index be926f0eee..960d336045 100644 --- a/packages/ui/src/ui/tabs/tabs.module.css +++ b/packages/ui/src/ui/tabs/tabs.module.css @@ -10,7 +10,7 @@ position: relative; display: inline-block; padding: var(--space-xsmall) var(--space-small); - color: var(--color-text-ultra-light); + color: var(--color-text-ultra-muted); font-weight: bold; text-decoration: none; transition: var(--ui-transition-out); diff --git a/packages/ui/src/ui/tag/tag.module.css b/packages/ui/src/ui/tag/tag.module.css index bcc30aa849..2f881a818a 100644 --- a/packages/ui/src/ui/tag/tag.module.css +++ b/packages/ui/src/ui/tag/tag.module.css @@ -2,8 +2,8 @@ display: inline-block; font-family: var(--font-family); font-weight: bold; - background: var(--color-text-ultra-light); - color: var(--color-light); + background: var(--color-text-ultra-muted); + color: var(--color-background); text-align: center; text-transform: uppercase; } @@ -17,7 +17,7 @@ a.root:hover, a.root:focus, a.root:active { opacity: 0.8; - color: var(--color-light); + color: var(--color-background); text-decoration: none; transition: var(--transition-in); } @@ -32,7 +32,7 @@ a.root:active { } .warning { - background: var(--color-warning-dark); + background: var(--color-warning-intense); } .danger { diff --git a/packages/ui/src/ui/textarea/textarea.module.css b/packages/ui/src/ui/textarea/textarea.module.css index 3adf14eb88..36e5439b9b 100644 --- a/packages/ui/src/ui/textarea/textarea.module.css +++ b/packages/ui/src/ui/textarea/textarea.module.css @@ -1,7 +1,7 @@ .root { border: 1px solid var(--color-outline); border-radius: var(--radius-small); - color: var(--color-text-light); + color: var(--color-text-muted); width: 100%; resize: vertical; transition: var(--transition-in); @@ -10,29 +10,29 @@ } .root::-webkit-input-placeholder { - color: var(--color-text-light); + color: var(--color-text-muted); } .root::-moz-placeholder { - color: var(--color-text-light); + color: var(--color-text-muted); } .root:-ms-input-placeholder { - color: var(--color-text-light); + color: var(--color-text-muted); } .root:-moz-placeholder { - color: var(--color-text-light); + color: var(--color-text-muted); } .root:focus { - border-color: var(--color-outline-dark); + border-color: var(--color-outline-intense); color: inherit; transition: var(--transition-out); } .root[readonly=""] { - color: var(--color-text-light); + color: var(--color-text-muted); } /** Size modifier */ diff --git a/packages/ui/src/ui/tooltip/tooltip.module.css b/packages/ui/src/ui/tooltip/tooltip.module.css index d18a1d3eff..d6517e7975 100644 --- a/packages/ui/src/ui/tooltip/tooltip.module.css +++ b/packages/ui/src/ui/tooltip/tooltip.module.css @@ -14,7 +14,7 @@ z-index: var(--layer-high); padding: var(--space-xxxsmall) var(--space-xxsmall); border-radius: var(--radius-small); - background: var(--color-text); + background: var(--color-text-intense); max-width: 28em; color: var(--color-background); font-size: var(--size-small); @@ -25,5 +25,5 @@ .arrow svg { display: block; - fill: var(--color-text); + fill: var(--color-text-intense); } diff --git a/packages/ui/src/utils/index.ts b/packages/ui/src/utils/index.ts index 0c490affe6..7f522192be 100644 --- a/packages/ui/src/utils/index.ts +++ b/packages/ui/src/utils/index.ts @@ -1,3 +1,4 @@ export * from './colors'; +export * from './theme'; export * from './jobs'; export * from './components'; diff --git a/packages/ui/src/utils/theme.ts b/packages/ui/src/utils/theme.ts new file mode 100644 index 0000000000..d6321be7ea --- /dev/null +++ b/packages/ui/src/utils/theme.ts @@ -0,0 +1,53 @@ +import { useCookie } from 'react-use'; +import Cookies from 'js-cookie'; +import { wait } from '@bundle-stats/utils'; + +export type ThemeName = 'light' | 'dark'; + +// classList required timeout to allow to disable motion during the switch +const CLASSLIST_UPDATE_TIMEOUT = 10; + +const COOKIE_NAME = 'theme'; + +export const getCurrentTheme = (): ThemeName => { + const { matches } = window.matchMedia('(prefers-color-scheme: dark)'); + return matches ? 'dark' : 'light'; +}; + +export const getCookieTheme = (): ThemeName | null => { + const nextTheme = Cookies.get(COOKIE_NAME); + + if (!nextTheme) { + return null; + } + + if (['light', 'dark'].includes(nextTheme)) { + return nextTheme as ThemeName; + } + + return null; +}; + +export const switchTheme = async (newTheme: ThemeName) => { + const htmlElm = document.querySelector('html'); + + htmlElm?.classList.add('no-motion'); + + await wait(CLASSLIST_UPDATE_TIMEOUT); + + if (newTheme === 'dark') { + htmlElm?.classList.remove('light-theme'); + htmlElm?.classList.add('dark-theme'); + } else { + htmlElm?.classList.remove('dark-theme'); + htmlElm?.classList.add('light-theme'); + } + + await wait(CLASSLIST_UPDATE_TIMEOUT); + + htmlElm?.classList.remove('no-motion'); +}; + +export const useCookieTheme = () => { + return useCookie(COOKIE_NAME); +}; diff --git a/packages/utils/src/utils/index.js b/packages/utils/src/utils/index.js index ec2361b496..d95124df60 100644 --- a/packages/utils/src/utils/index.js +++ b/packages/utils/src/utils/index.js @@ -5,3 +5,4 @@ export * from './insights'; export * from './file-types'; export * from './format'; export * from './metrics'; +export * from './wait'; diff --git a/packages/utils/src/utils/wait.ts b/packages/utils/src/utils/wait.ts new file mode 100644 index 0000000000..7b1a4bf9c8 --- /dev/null +++ b/packages/utils/src/utils/wait.ts @@ -0,0 +1,6 @@ +export const wait = (timeout = 0): Promise => + new Promise((resolve) => { + setTimeout(() => { + return resolve(); + }, timeout); +});