Skip to content
Draft
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
6 changes: 1 addition & 5 deletions build-tools/tasks/generate-environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ const { writeFile } = require('../utils/files');
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';
const values = {
Expand All @@ -16,8 +13,7 @@ function writeEnvironmentFile(theme) {
GIT_SHA: workspace.gitCommitVersion,
THEME: theme.name,
SYSTEM: 'core',
ALWAYS_VISUAL_REFRESH: !!theme.alwaysVisualRefresh || ALWAYS_VISUAL_REFRESH,
INCLUDE_ONE_THEME: INCLUDE_ONE_THEME,
ALWAYS_VISUAL_REFRESH: !!theme.alwaysVisualRefresh,
};
const basePath = path.join(theme.outputPath, filepath);

Expand Down
3 changes: 2 additions & 1 deletion build-tools/tasks/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ function stylesTask(theme) {
theme.secondaryThemePaths?.map(async path => (await import(join(styleDictionaryRoot, path))).default) ?? []
);

const metadataPath = theme.metadataPath || join(theme.primaryThemePath, '../metadata.js');
// eslint-disable-next-line no-unsanitized/method
const { default: metadata } = await import(join(styleDictionaryRoot, theme.primaryThemePath, '../metadata.js'));
const { default: metadata } = await import(join(styleDictionaryRoot, metadataPath));
const exposed = [];
const themeable = [];
const variablesMap = {};
Expand Down
2 changes: 1 addition & 1 deletion build-tools/tasks/themeable-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const componentsTemplateDir = 'internal/template';
const designTokensTemplateDir = 'internal/template-tokens';
const stylesDir = 'internal/scss';

const theme = themes.find(theme => theme.name === 'default');
const theme = themes[0];
const themeable = {
name: 'components-themeable',
sourceDir: theme.outputPath,
Expand Down
56 changes: 49 additions & 7 deletions build-tools/utils/themes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,65 @@
const path = require('path');
const workspace = require('./workspace');

const INCLUDE_ONE_THEME = process.env.INCLUDE_ONE_THEME === 'true';
const THEME = process.env.THEME || 'default';

const themes = [
// This is the default Cloudscape theme, which is best used with Visual Refresh enabled (by default)
const allThemes = [
{
name: 'default',
packageJson: { name: '@cloudscape-design/components' },
designTokensOutput: 'index',
designTokensDir: 'design-tokens',
designTokensPackageJson: { name: '@cloudscape-design/design-tokens' },
outputPath: path.join(workspace.targetPath, 'components'),
primaryThemePath: './core-open-source/index.js',
secondaryThemePaths: [],
metadataPath: './visual-refresh/metadata.js',
alwaysVisualRefresh: true,
},
{
name: 'visual-refresh',
packageJson: { name: '@cloudscape-design/components' },
designTokensOutput: 'index',
designTokensDir: 'design-tokens',
designTokensPackageJson: { name: '@cloudscape-design/design-tokens' },
outputPath: path.join(workspace.targetPath, 'components'),
primaryThemePath: './visual-refresh/index.js',
secondaryThemePaths: [],
metadataPath: './visual-refresh/metadata.js',
alwaysVisualRefresh: true,
},
{
name: 'classic',
packageJson: { name: '@cloudscape-design/components' },
designTokensOutput: 'index',
designTokensDir: 'design-tokens',
designTokensPackageJson: { name: '@cloudscape-design/design-tokens' },
outputPath: path.join(workspace.targetPath, 'components'),
primaryThemePath: './classic/index.js',
secondaryThemePaths: [
'./visual-refresh-secondary/index.js',
...(INCLUDE_ONE_THEME ? ['./one-theme/index.js'] : []),
],
secondaryThemePaths: [],
metadataPath: './classic/metadata.js',
alwaysVisualRefresh: false,
},
{
name: 'one-theme',
packageJson: { name: '@cloudscape-design/components' },
designTokensOutput: 'index',
designTokensDir: 'design-tokens',
designTokensPackageJson: { name: '@cloudscape-design/design-tokens' },
outputPath: path.join(workspace.targetPath, 'components'),
primaryThemePath: './one-theme/index.js',
secondaryThemePaths: [],
metadataPath: './visual-refresh/metadata.js',
alwaysVisualRefresh: true,
},
];

// Build only the selected theme (default: 'default' which is core-open-source)
const themes = allThemes.filter(t => t.name === THEME);

if (themes.length === 0) {
const available = allThemes.map(t => t.name).join(', ');
throw new Error(`Unknown theme "${THEME}". Available themes: ${available}`);
}

module.exports = themes;
2 changes: 1 addition & 1 deletion pages/app-layout/with-table-and-sticky-offset.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function () {
<Table<Instance>
header={<Header variant="awsui-h1-sticky">Sticky Scrollbar Example</Header>}
// manually set vertical offset to test this feature
stickyHeaderVerticalOffset={urlParams.visualRefresh ? 57 : 45}
stickyHeaderVerticalOffset={57}
stickyHeader={true}
variant="full-page"
columnDefinitions={columnsConfig}
Expand Down
6 changes: 0 additions & 6 deletions pages/app/app-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,9 @@ import mapValues from 'lodash/mapValues';

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

import { THEME } from '~components/internal/environment';

interface AppUrlParams {
density: Density;
direction: 'ltr' | 'rtl';
visualRefresh: boolean;
oneTheme: boolean;
motionDisabled: boolean;
appLayoutWidget: boolean;
mode?: Mode;
Expand All @@ -32,8 +28,6 @@ const appContextDefaults: AppContextType = {
urlParams: {
density: Density.Comfortable,
direction: 'ltr',
visualRefresh: THEME === 'default',
oneTheme: false,
motionDisabled: false,
appLayoutWidget: false,
},
Expand Down
37 changes: 0 additions & 37 deletions pages/app/components/theme-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,52 +4,15 @@ import React, { useContext } from 'react';

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

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',
};

if (ALWAYS_VISUAL_REFRESH) {
vrSwitchProps.checked = true;
vrSwitchProps.readOnly = true;
} else {
vrSwitchProps.checked = urlParams.visualRefresh && !urlParams.oneTheme;
vrSwitchProps.onChange = event => activateTheme(event.target.checked ? 'visualRefresh' : 'classic');
}

return (
<SpaceBetween direction="horizontal" size="xs">
<label>
<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')}
/>
One theme
</label>
)}
<label>
<input
id="mode-toggle"
Expand Down
11 changes: 1 addition & 10 deletions pages/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,10 @@ interface GlobalFlags {
interface CustomFlags {
appLayoutDelayedWidget?: boolean;
}
const awsuiVisualRefreshFlag = Symbol.for('awsui-visual-refresh-flag');
const awsuiGlobalFlagsSymbol = Symbol.for('awsui-global-flags');
const awsuiCustomFlagsSymbol = Symbol.for('awsui-custom-flags');

interface ExtendedWindow extends Window {
[awsuiVisualRefreshFlag]?: () => boolean;
[awsuiGlobalFlagsSymbol]?: GlobalFlags;
[awsuiCustomFlagsSymbol]?: CustomFlags;
}
Expand Down Expand Up @@ -96,12 +94,10 @@ function App() {
}

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

// The VR class needs to be set before any React rendering occurs.
window[awsuiVisualRefreshFlag] = () => visualRefresh && !oneTheme;
if (!window[awsuiGlobalFlagsSymbol]) {
window[awsuiGlobalFlagsSymbol] = {};
}
Expand All @@ -112,11 +108,6 @@ 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
2 changes: 1 addition & 1 deletion pages/theming/integration.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const sharedItems = [
export default function () {
const { urlParams } = useContext(AppContext);
const [themed, setThemed] = useState<boolean>(false);
const [secondaryTheme, setSecondaryTheme] = useState<boolean>(urlParams.visualRefresh);
const [secondaryTheme, setSecondaryTheme] = useState<boolean>(false);
const [themeMethod, setThemeMethod] = useState<'applyTheme' | 'generateThemeStylesheet'>('applyTheme');

useEffect(() => {
Expand Down
5 changes: 2 additions & 3 deletions pages/webpack.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ const workspace = require('../build-tools/utils/workspace');

module.exports = () => {
const react18 = process.env.REACT_VERSION === '18';
const theme = process.env.THEME || 'default';
const themeDefinition = themes.find(t => t.name === theme);
const themeDefinition = themes[0];
return baseConfig({
componentsPath: path.resolve(themeDefinition.outputPath),
designTokensPath: path.resolve(
Expand All @@ -18,7 +17,7 @@ module.exports = () => {
themeDefinition.designTokensOutput
),
globalStylesPath: themeDefinition.globalStylesPath,
outputPath: `pages/lib/static-${theme}`,
outputPath: `pages/lib/static-${themeDefinition.name}`,
react18,
});
};
33 changes: 33 additions & 0 deletions style-dictionary/core-open-source/borders.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 { StyleDictionary } from '../utils/interfaces.js';

export const tokens: StyleDictionary.BordersDictionary = {
borderWidthButton: '1px',
borderWidthToken: '1px',
borderWidthAlert: '0px',
borderItemWidth: '1px',
borderWidthAlertInlineStart: '2px',
borderWidthItemSelected: '1px',
borderWidthCardSelected: '1px',

borderRadiusAlert: '2px',
borderRadiusBadge: '16px',
borderRadiusButton: '8px',
borderRadiusContainer: '12px',
borderRadiusDropdown: '8px',
borderRadiusDropzone: '8px',
borderRadiusFlashbar: '4px',
borderRadiusItem: '8px',
borderRadiusInput: '8px',
borderRadiusPopover: '8px',
borderRadiusTabsFocusRing: '10px',
borderRadiusToken: '8px',
borderRadiusTutorialPanelItem: '4px',

borderWidthIconSmall: '1.5px',
borderWidthIconNormal: '1.5px',
borderWidthIconMedium: '1.75px',
borderWidthIconBig: '2px',
borderWidthIconLarge: '2.5px',
};
82 changes: 82 additions & 0 deletions style-dictionary/core-open-source/colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { ReferenceTokens } from '@cloudscape-design/theming-build';

import { expandColorDictionary, expandReferenceTokens } from '../utils/index.js';
import { StyleDictionary } from '../utils/interfaces.js';

const tokens: StyleDictionary.ColorsDictionary = {
colorTextBodyDefault: { light: '#0f141a', dark: '#ccccd1' },

colorBorderButtonNormalDefault: { light: '#1b232d', dark: '#f3f3f7' },
colorBorderButtonNormalHover: { light: '#1b232d', dark: '#f9f9fb' },
colorBorderButtonNormalActive: { light: '#1b232d', dark: '#f9f9fb' },
colorBackgroundButtonNormalDefault: { light: '#ffffff', dark: '#161d26' },
colorBackgroundButtonNormalHover: { light: '#f6f6f9', dark: '#424650' },
colorBackgroundButtonNormalActive: { light: '#ebebf0', dark: '#131920' },
colorTextButtonNormalDefault: { light: '#1b232d', dark: '#f3f3f7' },
colorTextButtonNormalHover: { light: '#1b232d', dark: '#f9f9fb' },
colorTextButtonNormalActive: { light: '#1b232d', dark: '#f9f9fb' },

colorBackgroundButtonPrimaryDefault: { light: '#1b232d', dark: '#f9f9fb' },
colorBackgroundButtonPrimaryHover: { light: '#06080a', dark: '#ffffff' },
colorBackgroundButtonPrimaryActive: { light: '#1b232d', dark: '#f9f9fb' },
colorTextButtonPrimaryDefault: { light: '#ffffff', dark: '#131920' },
colorTextButtonPrimaryHover: { light: '#ffffff', dark: '#131920' },
colorTextButtonPrimaryActive: { light: '#ffffff', dark: '#131920' },

colorBackgroundButtonLinkDefault: { light: '#f6f6f9', dark: '#232b37' },
colorBackgroundButtonLinkHover: { light: '#ebebf0', dark: '#424650' },
colorBackgroundButtonLinkActive: { light: '#ebebf0', dark: '#131920' },
colorTextLinkButtonNormalDefault: { light: '#1b232d', dark: '#f9f9fb' },

colorBackgroundToggleButtonNormalPressed: { light: '#ebebf0', dark: '#131920' },
colorBorderToggleButtonNormalPressed: { light: '#1b232d', dark: '#f9f9fb' },
colorTextToggleButtonNormalPressed: { light: '#1b232d', dark: '#f9f9fb' },

colorBackgroundControlChecked: { light: '#1b232d', dark: '#f9f9fb' },

colorTextLinkDefault: { light: '#0f141a', dark: '#ccccd1' },
colorTextLinkHover: { light: '#424650', dark: '#ffffff' },
colorTextLinkSecondaryDefault: { light: '#295eff', dark: '#7598ff' },
colorTextLinkSecondaryHover: { light: '#0033cc', dark: '#94afff' },
colorTextLinkInfoDefault: { light: '#295eff', dark: '#7598ff' },
colorTextLinkInfoHover: { light: '#0033cc', dark: '#94afff' },
colorTextAccent: { light: '#1b232d', dark: '#f9f9fb' },

colorBorderItemFocused: { light: '#1b232d', dark: '#f9f9fb' },
colorBorderItemSelected: { light: '#1b232d', dark: '#f9f9fb' },
colorBackgroundItemSelected: { light: '#f6f6f9', dark: '#0f141a' },
colorBackgroundLayoutToggleSelectedDefault: { light: '#1b232d', dark: '#f9f9fb' },

colorBackgroundSegmentActive: { light: '#1b232d', dark: '#f9f9fb' },

colorBackgroundSliderRangeDefault: { light: '#1b232d', dark: '#f9f9fb' },
colorBackgroundSliderHandleDefault: { light: '#1b232d', dark: '#f9f9fb' },

colorBackgroundProgressBarValueDefault: { light: '#1b232d', dark: '#f9f9fb' },

colorBackgroundNotificationGreen: { light: '#008559', dark: '#008559' },
colorBackgroundNotificationBlue: { light: '#0033cc', dark: '#0033cc' },
colorTextNotificationDefault: { light: '#ffffff', dark: '#ffffff' },

colorTextStatusInfo: { light: '#0033cc', dark: '#7598ff' },
colorTextStatusSuccess: { light: '#008559', dark: '#00bd6b' },
colorTextDropdownItemFilterMatch: { light: '#1b232d', dark: '#f9f9fb' },
colorBackgroundDropdownItemFilterMatch: { light: '#f3f3f7', dark: '#06080a' },

colorTextBreadcrumbCurrent: { light: '#656871', dark: '#8c8c94' },
};

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

export const referenceTokens: ReferenceTokens = expandReferenceTokens({
color: {
primary: {
seed: '#1b232d',
},
},
});

export { expandedTokens as tokens };
export const mode: StyleDictionary.ModeIdentifier = 'color';
Loading
Loading