@bethinkpl/design-system 37.0.6 → 38.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +1 -2
  2. package/dist/design-system.css +1 -1
  3. package/dist/design-system.js +6454 -6404
  4. package/dist/design-system.js.map +1 -1
  5. package/dist/lib/js/components/Banner/Banner.consts.d.ts +1 -0
  6. package/dist/lib/js/components/Banner/Banner.vue.d.ts +6 -0
  7. package/dist/lib/js/components/Buttons/Button/Button.consts.d.ts +1 -0
  8. package/dist/lib/js/components/Buttons/Button/useMagicGradient.d.ts +5 -0
  9. package/dist/lib/js/components/Buttons/IconButton/IconButton.consts.d.ts +1 -0
  10. package/dist/lib/js/components/Buttons/IconButton/IconButton.vue.d.ts +5 -2
  11. package/dist/lib/js/components/Drawer/DrawerHeader/DrawerHeader.vue.d.ts +3 -0
  12. package/dist/lib/js/components/Drawer/DrawerSection/DrawerSection.vue.d.ts +5 -0
  13. package/dist/lib/js/components/Headers/OverlayHeader/OverlayHeader.vue.d.ts +4 -0
  14. package/dist/lib/js/components/Headers/SectionHeader/SectionHeader.vue.d.ts +4 -0
  15. package/dist/lib/js/components/Modals/Modal/Modal.vue.d.ts +5 -0
  16. package/dist/lib/js/components/Modals/ModalDialog/ModalDialog.vue.d.ts +5 -0
  17. package/dist/lib/js/components/Pagination/Pagination.vue.d.ts +4 -0
  18. package/dist/lib/js/components/SurveyQuestions/SurveyQuestionOpenEnded/SurveyQuestionOpenEnded.vue.d.ts +4 -0
  19. package/dist/lib/js/components/SurveyQuestions/SurveyQuestionScale/SurveyQuestionScale.vue.d.ts +4 -0
  20. package/dist/lib/js/components/Tile/Tile.sb.shared.d.ts +10 -0
  21. package/dist/lib/js/components/Toast/Toast.vue.d.ts +1 -0
  22. package/lib/js/components/Banner/Banner.consts.ts +1 -0
  23. package/lib/js/components/Banner/Banner.spec.ts +15 -0
  24. package/lib/js/components/Banner/Banner.stories.ts +5 -0
  25. package/lib/js/components/Banner/Banner.vue +16 -1
  26. package/lib/js/components/Buttons/Button/Button.consts.ts +1 -0
  27. package/lib/js/components/Buttons/Button/Button.spec.ts +35 -0
  28. package/lib/js/components/Buttons/Button/Button.vue +19 -0
  29. package/lib/js/components/Buttons/Button/useMagicGradient.spec.ts +47 -0
  30. package/lib/js/components/Buttons/Button/useMagicGradient.ts +50 -0
  31. package/lib/js/components/Buttons/IconButton/IconButton.consts.ts +1 -0
  32. package/lib/js/components/Buttons/IconButton/IconButton.spec.ts +107 -0
  33. package/lib/js/components/Buttons/IconButton/IconButton.stories.ts +5 -1
  34. package/lib/js/components/Buttons/IconButton/IconButton.vue +45 -5
  35. package/lib/js/components/DatePickers/DatePicker/DatePicker.vue +5 -0
  36. package/lib/js/components/DatePickers/DateRangePicker/DateRangePicker.vue +5 -0
  37. package/lib/js/components/Switch/Switch.vue +3 -3
  38. package/lib/styles/components/_buttons.scss +96 -1
  39. package/lib/styles/design-system.scss +9 -3
  40. package/lib/styles/mixins/_scrollbars.scss +6 -2
  41. package/lib/styles/settings/_gradients-variables.scss +34 -0
  42. package/lib/styles/settings/_gradients.scss +4 -0
  43. package/lib/styles/settings/colors/_magic-variables.scss +10 -0
  44. package/lib/styles/settings/colors/_magic.scss +9 -0
  45. package/lib/styles/settings/colors/_raw-bodywork-dark.scss +22 -0
  46. package/lib/styles/settings/colors/_raw-mc-dark.scss +22 -0
  47. package/lib/styles/settings/colors/_raw-wnl-dark.scss +22 -0
  48. package/lib/styles/settings/colors/_raw.json +1 -1
  49. package/lib/styles/settings/colors/_raw.scss +26 -0
  50. package/lib/styles/settings/colors/_tokens-variables-dark.scss +513 -0
  51. package/package.json +1 -1
@@ -21,6 +21,17 @@
21
21
  '-ds-elevation-x-small': elevation === BUTTON_ELEVATIONS.X_SMALL,
22
22
  '-ds-elevation-small': elevation === BUTTON_ELEVATIONS.SMALL,
23
23
  }"
24
+ :style="
25
+ color === BUTTON_COLORS.MAGIC
26
+ ? {
27
+ '--ds-magic-fill': `url(#${
28
+ state === BUTTON_STATES.DISABLED
29
+ ? GRADIENT_MAGIC_ICON_DISABLED_ID
30
+ : GRADIENT_MAGIC_ICON_ID
31
+ })`,
32
+ }
33
+ : undefined
34
+ "
24
35
  >
25
36
  <span class="ds-button__content">
26
37
  <wnl-icon
@@ -65,6 +76,11 @@ import {
65
76
  ButtonElevation,
66
77
  } from './Button.consts';
67
78
  import { computed } from 'vue';
79
+ import {
80
+ GRADIENT_MAGIC_ICON_DISABLED_ID,
81
+ GRADIENT_MAGIC_ICON_ID,
82
+ useMagicGradient,
83
+ } from './useMagicGradient';
68
84
 
69
85
  const {
70
86
  size = BUTTON_SIZES.MEDIUM,
@@ -100,6 +116,9 @@ const colorClassName = computed((): string => {
100
116
  return `-ds-color-${color}`;
101
117
  });
102
118
 
119
+ // The loading spinner's icon fills via `url(#MAGIC_GRADIENT_ID)`; ensure that def exists.
120
+ useMagicGradient(() => color === BUTTON_COLORS.MAGIC);
121
+
103
122
  const loadingIconSize = computed((): IconSize => {
104
123
  if (size === BUTTON_SIZES.LARGE) {
105
124
  return ICON_SIZES.MEDIUM;
@@ -0,0 +1,47 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
+ import { ref } from 'vue';
3
+
4
+ import { GRADIENT_MAGIC_ICON_ID, useMagicGradient } from './useMagicGradient';
5
+
6
+ describe('useMagicGradient', () => {
7
+ beforeEach(() => {
8
+ document.getElementById(GRADIENT_MAGIC_ICON_ID)?.remove();
9
+ });
10
+ afterEach(() => {
11
+ document.getElementById(GRADIENT_MAGIC_ICON_ID)?.remove();
12
+ });
13
+
14
+ it('injects a single linearGradient with the hardcoded id when enabled', () => {
15
+ useMagicGradient(true);
16
+
17
+ const gradient = document.getElementById(GRADIENT_MAGIC_ICON_ID);
18
+ expect(gradient).not.toBeNull();
19
+ expect(gradient?.tagName.toLowerCase()).toBe('lineargradient');
20
+ });
21
+
22
+ it('does not inject the def when disabled', () => {
23
+ useMagicGradient(false);
24
+
25
+ expect(document.getElementById(GRADIENT_MAGIC_ICON_ID)).toBeNull();
26
+ });
27
+
28
+ it('injects the def when the reactive source becomes enabled', async () => {
29
+ const enabled = ref(false);
30
+ useMagicGradient(enabled);
31
+
32
+ expect(document.getElementById(GRADIENT_MAGIC_ICON_ID)).toBeNull();
33
+
34
+ enabled.value = true;
35
+ await Promise.resolve();
36
+
37
+ expect(document.getElementById(GRADIENT_MAGIC_ICON_ID)).not.toBeNull();
38
+ });
39
+
40
+ it('is idempotent — multiple enabled consumers leave exactly one def', () => {
41
+ useMagicGradient(true);
42
+ useMagicGradient(() => true);
43
+ useMagicGradient(ref(true));
44
+
45
+ expect(document.querySelectorAll(`#${GRADIENT_MAGIC_ICON_ID}`)).toHaveLength(1);
46
+ });
47
+ });
@@ -0,0 +1,50 @@
1
+ import { toValue, watch, type MaybeRefOrGetter } from 'vue';
2
+
3
+ export const GRADIENT_MAGIC_ICON_ID = 'ds-gradient-magic-icon';
4
+ export const GRADIENT_MAGIC_ICON_DISABLED_ID = 'ds-gradient-magic-icon-disabled';
5
+
6
+ // SVG icons (the loading spinner) fill with the magic gradient via `fill: url(#...)`, which
7
+ // CSS gradients can't do. The gradient is identical for every magic button, so a single
8
+ // shared def is injected into <body> once and left for the page lifetime (invisible, 0×0).
9
+ function ensureMagicGradientDef(): void {
10
+ if (typeof document === 'undefined') {
11
+ return; // SSR guard
12
+ }
13
+ if (document.getElementById(GRADIENT_MAGIC_ICON_ID)) {
14
+ return; // already injected
15
+ }
16
+ // `var()` works in the `style` property but not in the `stop-color` presentation
17
+ // attribute, so stop colors are set inline.
18
+ document.body.insertAdjacentHTML(
19
+ 'beforeend',
20
+ `<svg aria-hidden="true" width="0" height="0" style="position:absolute">
21
+ <defs>
22
+ <linearGradient id="${GRADIENT_MAGIC_ICON_ID}" x1="0" y1="0" x2="1" y2="0.17">
23
+ <stop offset="7%" style="stop-color:var(--gradient-magic-color-1)"/>
24
+ <stop offset="36%" style="stop-color:var(--gradient-magic-color-2)"/>
25
+ <stop offset="65%" style="stop-color:var(--gradient-magic-color-3)"/>
26
+ <stop offset="95%" style="stop-color:var(--gradient-magic-color-4)"/>
27
+ </linearGradient>
28
+ <linearGradient id="${GRADIENT_MAGIC_ICON_DISABLED_ID}" x1="0" y1="0" x2="1" y2="0.17">
29
+ <stop offset="7.16%" style="stop-color:rgba(var(--gradient-magic-color-1-rgb), 0.3)"/>
30
+ <stop offset="36.18%" style="stop-color:rgba(var(--gradient-magic-color-2-rgb), 0.3)"/>
31
+ <stop offset="65.19%" style="stop-color:rgba(var(--gradient-magic-color-3-rgb), 0.3)"/>
32
+ <stop offset="95.08%" style="stop-color:rgba(var(--gradient-magic-color-4-rgb), 0.3)"/>
33
+ </linearGradient>
34
+ </defs>
35
+ </svg>`,
36
+ );
37
+ }
38
+
39
+ // Injects the shared magic-gradient def once `enabled` becomes truthy (immediate + reactive).
40
+ export function useMagicGradient(enabled: MaybeRefOrGetter<boolean>): void {
41
+ watch(
42
+ () => toValue(enabled),
43
+ (isEnabled) => {
44
+ if (isEnabled) {
45
+ ensureMagicGradientDef();
46
+ }
47
+ },
48
+ { immediate: true },
49
+ );
50
+ }
@@ -36,6 +36,7 @@ export const ICON_BUTTON_COLORS = {
36
36
  INVERTED: 'inverted',
37
37
  WARNING: 'warning',
38
38
  INFO: 'info',
39
+ MAGIC: 'magic',
39
40
  } as const;
40
41
 
41
42
  export type IconButtonColor = Value<typeof ICON_BUTTON_COLORS>;
@@ -0,0 +1,107 @@
1
+ import { afterEach, describe, expect, it, test } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+
4
+ import IconButton from './IconButton.vue';
5
+ import {
6
+ ICON_BUTTON_COLORS,
7
+ ICON_BUTTON_SIZES,
8
+ ICON_BUTTON_STATES,
9
+ ICON_BUTTON_TYPES,
10
+ } from './IconButton.consts';
11
+ import Button, { BUTTON_TYPES } from '../Button';
12
+ import Icon, { ICONS } from '../../Icons/Icon';
13
+ import { GRADIENT_MAGIC_ICON_ID } from '../Button/useMagicGradient';
14
+
15
+ describe('IconButton', () => {
16
+ const createComponent = (props = {} as any) => {
17
+ return mount(IconButton, {
18
+ props: {
19
+ icon: ICONS.FA_XMARK,
20
+ ...props,
21
+ },
22
+ });
23
+ };
24
+
25
+ it('should create', () => {
26
+ const component = createComponent();
27
+
28
+ expect(component.exists()).toBe(true);
29
+ });
30
+
31
+ test.each([
32
+ [{ props: { size: ICON_BUTTON_SIZES.X_SMALL }, expectedClass: '-ds-x-small' }],
33
+ [{ props: { size: ICON_BUTTON_SIZES.SMALL }, expectedClass: '-ds-small' }],
34
+ [{ props: { size: ICON_BUTTON_SIZES.LARGE }, expectedClass: '-ds-large' }],
35
+ [{ props: { state: ICON_BUTTON_STATES.HOVERED }, expectedClass: '-ds-hovered' }],
36
+ [{ props: { state: ICON_BUTTON_STATES.FOCUSED }, expectedClass: '-ds-focused' }],
37
+ [{ props: { state: ICON_BUTTON_STATES.DISABLED }, expectedClass: '-ds-disabled' }],
38
+ [{ props: { state: ICON_BUTTON_STATES.LOADING }, expectedClass: '-ds-loading' }],
39
+ [{ props: { color: ICON_BUTTON_COLORS.PRIMARY }, expectedClass: '-ds-color-primary' }],
40
+ [{ props: { color: ICON_BUTTON_COLORS.MAGIC }, expectedClass: '-ds-color-magic' }],
41
+ ])('correct class for props', ({ props, expectedClass }) => {
42
+ const component = createComponent(props);
43
+ expect(component.classes()).toContain(expectedClass);
44
+ });
45
+
46
+ it('renders the icon', () => {
47
+ const component = createComponent({ icon: ICONS.FA_CLOCK });
48
+
49
+ const icon = component.findComponent<typeof Icon>('.ds-iconButton__icon');
50
+ expect(icon.exists()).toBe(true);
51
+ expect(icon.props().icon).toEqual(ICONS.FA_CLOCK);
52
+ });
53
+
54
+ it('renders the spinner icon when loading', () => {
55
+ const component = createComponent({ state: ICON_BUTTON_STATES.LOADING });
56
+
57
+ const icon = component.findComponent<typeof Icon>('.ds-iconButton__icon');
58
+ expect(icon.props().icon).toEqual(ICONS.FAD_SPINNER_THIRD);
59
+ expect(icon.props().spinning).toBe(true);
60
+ });
61
+
62
+ test.each([
63
+ [{ type: ICON_BUTTON_TYPES.ICON_ONLY, expectedButtonType: BUTTON_TYPES.OUTLINED }],
64
+ [{ type: ICON_BUTTON_TYPES.OUTLINED, expectedButtonType: BUTTON_TYPES.OUTLINED }],
65
+ [{ type: ICON_BUTTON_TYPES.FILLED, expectedButtonType: BUTTON_TYPES.FILLED }],
66
+ ])('maps type to inner button type', ({ type, expectedButtonType }) => {
67
+ const component = createComponent({ type });
68
+
69
+ const button = component.findComponent<typeof Button>('.ds-iconButton__button');
70
+ expect(button.props().type).toBe(expectedButtonType);
71
+ });
72
+
73
+ it('marks the inner button as icon-only for the icon-only type', () => {
74
+ const component = createComponent({ type: ICON_BUTTON_TYPES.ICON_ONLY });
75
+
76
+ const button = component.findComponent<typeof Button>('.ds-iconButton__button');
77
+ expect(button.classes()).toContain('-ds-iconOnly');
78
+ });
79
+
80
+ describe('magic color', () => {
81
+ afterEach(() => {
82
+ document.getElementById(GRADIENT_MAGIC_ICON_ID)?.remove();
83
+ });
84
+
85
+ it('passes the magic color through to the inner button', () => {
86
+ const component = createComponent({
87
+ color: ICON_BUTTON_COLORS.MAGIC,
88
+ type: ICON_BUTTON_TYPES.OUTLINED,
89
+ });
90
+
91
+ const button = component.findComponent<typeof Button>('.ds-iconButton__button');
92
+ expect(button.props().color).toBe(ICON_BUTTON_COLORS.MAGIC);
93
+ });
94
+
95
+ it('injects the shared gradient paint server into the document when color is magic', () => {
96
+ createComponent({ color: ICON_BUTTON_COLORS.MAGIC });
97
+
98
+ expect(document.getElementById(GRADIENT_MAGIC_ICON_ID)).not.toBeNull();
99
+ });
100
+
101
+ it('does not inject the gradient paint server for non-magic colors', () => {
102
+ createComponent({ color: ICON_BUTTON_COLORS.PRIMARY });
103
+
104
+ expect(document.getElementById(GRADIENT_MAGIC_ICON_ID)).toBeNull();
105
+ });
106
+ });
107
+ });
@@ -45,7 +45,11 @@ const StoryTemplate: StoryFn<typeof IconButton> = (args) => ({
45
45
  :elevation="elevation"
46
46
  :color="color"
47
47
  :state="state"
48
- ><span v-if="label !== ''">{{label}}</span></icon-button>
48
+ >
49
+ <template v-if="label !== ''" #default>
50
+ <span>{{label}}</span>
51
+ </template>
52
+ </icon-button>
49
53
  </div>`,
50
54
  });
51
55
 
@@ -36,13 +36,10 @@
36
36
  class="ds-iconButton__button"
37
37
  :class="{
38
38
  '-ds-iconOnly': type === ICON_BUTTON_TYPES.ICON_ONLY,
39
- '-ds-hovered': state === ICON_BUTTON_STATES.HOVERED,
40
- '-ds-focused': state === ICON_BUTTON_STATES.FOCUSED,
41
- '-ds-disabled': state === ICON_BUTTON_STATES.DISABLED,
42
39
  }"
43
40
  :radius="radius"
44
41
  :type="buttonType"
45
- :state="isHovered ? ICON_BUTTON_STATES.HOVERED : ICON_BUTTON_STATES.DEFAULT"
42
+ :state="ICON_BUTTON_STATE_MAP[state]"
46
43
  :elevation="elevation"
47
44
  :color="isButtonColor ? color : null"
48
45
  >
@@ -61,6 +58,7 @@
61
58
  <style lang="scss" scoped>
62
59
  @import '../../../../styles/settings/animations';
63
60
  @import '../../../../styles/settings/buttons';
61
+ @import '../../../../styles/settings/gradients';
64
62
  @import '../../../../styles/settings/icons';
65
63
  @import '../../../../styles/settings/media-queries';
66
64
  @import '../../../../styles/settings/spacings';
@@ -140,7 +138,8 @@
140
138
  transition: color ease-in-out $default-transition-time;
141
139
 
142
140
  &:disabled,
143
- &.-ds-disabled {
141
+ &.-ds-disabled,
142
+ &.-ds-loading {
144
143
  pointer-events: none;
145
144
  }
146
145
 
@@ -148,6 +147,14 @@
148
147
  color: map-get($icon-button-colors, 'theme', 'hovered');
149
148
  }
150
149
 
150
+ &.-ds-color-magic {
151
+ #{$self}__icon {
152
+ :deep(svg path) {
153
+ fill: var(--ds-magic-fill, currentColor);
154
+ }
155
+ }
156
+ }
157
+
151
158
  // Doubled class selector increases specificity to ensure icon button styles never get overridden by .ds-button base styles
152
159
  #{$self}__button#{$self}__button {
153
160
  height: $icon-button-medium-size;
@@ -158,6 +165,27 @@
158
165
 
159
166
  &.-ds-iconOnly.-ds-outlined {
160
167
  border: none;
168
+
169
+ &.-ds-color-magic {
170
+ background: none;
171
+
172
+ &.-ds-hovered,
173
+ &:hover,
174
+ &.-ds-active,
175
+ &:active {
176
+ background: $gradient-magic-background-ghost-hovered;
177
+ }
178
+
179
+ &.-ds-focused,
180
+ &:focus {
181
+ background: $gradient-magic-background-ghost-focused;
182
+ }
183
+
184
+ // no glow (blurred gradient shadow) in iconOnly magic IconButton
185
+ &::after {
186
+ display: none;
187
+ }
188
+ }
161
189
  }
162
190
  }
163
191
 
@@ -266,9 +294,11 @@ import WnlButton, {
266
294
  BUTTON_COLORS,
267
295
  BUTTON_ELEVATIONS,
268
296
  BUTTON_RADIUSES,
297
+ BUTTON_STATES,
269
298
  BUTTON_TYPES,
270
299
  ButtonElevation,
271
300
  ButtonRadius,
301
+ ButtonState,
272
302
  } from '../Button';
273
303
  import {
274
304
  ICON_BUTTON_COLOR_SCHEMES,
@@ -279,6 +309,7 @@ import {
279
309
  IconButtonColor,
280
310
  IconButtonColorScheme,
281
311
  IconButtonSize,
312
+ IconButtonState,
282
313
  IconButtonType,
283
314
  } from './IconButton.consts';
284
315
  import { Value } from '../../../utils/type.utils';
@@ -293,6 +324,14 @@ const ICON_ONLY_ICON_SIZES_MAP = {
293
324
  [ICON_BUTTON_SIZES.LARGE]: ICON_SIZES.MEDIUM,
294
325
  } as const;
295
326
 
327
+ const ICON_BUTTON_STATE_MAP: Record<IconButtonState, ButtonState> = {
328
+ [ICON_BUTTON_STATES.DEFAULT]: BUTTON_STATES.DEFAULT,
329
+ [ICON_BUTTON_STATES.HOVERED]: BUTTON_STATES.HOVERED,
330
+ [ICON_BUTTON_STATES.FOCUSED]: BUTTON_STATES.FOCUSED,
331
+ [ICON_BUTTON_STATES.LOADING]: BUTTON_STATES.LOADING,
332
+ [ICON_BUTTON_STATES.DISABLED]: BUTTON_STATES.DISABLED,
333
+ };
334
+
296
335
  export default defineComponent({
297
336
  name: 'IconButton',
298
337
  components: {
@@ -364,6 +403,7 @@ export default defineComponent({
364
403
  setup() {
365
404
  return {
366
405
  ...useHoverState(),
406
+ ICON_BUTTON_STATE_MAP,
367
407
  };
368
408
  },
369
409
  data() {
@@ -64,6 +64,11 @@
64
64
 
65
65
  <style lang="scss">
66
66
  @import 'flatpickr/dist/flatpickr';
67
+
68
+ .-ds-dark {
69
+ // stylelint-disable-next-line no-invalid-position-at-import-rule -- nested import scopes flatpickr's dark theme under `.-ds-dark`
70
+ @import 'flatpickr/dist/themes/dark';
71
+ }
67
72
  </style>
68
73
 
69
74
  <style lang="scss" scoped>
@@ -23,6 +23,11 @@
23
23
 
24
24
  <style lang="scss">
25
25
  @import 'flatpickr/dist/flatpickr';
26
+
27
+ .-ds-dark {
28
+ // stylelint-disable-next-line no-invalid-position-at-import-rule -- nested import scopes flatpickr's dark theme under `.-ds-dark`
29
+ @import 'flatpickr/dist/themes/dark';
30
+ }
26
31
  </style>
27
32
 
28
33
  <style scoped lang="scss">
@@ -78,7 +78,7 @@ $switch-transition: all $default-transition-time ease-out;
78
78
  $root: &;
79
79
 
80
80
  background-color: $color-default-background;
81
- border: 1px solid $color-neutral-border;
81
+ border: 1px solid $color-neutral-border-weak;
82
82
  border-radius: $radius-xs;
83
83
  display: inline-flex;
84
84
  max-width: 100%;
@@ -107,7 +107,7 @@ $switch-transition: all $default-transition-time ease-out;
107
107
  }
108
108
 
109
109
  #{$root}__label {
110
- color: $color-neutral-text-heavy;
110
+ color: $color-primary-text-strong;
111
111
  }
112
112
  }
113
113
 
@@ -213,7 +213,7 @@ $switch-transition: all $default-transition-time ease-out;
213
213
  }
214
214
 
215
215
  #{$root}__label {
216
- color: $color-neutral-text-heavy;
216
+ color: $color-primary-text-strong;
217
217
  }
218
218
  }
219
219
 
@@ -2,6 +2,7 @@
2
2
  @import '../settings/spacings';
3
3
  @import '../settings/radiuses';
4
4
  @import '../settings/buttons';
5
+ @import '../settings/gradients';
5
6
  @import '../settings/shadows';
6
7
  @import '../settings/icons';
7
8
  @import '../settings/typography/tokens';
@@ -122,6 +123,25 @@
122
123
  }
123
124
  }
124
125
 
126
+ // The magic outlined background stack: a solid white fill clipped to `padding-box` over a
127
+ // gradient ring clipped to `border-box`, showing through only in the 1px transparent border.
128
+ // `$tint` adds a faint gradient over the white pill (hover / focus states); `$border` swaps the
129
+ // gradient ring (e.g. the faded disabled variant).
130
+ @mixin setMagicOutlinedBackground($tint: null, $border: $gradient-magic-border-angular) {
131
+ $fill: linear-gradient($color-default-background, $color-default-background) padding-box;
132
+
133
+ @if $tint {
134
+ background:
135
+ $tint padding-box,
136
+ $fill,
137
+ $border border-box;
138
+ } @else {
139
+ background:
140
+ $fill,
141
+ $border border-box;
142
+ }
143
+ }
144
+
125
145
  $button-small-min-height: 28px;
126
146
  $button-medium-min-height: 32px;
127
147
  $button-large-min-height: 48px;
@@ -201,6 +221,81 @@ $button-disabled-alpha: 0.6;
201
221
  }
202
222
  }
203
223
 
224
+ // `magic` is a gradient color that doesn't fit the flat color maps. Outlined only:
225
+ // a white fill over a blurred gradient glow.
226
+ &.-ds-color-magic.-ds-outlined {
227
+ $magic-glow-spread: 1px;
228
+
229
+ // `border-image` can't follow `border-radius`, so the gradient ring is a transparent
230
+ // border with two background layers: a solid fill clipped to `padding-box` over the
231
+ // gradient clipped to `border-box`, showing through only in the 1px border gap. The
232
+ // conic gradient starts and ends on the same color so its wrap-around has no seam.
233
+ @include setMagicOutlinedBackground;
234
+
235
+ border: 1px solid transparent;
236
+ color: $color-neutral-text;
237
+
238
+ #{$self}__icon {
239
+ color: $color-neutral-icon;
240
+ }
241
+
242
+ #{$self}__loadingIcon {
243
+ svg path {
244
+ fill: var(--ds-magic-fill, currentColor);
245
+ }
246
+ }
247
+
248
+ // Blurred conic gradient creating the glow.
249
+ &::after {
250
+ background: $gradient-magic-border-angular;
251
+ border-radius: inherit;
252
+ bottom: -$magic-glow-spread;
253
+ content: '';
254
+ display: block;
255
+ filter: blur(4px);
256
+ left: -$magic-glow-spread;
257
+ position: absolute;
258
+ right: -$magic-glow-spread;
259
+ top: -$magic-glow-spread;
260
+ transform: translateZ(-1px);
261
+ z-index: -1;
262
+ }
263
+
264
+ // Hover / pressed add a faint gradient tint over the white pill.
265
+ &:hover,
266
+ &.-ds-hovered,
267
+ &:active,
268
+ &.-ds-active {
269
+ // Re-declare the full stack so the gradient ring survives, with the ghost tint
270
+ // layered on top.
271
+ @include setMagicOutlinedBackground($tint: $gradient-magic-background-ghost-hovered);
272
+ }
273
+
274
+ // Focused uses its own ghost tint.
275
+ &:focus,
276
+ &.-ds-focused {
277
+ @include setMagicOutlinedBackground($tint: $gradient-magic-background-ghost-focused);
278
+ }
279
+
280
+ // Disabled drops the glow entirely and fades text/icon.
281
+ &.-ds-disabled,
282
+ &:disabled {
283
+ // Keep the gradient ring but drop the hover tint; the glow is removed below.
284
+ @include setMagicOutlinedBackground($border: $gradient-magic-border-angular-disabled);
285
+
286
+ color: $color-neutral-text-disabled;
287
+
288
+ #{$self}__icon,
289
+ #{$self}__loadingIcon {
290
+ color: $color-neutral-icon-disabled;
291
+ }
292
+
293
+ &::after {
294
+ display: none;
295
+ }
296
+ }
297
+ }
298
+
204
299
  &.-ds-large {
205
300
  @include button-l-default-bold-uppercase;
206
301
 
@@ -240,7 +335,7 @@ $button-disabled-alpha: 0.6;
240
335
  }
241
336
 
242
337
  &:not(.-ds-text) {
243
- min-width: 100px;
338
+ min-width: 80px;
244
339
 
245
340
  &.-ds-rounded {
246
341
  border-radius: $radius-s;
@@ -1,6 +1,7 @@
1
1
  // Settings
2
2
  @import 'settings/animations';
3
3
  @import 'settings/buttons';
4
+ @import 'settings/gradients';
4
5
  @import 'settings/fonts';
5
6
  @import 'settings/icons';
6
7
  @import 'settings/media-queries';
@@ -12,6 +13,9 @@
12
13
  @import 'settings/colors/raw-wnl';
13
14
  @import 'settings/colors/raw-bodywork';
14
15
  @import 'settings/colors/raw-mc';
16
+ @import 'settings/colors/raw-wnl-dark';
17
+ @import 'settings/colors/raw-bodywork-dark';
18
+ @import 'settings/colors/raw-mc-dark';
15
19
  @import 'settings/typography/variables-css';
16
20
 
17
21
  // Mixins
@@ -32,6 +36,9 @@
32
36
  @import 'settings/colors/tokens-wnl-variables';
33
37
  @import 'settings/colors/tokens-bodywork-variables';
34
38
  @import 'settings/colors/tokens-mc-variables';
39
+ @import 'settings/colors/tokens-variables-dark';
40
+ @import 'settings/colors/magic-variables';
41
+ @import 'settings/gradients-variables';
35
42
 
36
43
  html,
37
44
  body {
@@ -50,13 +57,12 @@ textarea {
50
57
 
51
58
  html.-ds-thick-scrollbar {
52
59
  @media #{breakpoint-m()} {
53
- @include scrollbar(15px, var(--raw-gray-100));
60
+ @include scrollbar(15px, var(--color-neutral-background-hovered));
54
61
  }
55
62
  }
56
63
 
57
64
  html {
58
- // Scrollbars
59
- @include scrollbar(2px, var(--raw-gray-100));
65
+ @include scrollbar(2px, var(--color-neutral-background-hovered));
60
66
 
61
67
  box-sizing: border-box;
62
68
  overflow-y: auto; // Without this style, we always display a scrollbar that covers the scrollbar in a container that is full-width. eg. scrollable-main-container
@@ -1,4 +1,4 @@
1
- @mixin scrollbar($size, $foreground-color, $background-color: white) {
1
+ @mixin scrollbar($size, $foreground-color, $background-color: var(--color-default-background)) {
2
2
  ::-webkit-scrollbar {
3
3
  height: $size;
4
4
  width: $size;
@@ -13,7 +13,11 @@
13
13
  }
14
14
  }
15
15
 
16
- @mixin scrollbar-on-self($size, $foreground-color, $background-color: white) {
16
+ @mixin scrollbar-on-self(
17
+ $size,
18
+ $foreground-color,
19
+ $background-color: var(--color-default-background)
20
+ ) {
17
21
  &::-webkit-scrollbar {
18
22
  height: $size;
19
23
  width: $size;
@@ -0,0 +1,34 @@
1
+ @import 'colors/magic';
2
+
3
+ :root {
4
+ --gradient-magic-background-ghost-hovered: linear-gradient(
5
+ 100deg,
6
+ rgba(#{$gradient-magic-color-1-rgb}, 0.12) 7.16%,
7
+ rgba(#{$gradient-magic-color-2-rgb}, 0.12) 36.18%,
8
+ rgba(#{$gradient-magic-color-3-rgb}, 0.12) 65.19%,
9
+ rgba(#{$gradient-magic-color-4-rgb}, 0.12) 95.08%
10
+ );
11
+ --gradient-magic-background-ghost-focused: linear-gradient(
12
+ 100deg,
13
+ rgba(#{$gradient-magic-color-1-rgb}, 0.12) 7.16%,
14
+ rgba(#{$gradient-magic-color-2-rgb}, 0.12) 36.18%,
15
+ rgba(#{$gradient-magic-color-3-rgb}, 0.12) 65.19%,
16
+ rgba(#{$gradient-magic-color-4-rgb}, 0.12) 95.08%
17
+ );
18
+ --gradient-magic-border-angular: conic-gradient(
19
+ from 90deg,
20
+ #{$gradient-magic-color-4} 0%,
21
+ #{$gradient-magic-color-1} 25%,
22
+ #{$gradient-magic-color-2} 50%,
23
+ #{$gradient-magic-color-3} 75%,
24
+ #{$gradient-magic-color-4} 100%
25
+ );
26
+ --gradient-magic-border-angular-disabled: conic-gradient(
27
+ from 90deg,
28
+ rgba(#{$gradient-magic-color-4-rgb}, 0.4) 0%,
29
+ rgba(#{$gradient-magic-color-1-rgb}, 0.4) 25%,
30
+ rgba(#{$gradient-magic-color-2-rgb}, 0.4) 50%,
31
+ rgba(#{$gradient-magic-color-3-rgb}, 0.4) 75%,
32
+ rgba(#{$gradient-magic-color-4-rgb}, 0.4) 100%
33
+ );
34
+ }
@@ -0,0 +1,4 @@
1
+ $gradient-magic-background-ghost-hovered: var(--gradient-magic-background-ghost-hovered);
2
+ $gradient-magic-background-ghost-focused: var(--gradient-magic-background-ghost-focused);
3
+ $gradient-magic-border-angular: var(--gradient-magic-border-angular);
4
+ $gradient-magic-border-angular-disabled: var(--gradient-magic-border-angular-disabled);
@@ -0,0 +1,10 @@
1
+ :root {
2
+ --gradient-magic-color-1: var(--raw-teal-500);
3
+ --gradient-magic-color-2: var(--raw-indigo-500);
4
+ --gradient-magic-color-3: var(--raw-violet-500);
5
+ --gradient-magic-color-4: var(--raw-gold-500);
6
+ --gradient-magic-color-1-rgb: var(--raw-teal-500-rgb);
7
+ --gradient-magic-color-2-rgb: var(--raw-indigo-500-rgb);
8
+ --gradient-magic-color-3-rgb: var(--raw-violet-500-rgb);
9
+ --gradient-magic-color-4-rgb: var(--raw-gold-500-rgb);
10
+ }
@@ -0,0 +1,9 @@
1
+ $gradient-magic-color-1: var(--gradient-magic-color-1);
2
+ $gradient-magic-color-2: var(--gradient-magic-color-2);
3
+ $gradient-magic-color-3: var(--gradient-magic-color-3);
4
+ $gradient-magic-color-4: var(--gradient-magic-color-4);
5
+
6
+ $gradient-magic-color-1-rgb: var(--gradient-magic-color-1-rgb);
7
+ $gradient-magic-color-2-rgb: var(--gradient-magic-color-2-rgb);
8
+ $gradient-magic-color-3-rgb: var(--gradient-magic-color-3-rgb);
9
+ $gradient-magic-color-4-rgb: var(--gradient-magic-color-4-rgb);