Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build-tools/tasks/generate-environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const themes = require('../utils/themes');
const workspace = require('../utils/workspace');

const ALWAYS_VISUAL_REFRESH = process.env.ALWAYS_VISUAL_REFRESH === 'true';
const INCLUDE_ONE_THEME = process.env.INCLUDE_ONE_THEME === 'true';

function writeEnvironmentFile(theme) {
const filepath = 'internal/environment';
Expand All @@ -16,6 +17,7 @@ function writeEnvironmentFile(theme) {
THEME: theme.name,
SYSTEM: 'core',
ALWAYS_VISUAL_REFRESH: !!theme.alwaysVisualRefresh || ALWAYS_VISUAL_REFRESH,
INCLUDE_ONE_THEME: INCLUDE_ONE_THEME,
};
const basePath = path.join(theme.outputPath, filepath);

Expand Down
7 changes: 6 additions & 1 deletion build-tools/utils/themes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
const path = require('path');
const workspace = require('./workspace');

const INCLUDE_ONE_THEME = process.env.INCLUDE_ONE_THEME === 'true';

const themes = [
// This is the default Cloudscape theme, which is best used with Visual Refresh enabled (by default)
{
Expand All @@ -13,7 +15,10 @@ const themes = [
designTokensPackageJson: { name: '@cloudscape-design/design-tokens' },
outputPath: path.join(workspace.targetPath, 'components'),
primaryThemePath: './classic/index.js',
secondaryThemePaths: ['./visual-refresh-secondary/index.js'],
secondaryThemePaths: [
'./visual-refresh-secondary/index.js',
...(INCLUDE_ONE_THEME ? ['./one-theme/index.js'] : []),
],
},
];

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"homepage": "https://cloudscape.design",
"files": [],
"scripts": {
"quick-build": "gulp quick-build",
"quick-build": "cross-env INCLUDE_ONE_THEME=true gulp quick-build",
"build": "cross-env NODE_ENV=production gulp build",
"test": "TZ=UTC gulp test",
"test:unit": "TZ=UTC gulp test:unit",
Expand All @@ -20,7 +20,7 @@
"lint:stylelint": "stylelint --ignore-path .gitignore '{src,pages}/**/*.{css,scss}'",
"build:react18": "cross-env NODE_ENV=production REACT_VERSION=18 gulp build",
"start": "npm-run-all --parallel start:watch start:dev",
"start:watch": "gulp watch",
"start:watch": "cross-env INCLUDE_ONE_THEME=true gulp watch",
"start:dev": "cross-env NODE_ENV=development webpack serve --config pages/webpack.config.cjs",
"start:integ": "cross-env NODE_ENV=development webpack serve --config pages/webpack.config.integ.cjs",
"start:react18": "npm-run-all --parallel start:watch start:react18:dev",
Expand Down
2 changes: 2 additions & 0 deletions pages/app/app-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface AppUrlParams {
density: Density;
direction: 'ltr' | 'rtl';
visualRefresh: boolean;
oneTheme: boolean;
motionDisabled: boolean;
appLayoutWidget: boolean;
mode?: Mode;
Expand All @@ -32,6 +33,7 @@ const appContextDefaults: AppContextType = {
density: Density.Comfortable,
direction: 'ltr',
visualRefresh: THEME === 'default',
oneTheme: false,
motionDisabled: false,
appLayoutWidget: false,
},
Expand Down
28 changes: 22 additions & 6 deletions pages/app/components/theme-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ import React, { useContext } from 'react';

import { Density, Mode } from '@cloudscape-design/global-styles';

import { ALWAYS_VISUAL_REFRESH } from '~components/internal/environment';
import { ALWAYS_VISUAL_REFRESH, INCLUDE_ONE_THEME } from '~components/internal/environment';
import SpaceBetween from '~components/space-between';

import AppContext from '../app-context';

export default function ThemeSwitcher() {
const { mode, urlParams, setUrlParams, setMode } = useContext(AppContext);

function activateTheme(theme: 'visualRefresh' | 'oneTheme' | 'classic') {
setUrlParams({
visualRefresh: theme === 'visualRefresh',
oneTheme: theme === 'oneTheme',
});
window.location.reload();
}

const vrSwitchProps: React.InputHTMLAttributes<HTMLInputElement> = {
id: 'visual-refresh-toggle',
type: 'checkbox',
Expand All @@ -21,11 +29,8 @@ export default function ThemeSwitcher() {
vrSwitchProps.checked = true;
vrSwitchProps.readOnly = true;
} else {
vrSwitchProps.checked = urlParams.visualRefresh;
vrSwitchProps.onChange = event => {
setUrlParams({ visualRefresh: event.target.checked });
window.location.reload();
};
vrSwitchProps.checked = urlParams.visualRefresh && !urlParams.oneTheme;
vrSwitchProps.onChange = event => activateTheme(event.target.checked ? 'visualRefresh' : 'classic');
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same behavior as before: un-toggling visual refresh falls back to classic.

}

return (
Expand All @@ -34,6 +39,17 @@ export default function ThemeSwitcher() {
<input {...vrSwitchProps} />
Visual refresh
</label>
{INCLUDE_ONE_THEME && (
<label>
<input
id="one-theme-toggle"
type="checkbox"
checked={urlParams.oneTheme}
onChange={event => activateTheme(event.target.checked ? 'oneTheme' : 'classic')}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar behavior as with visual refresh: un-toggling falls back to classic.

/>
One theme
</label>
)}
<label>
<input
id="mode-toggle"
Expand Down
9 changes: 7 additions & 2 deletions pages/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,12 @@ function App() {
}

const history = createHashHistory();
const { direction, visualRefresh, appLayoutWidget, appLayoutToolbar, appLayoutDelayedWidget } = parseQuery(
const { direction, visualRefresh, oneTheme, appLayoutWidget, appLayoutToolbar, appLayoutDelayedWidget } = parseQuery(
history.location.search
);

// The VR class needs to be set before any React rendering occurs.
window[awsuiVisualRefreshFlag] = () => visualRefresh;
window[awsuiVisualRefreshFlag] = () => visualRefresh && !oneTheme;
if (!window[awsuiGlobalFlagsSymbol]) {
window[awsuiGlobalFlagsSymbol] = {};
}
Expand All @@ -111,6 +111,11 @@ window[awsuiGlobalFlagsSymbol].appLayoutWidget = appLayoutWidget;
window[awsuiGlobalFlagsSymbol].appLayoutToolbar = appLayoutToolbar;
window[awsuiCustomFlagsSymbol].appLayoutDelayedWidget = appLayoutDelayedWidget;

// Visual Refresh and One Theme are mutually exclusive — manage both classes here so they never coexist.
// useRuntimeVisualRefresh() detects .awsui-visual-refresh on body and short-circuits before its Symbol fallback.
document.body.classList.toggle('awsui-one-theme', oneTheme);
document.body.classList.toggle('awsui-visual-refresh', visualRefresh && !oneTheme);

// Apply the direction value to the HTML element dir attribute
document.documentElement.setAttribute('dir', direction);

Expand Down
14 changes: 14 additions & 0 deletions style-dictionary/one-theme/colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { expandColorDictionary } from '../utils/index.js';
import { StyleDictionary } from '../utils/interfaces.js';

// Anything not listed here falls back to the visual-refresh value via ThemeBuilder.addTokens in ./index.ts.
const tokens: StyleDictionary.ColorsDictionary = {
colorBackgroundButtonPrimaryDefault: '{colorBlack}',
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subsequent PRs will fill this dictionary.

};

const expandedTokens: StyleDictionary.ExpandedColorScopeDictionary = expandColorDictionary(tokens);

export { expandedTokens as tokens };
export const mode: StyleDictionary.ModeIdentifier = 'color';
33 changes: 33 additions & 0 deletions style-dictionary/one-theme/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { ThemeBuilder } from '@cloudscape-design/theming-build';

import { StyleDictionary } from '../utils/interfaces.js';
import { createColorMode, createDensityMode, createMotionMode } from '../utils/modes.js';
import { buildVisualRefresh } from '../visual-refresh/index.js';

const modes = [
createColorMode('.awsui-dark-mode'),
createDensityMode('.awsui-compact-mode'),
createMotionMode('.awsui-motion-disabled'),
];

// One Theme starts from the full visual-refresh token set and layers overrides on top.
const overrides: Array<StyleDictionary.CategoryModule> = [await import('./colors.js')];

const builder = new ThemeBuilder('one-theme', '.awsui-one-theme', modes);

// Layer 1: visual refresh baseline (full token set + contexts).
await buildVisualRefresh(builder);

// Layer 2: One Theme overrides.
overrides.forEach(({ tokens, mode: modeId, referenceTokens }) => {
const mode = modes.find(m => m.id === modeId);
if (referenceTokens) {
builder.addReferenceTokens(referenceTokens, mode);
}
builder.addTokens(tokens, mode);
});

const theme = builder.build();
export default theme;
5 changes: 5 additions & 0 deletions style-dictionary/one-theme/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import metadata from '../visual-refresh/metadata/index.js';

export default metadata;
Loading