diff --git a/config/eslint-config-carbon/plugins/react.js b/config/eslint-config-carbon/plugins/react.js
index 2dbc3da652b3..b27f7046ba58 100644
--- a/config/eslint-config-carbon/plugins/react.js
+++ b/config/eslint-config-carbon/plugins/react.js
@@ -49,7 +49,7 @@ module.exports = {
// In these cases, we don't need to handle prop type validation like we
// would for code we ship to users.
{
- files: ['*-story.js'],
+ files: ['*-story.js', '*.stories.js'],
rules: {
'react/display-name': 0,
'react/prop-types': 0,
diff --git a/packages/carbon-react/.storybook/styles.scss b/packages/carbon-react/.storybook/styles.scss
index 8187462b27e5..23207583f534 100644
--- a/packages/carbon-react/.storybook/styles.scss
+++ b/packages/carbon-react/.storybook/styles.scss
@@ -5,10 +5,6 @@
// LICENSE file in the root directory of this source tree.
//
-$feature-flags: (
- enable-v11-release: true,
-);
-
@use '../index.scss' as styles with (
$css--font-face: true,
$css--plex-arabic: true,
@@ -32,6 +28,10 @@ $feature-flags: (
@include styles.theme(styles.$g100, button.$g100, tag.$g100);
}
+body {
+ background: styles.$background;
+}
+
html[lang='en'] body {
font-family: 'IBM Plex Sans', 'Helvetica Neue', Arial, sans-serif;
}
diff --git a/packages/carbon-react/src/components/Notification/Notification.stories.js b/packages/carbon-react/src/components/Notification/Notification.stories.js
index 97a5e3a6eb4b..443866f0117c 100644
--- a/packages/carbon-react/src/components/Notification/Notification.stories.js
+++ b/packages/carbon-react/src/components/Notification/Notification.stories.js
@@ -25,6 +25,11 @@ const toastNotificationProps = () => ({
export default {
title: 'Components/Notifications',
+ parameters: {
+ controls: {
+ hideNoControlsWarning: true,
+ },
+ },
};
export const Toast = () => (
@@ -35,6 +40,45 @@ export const Toast = () => (
/>
);
+export const ToastPlayground = ({
+ kind = 'info',
+ title = 'Notification title',
+ subtitle = 'Notification subtitle',
+ caption = '00:00:00 AM',
+ lowContrast = false,
+}) => {
+ return (
+
+ );
+};
+ToastPlayground.argTypes = {
+ kind: {
+ options: [
+ 'error',
+ 'info',
+ 'info-square',
+ 'success',
+ 'warning',
+ 'warning-alt',
+ ],
+ control: {
+ type: 'select',
+ },
+ },
+ lowContrast: {
+ value: false,
+ control: {
+ type: 'boolean',
+ },
+ },
+};
+
export const Inline = () => (
{
- test('Public API', async () => {
- const { unwrap } = await render(`
- @use 'sass:map';
- @use 'sass:meta';
- @use '../notification';
-
- $_: get('mixin', meta.mixin-exists('inline-notification', 'notification'));
- `);
- expect(unwrap('mixin')).toBe(true);
- });
-});
diff --git a/packages/styles/scss/components/__tests__/notification-test.js b/packages/styles/scss/components/__tests__/notification-test.js
new file mode 100644
index 000000000000..0205796c3155
--- /dev/null
+++ b/packages/styles/scss/components/__tests__/notification-test.js
@@ -0,0 +1,44 @@
+/**
+ * Copyright IBM Corp. 2018, 2018
+ *
+ * This source code is licensed under the Apache-2.0 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @jest-environment node
+ */
+
+'use strict';
+
+const { SassRenderer } = require('@carbon/test-utils/scss');
+
+const { render } = SassRenderer.create(__dirname);
+
+describe('scss/components/notification', () => {
+ test('Public API', async () => {
+ const { unwrap } = await render(`
+ @use 'sass:map';
+ @use 'sass:meta';
+ @use '../notification';
+
+ $_: get('mixins', (
+ toast-notification: meta.mixin-exists('toast-notification', 'notification'),
+ inline-notification: meta.mixin-exists('inline-notification', 'notification'),
+ ));
+
+ $_: get('tokens', map.keys(meta.module-variables('notification')));
+ `);
+
+ expect(unwrap('mixins')).toEqual({
+ 'toast-notification': true,
+ 'inline-notification': true,
+ });
+ expect(unwrap('tokens')).toEqual([
+ 'notification-background-error',
+ 'notification-background-success',
+ 'notification-background-info',
+ 'notification-background-warning',
+ 'notification-action-hover',
+ 'notification-tokens',
+ ]);
+ });
+});
diff --git a/packages/styles/scss/components/__tests__/toast-notification-test.js b/packages/styles/scss/components/__tests__/toast-notification-test.js
deleted file mode 100644
index f688a3ad9bf7..000000000000
--- a/packages/styles/scss/components/__tests__/toast-notification-test.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Copyright IBM Corp. 2018, 2018
- *
- * This source code is licensed under the Apache-2.0 license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @jest-environment node
- */
-
-'use strict';
-
-const { SassRenderer } = require('@carbon/test-utils/scss');
-
-const { render } = SassRenderer.create(__dirname);
-
-describe('scss/components/notification', () => {
- test('Public API', async () => {
- const { unwrap } = await render(`
- @use 'sass:map';
- @use 'sass:meta';
- @use '../notification';
-
- $_: get('mixin', meta.mixin-exists('toast-notification', 'notification'));
- `);
- expect(unwrap('mixin')).toBe(true);
- });
-});
diff --git a/packages/styles/scss/components/notification/_index.scss b/packages/styles/scss/components/notification/_index.scss
index d281b0723465..83f68110c448 100644
--- a/packages/styles/scss/components/notification/_index.scss
+++ b/packages/styles/scss/components/notification/_index.scss
@@ -9,8 +9,11 @@
@forward 'inline-notification';
@forward 'toast-notification';
-@use 'inline-notification';
-@use 'toast-notification';
+@use '../../theme';
+@use './tokens';
+@use './inline-notification';
+@use './toast-notification';
+@include theme.add-component-tokens(tokens.$notification-tokens);
@include inline-notification.inline-notification;
@include toast-notification.toast-notification;
diff --git a/packages/styles/scss/components/notification/_inline-notification.scss b/packages/styles/scss/components/notification/_inline-notification.scss
index fc57355905a3..33cc79cebdb5 100644
--- a/packages/styles/scss/components/notification/_inline-notification.scss
+++ b/packages/styles/scss/components/notification/_inline-notification.scss
@@ -15,9 +15,9 @@
@use '../../themes' as *;
@use '../../type' as *;
@use '../../utilities/convert' as *;
-@use '../../utilities/component-tokens' as *;
@use '../../utilities/high-contrast-mode' as *;
@use '../../utilities/focus-outline' as *;
+@use './tokens' as *;
/// Inline notification styles
/// @access public
@@ -102,7 +102,7 @@
.#{$prefix}--inline-notification--low-contrast.#{$prefix}--inline-notification--error {
@include notification--experimental(
$support-error,
- get-token(var(--cds-background-error))
+ $notification-background-error
);
&::before {
@@ -120,7 +120,7 @@
.#{$prefix}--inline-notification--low-contrast.#{$prefix}--inline-notification--success {
@include notification--experimental(
$support-success,
- get-token-value(var(--cds-background-success))
+ $notification-background-success
);
&::before {
@@ -140,7 +140,7 @@
.#{$prefix}--inline-notification--low-contrast.#{$prefix}--inline-notification--info-square {
@include notification--experimental(
$support-info,
- get-token-value(var(--cds-background-info))
+ $notification-background-info
);
&::before {
@@ -160,7 +160,7 @@
.#{$prefix}--inline-notification--low-contrast.#{$prefix}--inline-notification--warning-alt {
@include notification--experimental(
$support-warning,
- get-token-value(var(--cds-background-warning))
+ $notification-background-warning
);
&::before {
@@ -241,7 +241,7 @@
.#{$prefix}--inline-notification__action-button.#{$prefix}--btn--ghost:active,
.#{$prefix}--inline-notification--low-contrast
.#{$prefix}--inline-notification__action-button.#{$prefix}--btn--ghost:hover {
- background-color: get-token-value(var(--cds-action-hover));
+ background-color: $notification-action-hover;
}
.#{$prefix}--inline-notification__action-button.#{$prefix}--btn--ghost:focus {
diff --git a/packages/styles/scss/components/notification/_toast-notification.scss b/packages/styles/scss/components/notification/_toast-notification.scss
index 552a79879cda..6a187e55dbc5 100644
--- a/packages/styles/scss/components/notification/_toast-notification.scss
+++ b/packages/styles/scss/components/notification/_toast-notification.scss
@@ -19,6 +19,7 @@
@use '../../utilities/convert' as *;
@use '../../utilities/high-contrast-mode' as *;
@use '../../utilities/focus-outline' as *;
+@use './tokens' as *;
/// Toast notification styles
/// @access public
@@ -82,7 +83,7 @@
.#{$prefix}--toast-notification--low-contrast.#{$prefix}--toast-notification--error {
@include notification--experimental(
$support-error,
- get-token(var(--cds-background-error))
+ $notification-background-error
);
}
@@ -96,7 +97,7 @@
.#{$prefix}--toast-notification--low-contrast.#{$prefix}--toast-notification--success {
@include notification--experimental(
$support-success,
- get-token(var(--cds-background-success))
+ $notification-background-success
);
}
@@ -112,7 +113,7 @@
.#{$prefix}--toast-notification--low-contrast.#{$prefix}--toast-notification--info-square {
@include notification--experimental(
$support-info,
- get-token(var(--cds-background-info))
+ $notification-background-info
);
}
@@ -128,7 +129,7 @@
.#{$prefix}--toast-notification--low-contrast.#{$prefix}--toast-notification--warning-alt {
@include notification--experimental(
$support-warning,
- get-token(var(--cds-background-warning))
+ $notification-background-warning
);
}
diff --git a/packages/styles/scss/components/notification/_tokens.scss b/packages/styles/scss/components/notification/_tokens.scss
index d172d84ad1f2..b9985e3a2c0d 100644
--- a/packages/styles/scss/components/notification/_tokens.scss
+++ b/packages/styles/scss/components/notification/_tokens.scss
@@ -7,120 +7,149 @@
@use 'sass:color';
@use '../../colors';
-@use '../../theme' as *;
@use '../../themes';
+@use '../../theme';
@use '../../utilities/component-tokens';
-// prettier-ignore
-$-tokens: (
- 'notification-background-error': (
- fallback: colors.$red-10,
- values: (
- (
- theme: themes.$white,
- value: colors.$red-10,
- ),
- (
- theme: themes.$g10,
- value: colors.$red-10,
- ),
- (
- theme: themes.$g90,
- value: $layer,
- ),
- (
- theme: themes.$g100,
- value: $layer,
- ),
+$notification-background-error: (
+ fallback: colors.$red-10,
+ values: (
+ (
+ theme: themes.$white,
+ value: colors.$red-10,
+ ),
+ (
+ theme: themes.$g10,
+ value: colors.$red-10,
+ ),
+ (
+ theme: themes.$g90,
+ value: theme.$layer,
+ ),
+ (
+ theme: themes.$g100,
+ value: theme.$layer,
),
),
- 'notification-background-success': (
- fallback: colors.$green-10,
- values: (
- (
- theme: themes.$white,
- value: colors.$green-10,
- ),
- (
- theme: themes.$g10,
- value: colors.$green-10,
- ),
- (
- theme: themes.$g90,
- value: $layer,
- ),
- (
- theme: themes.$g100,
- value: $layer,
- ),
+) !default;
+
+$notification-background-success: (
+ fallback: colors.$green-10,
+ values: (
+ (
+ theme: themes.$white,
+ value: colors.$green-10,
+ ),
+ (
+ theme: themes.$g10,
+ value: colors.$green-10,
+ ),
+ (
+ theme: themes.$g90,
+ value: theme.$layer,
+ ),
+ (
+ theme: themes.$g100,
+ value: theme.$layer,
),
),
- 'notification-background-info': (
- fallback: colors.$blue-10,
- values: (
- (
- theme: themes.$white,
- value: colors.$blue-10,
- ),
- (
- theme: themes.$g10,
- value: colors.$blue-10,
- ),
- (
- theme: themes.$g90,
- value: $layer,
- ),
- (
- theme: themes.$g100,
- value: $layer,
- ),
+) !default;
+
+$notification-background-info: (
+ fallback: colors.$blue-10,
+ values: (
+ (
+ theme: themes.$white,
+ value: colors.$blue-10,
+ ),
+ (
+ theme: themes.$g10,
+ value: colors.$blue-10,
+ ),
+ (
+ theme: themes.$g90,
+ value: theme.$layer,
+ ),
+ (
+ theme: themes.$g100,
+ value: theme.$layer,
),
),
- 'notification-background-warning': (
- fallback: mix(colors.$yellow-30, colors.$white-0, 15%),
- values: (
- (
- theme: themes.$white,
- value: mix(colors.$yellow-30, colors.$white-0, 15%),
- ),
- (
- theme: themes.$g10,
- value: mix(colors.$yellow-30, colors.$white-0, 15%),
- ),
- (
- theme: themes.$g90,
- value: $layer,
- ),
- (
- theme: themes.$g100,
- value: $layer,
- ),
+) !default;
+
+$notification-background-warning: (
+ fallback: color.mix(colors.$yellow-30, colors.$white-0, 15%),
+ values: (
+ (
+ theme: themes.$white,
+ value: color.mix(colors.$yellow-30, colors.$white-0, 15%),
+ ),
+ (
+ theme: themes.$g10,
+ value: color.mix(colors.$yellow-30, colors.$white-0, 15%),
+ ),
+ (
+ theme: themes.$g90,
+ value: theme.$layer,
+ ),
+ (
+ theme: themes.$g100,
+ value: theme.$layer,
),
),
- 'notification-action-hover': (
- fallback: colors.$white-0,
- values: (
- (
- theme: themes.$white,
- value: colors.$white-0,
- ),
- (
- theme: themes.$g10,
- value: colors.$white-0,
- ),
- (
- theme: themes.$g90,
- value: $layer-hover,
- ),
- (
- theme: themes.$g100,
- value: $layer-hover,
- ),
+) !default;
+
+$notification-action-hover: (
+ fallback: colors.$white-0,
+ values: (
+ (
+ theme: themes.$white,
+ value: colors.$white-0,
+ ),
+ (
+ theme: themes.$g10,
+ value: colors.$white-0,
+ ),
+ (
+ theme: themes.$g90,
+ value: theme.$layer-hover,
+ ),
+ (
+ theme: themes.$g100,
+ value: theme.$layer-hover,
),
),
+) !default;
+
+$notification-tokens: (
+ notification-background-error: $notification-background-error,
+ notification-background-success: $notification-background-success,
+ notification-background-info: $notification-background-info,
+ notification-background-warning: $notification-background-warning,
+ notification-action-hover: $notification-action-hover,
+);
+
+$notification-background-error: component-tokens.get-var(
+ $notification-background-error,
+ 'notification-background-error'
);
-$white: component-tokens.get-tokens($-tokens, themes.$white);
-$g10: component-tokens.get-tokens($-tokens, themes.$g10);
-$g90: component-tokens.get-tokens($-tokens, themes.$g90);
-$g100: component-tokens.get-tokens($-tokens, themes.$g100);
+$notification-background-success: component-tokens.get-var(
+ $notification-background-success,
+ 'notification-background-success'
+);
+
+$notification-background-info: component-tokens.get-var(
+ $notification-background-info,
+ 'notification-background-info'
+);
+
+$notification-background-warning: component-tokens.get-var(
+ $notification-background-warning,
+ 'notification-background-warning'
+);
+
+$notification-action-hover: component-tokens.get-var(
+ $notification-action-hover,
+ 'notification-action-hover'
+);
diff --git a/packages/styles/scss/utilities/_component-tokens.scss b/packages/styles/scss/utilities/_component-tokens.scss
index bc578c570792..bc6f50e2a198 100644
--- a/packages/styles/scss/utilities/_component-tokens.scss
+++ b/packages/styles/scss/utilities/_component-tokens.scss
@@ -6,6 +6,10 @@
//
@use 'sass:map';
+@use 'sass:meta';
+@use '../themes';
+@use '../theme';
+@use './custom-property';
/// Extract the component tokens from a given theme
/// @param {SassMap} $tokens
@@ -25,3 +29,27 @@
@return $result;
}
+
+/// Get the CSS Custom Property value for a given token map
+/// @param {any} $token-map The possible values for the token, this value can
+/// be a plain value used as a CSS value or a Sass Map
+/// @param {String} $name The name of the CSS Custom Property
+@function get-var($token-map, $name) {
+ @if meta.type-of($token-map) == map {
+ $fallback: map.get($token-map, fallback);
+ $theme-values: map.get($token-map, values);
+
+ @each $theme-value in $theme-values {
+ $theme: map.get($theme-value, theme);
+ $value: map.get($theme-value, value);
+
+ @if theme.matches($theme, theme.$theme) {
+ @return custom-property.get-var($name, $value);
+ }
+ }
+
+ @return custom-property.get-var($name, $fallback);
+ }
+
+ @return custom-property.get-var($name, $token-map);
+}
diff --git a/packages/themes/scss/modules/_theme.scss b/packages/themes/scss/modules/_theme.scss
index 5b475d412879..f56a411bcd0f 100644
--- a/packages/themes/scss/modules/_theme.scss
+++ b/packages/themes/scss/modules/_theme.scss
@@ -20,6 +20,9 @@ $fallback: themes.$white !default;
$theme: () !default;
$theme: map.merge($fallback, $theme);
+/// Local component tokens that can be updated with `@mixin add-component-tokens`.
+$-component-tokens: ();
+
/// Include the CSS Custom Properties for the active theme or a given theme on
/// a selector
@mixin theme($active-theme: $theme, $component-tokens...) {
@@ -32,6 +35,13 @@ $theme: map.merge($fallback, $theme);
@include -custom-property($token, $value);
}
}
+
+ @each $token, $value in $-component-tokens {
+ @include -custom-property(
+ $token,
+ -resolve-token-value($active-theme, $value)
+ );
+ }
}
/// Retrieve the value for the given $token from the current $theme
@@ -43,6 +53,56 @@ $theme: map.merge($fallback, $theme);
@error "Unable to find token: #{$token} in current $theme";
}
+/// Compare two themes to see if the second theme is a superset of the first
+/// theme. In other words, this function will return true if every token in the
+/// first theme is available in the second theme and has the same value. The
+/// second theme is allowed to have additional values and it will still match.
+/// @param {Map} $a
+/// @param {Map} $b
+/// @returns {Boolean}
+@function matches($a, $b) {
+ @each $key, $value in $a {
+ @if map.has-key($b, $key) == false {
+ @return false;
+ }
+
+ @if map.get($b, $key) != $value {
+ @return false;
+ }
+ }
+
+ @return true;
+}
+
+/// Add component tokens which will be included any time the theme mixin is
+/// called
+@mixin add-component-tokens($token-map) {
+ $-component-tokens: map.merge($-component-tokens, $token-map) !global;
+}
+
+/// Determine the value from a component token that matches the given theme.
+/// This is helpful when a component token may change depending on what theme the
+/// component is being rendered in.
+@function -resolve-token-value($active-theme: $theme, $token-value) {
+ @if meta.type-of($token-value) == map and map.has-key($token-value, values) {
+ $fallback: map.get($token-value, fallback);
+ $theme-values: map.get($token-value, values);
+
+ @each $theme-value in $theme-values {
+ $theme-to-match: map.get($theme-value, theme);
+ $value: map.get($theme-value, value);
+
+ @if matches($theme-to-match, $active-theme) {
+ @return $value;
+ }
+ }
+
+ @return $fallback;
+ }
+
+ @return $token-value;
+}
+
/// @access private
/// @group @carbon/themes
@mixin -custom-property($name, $value) {