@bethinkpl/design-system 42.0.0 → 44.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 (85) hide show
  1. package/dist/design-system.css +1 -1
  2. package/dist/design-system.js +17470 -13135
  3. package/dist/design-system.js.map +1 -1
  4. package/dist/lib/js/components/Cards/Card/Card.consts.d.ts +14 -0
  5. package/dist/lib/js/components/Cards/Card/Card.vue.d.ts +10 -5
  6. package/dist/lib/js/components/Cards/CardExpandable/CardExpandable.vue.d.ts +33 -15
  7. package/dist/lib/js/components/Form/PinInputField/PinInputField.consts.d.ts +14 -0
  8. package/dist/lib/js/components/Form/PinInputField/PinInputField.types.d.ts +12 -0
  9. package/dist/lib/js/components/Form/PinInputField/PinInputField.utils.d.ts +2 -0
  10. package/dist/lib/js/components/Form/PinInputField/PinInputField.vue.d.ts +91 -0
  11. package/dist/lib/js/components/Form/PinInputField/index.d.ts +5 -0
  12. package/dist/lib/js/components/Form/PinInputField/usePinInputFieldWithinForm.d.ts +8 -0
  13. package/dist/lib/js/components/Form/SelectField/SelectField.types.d.ts +26 -0
  14. package/dist/lib/js/components/Form/SelectField/SelectField.utils.d.ts +7 -0
  15. package/dist/lib/js/components/Form/SelectField/SelectField.vue.d.ts +107 -0
  16. package/dist/lib/js/components/Form/SelectField/SelectFieldOption.vue.d.ts +19 -0
  17. package/dist/lib/js/components/Form/SelectField/index.d.ts +4 -0
  18. package/dist/lib/js/components/Form/SelectField/useSelectFieldWithinForm.d.ts +8 -0
  19. package/dist/lib/js/components/Form/TextAreaField/TextAreaField.types.d.ts +9 -0
  20. package/dist/lib/js/components/Form/TextAreaField/TextAreaField.vue.d.ts +76 -0
  21. package/dist/lib/js/components/Form/TextAreaField/TextAreaFieldAutosized.vue.d.ts +6 -0
  22. package/dist/lib/js/components/Form/TextAreaField/index.d.ts +4 -0
  23. package/dist/lib/js/components/LabelValue/LabelValueItem/LabelValueItem.vue.d.ts +12 -1
  24. package/dist/lib/js/components/Pagination/Pagination.vue.d.ts +2 -0
  25. package/dist/lib/js/components/SelectList/SelectListItem/SelectListItem.vue.d.ts +2 -0
  26. package/dist/lib/js/components/SelectList/SelectListItemToggle/SelectListItemToggle.vue.d.ts +2 -0
  27. package/dist/lib/js/components/SurveyQuestions/SurveyQuestionOpenEnded/SurveyQuestionOpenEnded.vue.d.ts +33 -15
  28. package/dist/lib/js/components/SurveyQuestions/SurveyQuestionScale/SurveyQuestionScale.vue.d.ts +33 -15
  29. package/dist/lib/js/components/Toast/Toast.consts.d.ts +11 -9
  30. package/dist/lib/js/components/Toast/Toast.vue.d.ts +51 -415
  31. package/dist/lib/js/composables/useFormFieldWithinForm.d.ts +3 -3
  32. package/dist/lib/js/composables/useTextFieldWithinForm.d.ts +8 -0
  33. package/dist/lib/js/index.d.ts +6 -0
  34. package/lib/js/components/Cards/Card/Card.consts.ts +18 -0
  35. package/lib/js/components/Cards/Card/Card.spec.ts +159 -33
  36. package/lib/js/components/Cards/Card/Card.stories.ts +21 -2
  37. package/lib/js/components/Cards/Card/Card.vue +90 -23
  38. package/lib/js/components/Dropdown/Dropdown.vue +6 -10
  39. package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.spec.ts +15 -0
  40. package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.vue +5 -2
  41. package/lib/js/components/Form/Form.stories.ts +37 -2
  42. package/lib/js/components/Form/InputField/InputField.spec.ts +11 -1
  43. package/lib/js/components/Form/InputField/InputField.vue +7 -4
  44. package/lib/js/components/Form/PinInputField/PinInputField.consts.ts +18 -0
  45. package/lib/js/components/Form/PinInputField/PinInputField.spec.ts +394 -0
  46. package/lib/js/components/Form/PinInputField/PinInputField.stories.ts +124 -0
  47. package/lib/js/components/Form/PinInputField/PinInputField.types.ts +42 -0
  48. package/lib/js/components/Form/PinInputField/PinInputField.utils.ts +5 -0
  49. package/lib/js/components/Form/PinInputField/PinInputField.vue +211 -0
  50. package/lib/js/components/Form/PinInputField/index.ts +6 -0
  51. package/lib/js/components/Form/PinInputField/usePinInputFieldWithinForm.ts +29 -0
  52. package/lib/js/components/Form/SelectField/SelectField.spec.ts +495 -0
  53. package/lib/js/components/Form/SelectField/SelectField.stories.ts +282 -0
  54. package/lib/js/components/Form/SelectField/SelectField.types.ts +65 -0
  55. package/lib/js/components/Form/SelectField/SelectField.utils.ts +83 -0
  56. package/lib/js/components/Form/SelectField/SelectField.vue +342 -0
  57. package/lib/js/components/Form/SelectField/SelectFieldOption.vue +51 -0
  58. package/lib/js/components/Form/SelectField/index.ts +5 -0
  59. package/lib/js/components/Form/SelectField/useSelectFieldWithinForm.ts +27 -0
  60. package/lib/js/components/Form/TextAreaField/TextAreaField.spec.ts +341 -0
  61. package/lib/js/components/Form/TextAreaField/TextAreaField.stories.ts +99 -0
  62. package/lib/js/components/Form/TextAreaField/TextAreaField.types.ts +23 -0
  63. package/lib/js/components/Form/TextAreaField/TextAreaField.vue +190 -0
  64. package/lib/js/components/Form/TextAreaField/TextAreaFieldAutosized.vue +19 -0
  65. package/lib/js/components/Form/TextAreaField/index.ts +5 -0
  66. package/lib/js/components/LabelValue/LabelValueItem/LabelValueItem.spec.ts +26 -0
  67. package/lib/js/components/LabelValue/LabelValueItem/LabelValueItem.stories.ts +16 -3
  68. package/lib/js/components/LabelValue/LabelValueItem/LabelValueItem.vue +5 -0
  69. package/lib/js/components/LoadingBar/LoadingBar.spec.ts +84 -0
  70. package/lib/js/components/LoadingBar/LoadingBar.vue +30 -19
  71. package/lib/js/components/SelectList/SelectListItem/SelectListItem.spec.ts +77 -0
  72. package/lib/js/components/SelectList/SelectListItem/SelectListItem.vue +4 -1
  73. package/lib/js/components/Toast/Toast.consts.ts +4 -2
  74. package/lib/js/components/Toast/Toast.spec.ts +177 -0
  75. package/lib/js/components/Toast/Toast.stories.ts +65 -31
  76. package/lib/js/components/Toast/Toast.vue +176 -195
  77. package/lib/js/composables/useFormFieldWithinForm.ts +4 -4
  78. package/lib/js/{components/Form/InputField/useInputFieldWithinForm.ts → composables/useTextFieldWithinForm.ts} +7 -2
  79. package/lib/js/index.ts +6 -0
  80. package/lib/js/styles/Shadows/Shadows.stories.scss +16 -0
  81. package/lib/js/styles/Shadows/Shadows.stories.ts +4 -0
  82. package/lib/styles/mixins/_dropdown-surface.scss +24 -0
  83. package/lib/styles/settings/_shadows.scss +18 -0
  84. package/package.json +1 -1
  85. package/dist/lib/js/components/Form/InputField/useInputFieldWithinForm.d.ts +0 -8
@@ -3,13 +3,37 @@ import { ComponentMountingOptions, mount } from '@vue/test-utils';
3
3
  import { h } from 'vue';
4
4
 
5
5
  import Card from './Card.vue';
6
- import { CARD_BACKGROUND_COLORS, CARD_PADDING_SIZES } from './Card.consts';
6
+ import {
7
+ CARD_BACKGROUND_COLORS,
8
+ CARD_ELEVATIONS,
9
+ CARD_PADDING_SIZES,
10
+ CARD_RADIUSES,
11
+ CardBackgroundColor,
12
+ CardElevation,
13
+ CardPaddingSize,
14
+ CardRadius,
15
+ } from './Card.consts';
7
16
 
8
17
  describe('Card', () => {
9
18
  const createComponent = (options: ComponentMountingOptions<typeof Card> = {}) => {
10
19
  return mount(Card, options);
11
20
  };
12
21
 
22
+ // asserts that exactly one modifier out of a variant group is applied
23
+ const expectOnlyModifier = (
24
+ classes: Array<string>,
25
+ modifiers: Array<string>,
26
+ expectedModifier: string | null,
27
+ ) => {
28
+ modifiers.forEach((modifier) => {
29
+ if (modifier === expectedModifier) {
30
+ expect(classes).toContain(modifier);
31
+ } else {
32
+ expect(classes).not.toContain(modifier);
33
+ }
34
+ });
35
+ };
36
+
13
37
  it('should create', () => {
14
38
  const component = createComponent();
15
39
 
@@ -198,62 +222,143 @@ describe('Card', () => {
198
222
  );
199
223
  });
200
224
 
201
- it('should set large padding class', () => {
202
- const component = createComponent({
203
- props: { paddingSize: CARD_PADDING_SIZES.LARGE },
204
- });
225
+ const PADDING_MODIFIERS = ['-ds-paddingMedium', '-ds-paddingLarge'];
226
+
227
+ it.each<[CardPaddingSize, string | null]>([
228
+ [CARD_PADDING_SIZES.SMALL, null],
229
+ [CARD_PADDING_SIZES.MEDIUM, '-ds-paddingMedium'],
230
+ [CARD_PADDING_SIZES.LARGE, '-ds-paddingLarge'],
231
+ ])('should set the padding class for paddingSize %s', (paddingSize, expectedModifier) => {
232
+ const component = createComponent({ props: { paddingSize } });
205
233
 
206
- expect(component.find('.ds-card').classes()).toContain('-ds-paddingLarge');
234
+ expectOnlyModifier(
235
+ component.find('.ds-card').classes(),
236
+ PADDING_MODIFIERS,
237
+ expectedModifier,
238
+ );
207
239
  });
208
240
 
209
- it('should not set large padding class', () => {
210
- const component = createComponent({
211
- props: { paddingSize: CARD_PADDING_SIZES.SMALL },
212
- });
241
+ it('should not set any padding class by default', () => {
242
+ const component = createComponent();
243
+
244
+ expectOnlyModifier(component.find('.ds-card').classes(), PADDING_MODIFIERS, null);
245
+ });
213
246
 
214
- expect(component.find('.ds-card').classes()).not.toContain('-ds-paddingLarge');
247
+ const ELEVATION_MODIFIERS = ['-ds-elevationTop', '-ds-elevationNone'];
248
+
249
+ it.each<[CardElevation, string | null]>([
250
+ [CARD_ELEVATIONS.DEFAULT, null],
251
+ [CARD_ELEVATIONS.TOP, '-ds-elevationTop'],
252
+ [CARD_ELEVATIONS.NONE, '-ds-elevationNone'],
253
+ ])('should set the elevation class for elevation %s', (elevation, expectedModifier) => {
254
+ const component = createComponent({ props: { elevation } });
255
+
256
+ expectOnlyModifier(
257
+ component.find('.ds-card').classes(),
258
+ ELEVATION_MODIFIERS,
259
+ expectedModifier,
260
+ );
215
261
  });
216
262
 
217
- it('should not set flat class by default', () => {
263
+ it('should not set any elevation class by default', () => {
218
264
  const component = createComponent();
219
265
 
220
- expect(component.find('.ds-card').classes()).not.toContain('-ds-flat');
266
+ expectOnlyModifier(component.find('.ds-card').classes(), ELEVATION_MODIFIERS, null);
221
267
  });
222
268
 
223
- it('should set flat class', () => {
224
- const component = createComponent({
225
- props: { isFlat: true },
226
- });
269
+ const RADIUS_MODIFIERS = ['-ds-radiusTop', '-ds-radiusNone'];
270
+
271
+ it.each<[CardRadius, string | null]>([
272
+ [CARD_RADIUSES.ALL, null],
273
+ [CARD_RADIUSES.TOP, '-ds-radiusTop'],
274
+ [CARD_RADIUSES.NONE, '-ds-radiusNone'],
275
+ ])('should set the radius class for radius %s', (radius, expectedModifier) => {
276
+ const component = createComponent({ props: { radius } });
227
277
 
228
- expect(component.find('.ds-card').classes()).toContain('-ds-flat');
278
+ expectOnlyModifier(
279
+ component.find('.ds-card').classes(),
280
+ RADIUS_MODIFIERS,
281
+ expectedModifier,
282
+ );
283
+ });
284
+
285
+ it('should not set any radius class by default', () => {
286
+ const component = createComponent();
287
+
288
+ expectOnlyModifier(component.find('.ds-card').classes(), RADIUS_MODIFIERS, null);
229
289
  });
230
290
 
231
- it('should not set no-radius class by default', () => {
291
+ const BACKGROUND_MODIFIERS = ['-ds-backgroundNeutral', '-ds-backgroundNone'];
292
+
293
+ it.each<[CardBackgroundColor, string | null]>([
294
+ [CARD_BACKGROUND_COLORS.DEFAULT, null],
295
+ [CARD_BACKGROUND_COLORS.NEUTRAL, '-ds-backgroundNeutral'],
296
+ [CARD_BACKGROUND_COLORS.NONE, '-ds-backgroundNone'],
297
+ ])(
298
+ 'should set the background class for backgroundColor %s',
299
+ (backgroundColor, expectedModifier) => {
300
+ const component = createComponent({ props: { backgroundColor } });
301
+
302
+ expectOnlyModifier(
303
+ component.find('.ds-card').classes(),
304
+ BACKGROUND_MODIFIERS,
305
+ expectedModifier,
306
+ );
307
+ },
308
+ );
309
+
310
+ it('should not set any background class by default', () => {
232
311
  const component = createComponent();
233
312
 
234
- expect(component.find('.ds-card').classes()).not.toContain('-ds-noRadius');
313
+ expectOnlyModifier(component.find('.ds-card').classes(), BACKGROUND_MODIFIERS, null);
314
+ });
315
+
316
+ it('should set the none modifiers of all three visual axes when isFlat is true', () => {
317
+ const classes = createComponent({ props: { isFlat: true } })
318
+ .find('.ds-card')
319
+ .classes();
320
+
321
+ expectOnlyModifier(classes, ELEVATION_MODIFIERS, '-ds-elevationNone');
322
+ expectOnlyModifier(classes, RADIUS_MODIFIERS, '-ds-radiusNone');
323
+ expectOnlyModifier(classes, BACKGROUND_MODIFIERS, '-ds-backgroundNone');
324
+ });
325
+
326
+ it('should not change the padding when isFlat is true', () => {
327
+ const component = createComponent({ props: { isFlat: true } });
328
+
329
+ expectOnlyModifier(component.find('.ds-card').classes(), PADDING_MODIFIERS, null);
235
330
  });
236
331
 
237
- it('should set no-radius class when hasRadius is false', () => {
332
+ it('should let an explicit elevation override isFlat', () => {
238
333
  const component = createComponent({
239
- props: { hasRadius: false },
334
+ props: { isFlat: true, elevation: CARD_ELEVATIONS.TOP },
240
335
  });
241
336
 
242
- expect(component.find('.ds-card').classes()).toContain('-ds-noRadius');
337
+ expectOnlyModifier(
338
+ component.find('.ds-card').classes(),
339
+ ELEVATION_MODIFIERS,
340
+ '-ds-elevationTop',
341
+ );
243
342
  });
244
343
 
245
- it('should not set neutral background class by default', () => {
246
- const component = createComponent();
344
+ it('should let an explicit radius override isFlat', () => {
345
+ const component = createComponent({
346
+ props: { isFlat: true, radius: CARD_RADIUSES.TOP },
347
+ });
247
348
 
248
- expect(component.find('.ds-card').classes()).not.toContain('-ds-backgroundNeutral');
349
+ expectOnlyModifier(component.find('.ds-card').classes(), RADIUS_MODIFIERS, '-ds-radiusTop');
249
350
  });
250
351
 
251
- it('should set neutral background class when backgroundColor is neutral', () => {
352
+ it('should let an explicit backgroundColor override isFlat', () => {
252
353
  const component = createComponent({
253
- props: { backgroundColor: CARD_BACKGROUND_COLORS.NEUTRAL },
354
+ props: { isFlat: true, backgroundColor: CARD_BACKGROUND_COLORS.NEUTRAL },
254
355
  });
255
356
 
256
- expect(component.find('.ds-card').classes()).toContain('-ds-backgroundNeutral');
357
+ expectOnlyModifier(
358
+ component.find('.ds-card').classes(),
359
+ BACKGROUND_MODIFIERS,
360
+ '-ds-backgroundNeutral',
361
+ );
257
362
  });
258
363
 
259
364
  it('should not give the ribbon a radius when only hasRibbonRadius is set', () => {
@@ -266,25 +371,46 @@ describe('Card', () => {
266
371
 
267
372
  it('should give the ribbon a radius when hasRibbonRadius and card has no radius', () => {
268
373
  const component = createComponent({
269
- props: { hasRibbon: true, hasRibbonRadius: true, hasRadius: false },
374
+ props: { hasRibbon: true, hasRibbonRadius: true, radius: CARD_RADIUSES.NONE },
270
375
  });
271
376
 
272
377
  expect(component.find('.-ds-radius-bottom').exists()).toBe(true);
273
378
  });
274
379
 
275
- it('should not give the ribbon a radius when hasRadius is false but hasRibbonRadius is false', () => {
380
+ it('should not give the ribbon a radius when radius is none but hasRibbonRadius is false', () => {
381
+ const component = createComponent({
382
+ props: { hasRibbon: true, hasRibbonRadius: false, radius: CARD_RADIUSES.NONE },
383
+ });
384
+
385
+ expect(component.find('.-ds-radius-bottom').exists()).toBe(false);
386
+ });
387
+
388
+ it('should not give the ribbon a radius when the card is rounded at the top', () => {
276
389
  const component = createComponent({
277
- props: { hasRibbon: true, hasRibbonRadius: false, hasRadius: false },
390
+ props: { hasRibbon: true, hasRibbonRadius: true, radius: CARD_RADIUSES.TOP },
278
391
  });
279
392
 
280
393
  expect(component.find('.-ds-radius-bottom').exists()).toBe(false);
281
394
  });
282
395
 
283
- it('should give the ribbon a radius when hasRibbonRadius and card is flat', () => {
396
+ it('should give the ribbon a radius when hasRibbonRadius and the card is flat', () => {
284
397
  const component = createComponent({
285
398
  props: { hasRibbon: true, hasRibbonRadius: true, isFlat: true },
286
399
  });
287
400
 
288
401
  expect(component.find('.-ds-radius-bottom').exists()).toBe(true);
289
402
  });
403
+
404
+ it('should not give the ribbon a radius when a flat card is overridden with radius top', () => {
405
+ const component = createComponent({
406
+ props: {
407
+ hasRibbon: true,
408
+ hasRibbonRadius: true,
409
+ isFlat: true,
410
+ radius: CARD_RADIUSES.TOP,
411
+ },
412
+ });
413
+
414
+ expect(component.find('.-ds-radius-bottom').exists()).toBe(false);
415
+ });
290
416
  });
@@ -3,6 +3,8 @@ import Card from './Card.vue';
3
3
  import { Args, ArgTypes, Meta, StoryFn } from '@storybook/vue3';
4
4
  import {
5
5
  CARD_BACKGROUND_COLORS,
6
+ CARD_ELEVATIONS,
7
+ CARD_RADIUSES,
6
8
  CARD_RIBBON_COLORS,
7
9
  CARD_RIBBON_POSITIONS,
8
10
  CARD_RIBBON_SIZES,
@@ -50,7 +52,9 @@ const args = {
50
52
  paddingSize: CARD_PADDING_SIZES.SMALL,
51
53
  dividerUnderHeader: false,
52
54
  hasRibbon: false,
53
- hasRadius: true,
55
+ isFlat: false,
56
+ elevation: CARD_ELEVATIONS.DEFAULT,
57
+ radius: CARD_RADIUSES.ALL,
54
58
  backgroundColor: CARD_BACKGROUND_COLORS.DEFAULT,
55
59
  ribbonPosition: CARD_RIBBON_POSITIONS.TOP,
56
60
  ribbonSize: CARD_RIBBON_SIZES.MEDIUM,
@@ -59,7 +63,6 @@ const args = {
59
63
  hasLoadingBar: false,
60
64
  loadingBarColor: LOADING_BAR_COLORS.NEUTRAL_HEAVY,
61
65
  loadingBarTime: '0',
62
- isFlat: false,
63
66
  isContentScrollable: false,
64
67
  } as Args;
65
68
 
@@ -70,6 +73,17 @@ const argTypes = {
70
73
  },
71
74
  footer: { control: 'text' },
72
75
  experimentalContent: { control: 'text' },
76
+ isFlat: {
77
+ control: 'boolean',
78
+ },
79
+ elevation: {
80
+ control: 'select',
81
+ options: Object.values(CARD_ELEVATIONS),
82
+ },
83
+ radius: {
84
+ control: 'select',
85
+ options: Object.values(CARD_RADIUSES),
86
+ },
73
87
  backgroundColor: {
74
88
  control: 'select',
75
89
  options: Object.values(CARD_BACKGROUND_COLORS),
@@ -108,3 +122,8 @@ Interactive.parameters = {
108
122
  url: 'https://www.figma.com/file/izQdYyiBR1GQgFkaOIfIJI/LMS---DS---Components?node-id=1552%3A34959',
109
123
  },
110
124
  };
125
+
126
+ export const Flat = StoryTemplate.bind({});
127
+
128
+ Flat.argTypes = argTypes;
129
+ Flat.args = { ...args, isFlat: true };
@@ -3,12 +3,17 @@
3
3
  :class="[
4
4
  'ds-card',
5
5
  {
6
+ '-ds-paddingMedium': paddingSize === CARD_PADDING_SIZES.MEDIUM,
6
7
  '-ds-paddingLarge': paddingSize === CARD_PADDING_SIZES.LARGE,
7
8
  '-ds-leftRibbon':
8
9
  hasRibbon && !hasLoadingBar && ribbonPosition === CARD_RIBBON_POSITIONS.LEFT,
9
- '-ds-flat': isFlat,
10
- '-ds-noRadius': !hasRadius,
11
- '-ds-backgroundNeutral': backgroundColor === CARD_BACKGROUND_COLORS.NEUTRAL,
10
+ '-ds-elevationTop': effectiveElevation === CARD_ELEVATIONS.TOP,
11
+ '-ds-elevationNone': effectiveElevation === CARD_ELEVATIONS.NONE,
12
+ '-ds-radiusTop': effectiveRadius === CARD_RADIUSES.TOP,
13
+ '-ds-radiusNone': effectiveRadius === CARD_RADIUSES.NONE,
14
+ '-ds-backgroundNeutral':
15
+ effectiveBackgroundColor === CARD_BACKGROUND_COLORS.NEUTRAL,
16
+ '-ds-backgroundNone': effectiveBackgroundColor === CARD_BACKGROUND_COLORS.NONE,
12
17
  },
13
18
  ]"
14
19
  >
@@ -80,23 +85,36 @@
80
85
  $root: &;
81
86
  $card-border-radius: $radius-m;
82
87
 
88
+ background-color: $color-default-background;
89
+ border-radius: $card-border-radius;
90
+ box-shadow: $shadow-s;
83
91
  display: flex;
84
92
  flex-direction: column;
85
93
  position: relative;
86
94
  width: inherit;
87
95
 
88
- &:not(.-ds-flat) {
89
- background-color: $color-default-background;
90
- border-radius: $card-border-radius;
91
- box-shadow: $shadow-s;
96
+ &.-ds-backgroundNeutral {
97
+ background-color: $color-neutral-background;
98
+ }
92
99
 
93
- &.-ds-backgroundNeutral {
94
- background-color: $color-neutral-background;
95
- }
100
+ &.-ds-backgroundNone {
101
+ background-color: transparent;
102
+ }
96
103
 
97
- &.-ds-noRadius {
98
- border-radius: 0;
99
- }
104
+ &.-ds-elevationTop {
105
+ box-shadow: $shadow-top-s;
106
+ }
107
+
108
+ &.-ds-elevationNone {
109
+ box-shadow: none;
110
+ }
111
+
112
+ &.-ds-radiusTop {
113
+ border-radius: $card-border-radius $card-border-radius 0 0;
114
+ }
115
+
116
+ &.-ds-radiusNone {
117
+ border-radius: 0;
100
118
  }
101
119
 
102
120
  &.-ds-leftRibbon {
@@ -127,6 +145,10 @@
127
145
  &.-ds-withPadding {
128
146
  padding: $space-8;
129
147
 
148
+ #{$root}.-ds-paddingMedium & {
149
+ padding: $space-12 $space-12 $space-8;
150
+ }
151
+
130
152
  #{$root}.-ds-paddingLarge & {
131
153
  padding: $space-16 $space-16 $space-8;
132
154
  }
@@ -137,6 +159,10 @@
137
159
  &.-ds-withHorizontalMargin {
138
160
  margin: 0 $space-8;
139
161
 
162
+ #{$root}.-ds-paddingMedium & {
163
+ margin: 0 $space-12;
164
+ }
165
+
140
166
  #{$root}.-ds-paddingLarge & {
141
167
  margin: 0 $space-16;
142
168
  }
@@ -149,6 +175,10 @@
149
175
  &.-ds-withPadding {
150
176
  padding: $space-8;
151
177
 
178
+ #{$root}.-ds-paddingMedium & {
179
+ padding: $space-8 $space-12;
180
+ }
181
+
152
182
  #{$root}.-ds-paddingLarge & {
153
183
  padding: $space-8 $space-16;
154
184
  }
@@ -164,6 +194,10 @@
164
194
  &.-ds-withPadding {
165
195
  padding: 0 $space-8 $space-8;
166
196
 
197
+ #{$root}.-ds-paddingMedium & {
198
+ padding: 0 $space-12 $space-12;
199
+ }
200
+
167
201
  #{$root}.-ds-paddingLarge & {
168
202
  padding: 0 $space-16 $space-16;
169
203
  }
@@ -176,20 +210,21 @@
176
210
  display: flex;
177
211
  flex-shrink: 0;
178
212
 
179
- #{$root}:not(.-ds-flat) & {
213
+ #{$root}:not(.-ds-radiusNone) & {
180
214
  border-top-left-radius: $card-border-radius;
181
215
  border-top-right-radius: $card-border-radius;
182
216
  overflow: hidden;
183
217
  }
184
218
 
185
- #{$root}:not(.-ds-flat).-ds-leftRibbon & {
186
- border-bottom-left-radius: $card-border-radius;
219
+ #{$root}:not(.-ds-radiusNone).-ds-leftRibbon & {
187
220
  border-top-left-radius: $card-border-radius;
188
221
  border-top-right-radius: 0;
189
222
  }
190
223
 
191
- #{$root}:not(.-ds-flat).-ds-noRadius & {
192
- border-radius: 0;
224
+ // a left ribbon spans the full height of the card, so its bottom corner is
225
+ // only rounded when the card itself is rounded at the bottom
226
+ #{$root}:not(.-ds-radiusNone):not(.-ds-radiusTop).-ds-leftRibbon & {
227
+ border-bottom-left-radius: $card-border-radius;
193
228
  }
194
229
 
195
230
  .-ds-leftRibbon & {
@@ -213,13 +248,18 @@ import DsDivider from '../../Divider/Divider.vue';
213
248
  import DsLoadingBar, { LOADING_BAR_COLORS, LoadingBarColors } from '../../LoadingBar';
214
249
  import DsContainerRibbon from '../../ContainerRibbon/ContainerRibbon.vue';
215
250
  import { CONTAINER_RIBBON_LAYOUTS, CONTAINER_RIBBON_RADIUSES } from '../../ContainerRibbon';
251
+ import { RemovedProp } from '../../../utils/type.utils';
216
252
  import {
217
253
  CARD_BACKGROUND_COLORS,
254
+ CARD_ELEVATIONS,
255
+ CARD_RADIUSES,
218
256
  CARD_RIBBON_COLORS,
219
257
  CARD_RIBBON_POSITIONS,
220
258
  CARD_RIBBON_SIZES,
221
259
  CARD_PADDING_SIZES,
222
260
  CardBackgroundColor,
261
+ CardElevation,
262
+ CardRadius,
223
263
  CardRibbonColors,
224
264
  CardRibbonPositions,
225
265
  CardRibbonSizes,
@@ -234,8 +274,10 @@ const {
234
274
  paddingSize = CARD_PADDING_SIZES.SMALL,
235
275
  dividerUnderHeader = false,
236
276
  hasRibbon = false,
237
- hasRadius = true,
277
+ elevation = CARD_ELEVATIONS.DEFAULT,
278
+ radius = CARD_RADIUSES.ALL,
238
279
  backgroundColor = CARD_BACKGROUND_COLORS.DEFAULT,
280
+ isFlat = false,
239
281
  ribbonPosition = CARD_RIBBON_POSITIONS.TOP,
240
282
  ribbonSize = CARD_RIBBON_SIZES.MEDIUM,
241
283
  ribbonColor = CARD_RIBBON_COLORS.NEUTRAL_HEAVY,
@@ -243,7 +285,6 @@ const {
243
285
  hasLoadingBar = false,
244
286
  loadingBarColor = LOADING_BAR_COLORS.NEUTRAL_HEAVY,
245
287
  loadingBarTime = '0',
246
- isFlat = false,
247
288
  isContentScrollable = false,
248
289
  } = defineProps<{
249
290
  contentHasPadding?: boolean;
@@ -252,8 +293,15 @@ const {
252
293
  paddingSize?: CardPaddingSize;
253
294
  dividerUnderHeader?: boolean;
254
295
  hasRibbon?: boolean;
255
- hasRadius?: boolean;
296
+ elevation?: CardElevation;
297
+ radius?: CardRadius;
256
298
  backgroundColor?: CardBackgroundColor;
299
+ /**
300
+ * Shorthand for `elevation="none"`, `radius="none"` and `background-color="none"`.
301
+ * An axis explicitly set to a non-default value still wins over `isFlat`,
302
+ * so `is-flat radius="top"` keeps the top corners rounded.
303
+ */
304
+ isFlat?: boolean;
257
305
  ribbonPosition?: CardRibbonPositions;
258
306
  ribbonSize?: CardRibbonSizes;
259
307
  ribbonColor?: CardRibbonColors;
@@ -261,8 +309,10 @@ const {
261
309
  hasLoadingBar?: boolean;
262
310
  loadingBarColor?: LoadingBarColors;
263
311
  loadingBarTime?: string;
264
- isFlat?: boolean;
265
312
  isContentScrollable?: boolean;
313
+ // Removed props kept as removal markers so existing usages fail type-checking.
314
+ /** @deprecated renamed to `radius` (all | top | none) */
315
+ hasRadius?: RemovedProp<'renamed to radius'>;
266
316
  }>();
267
317
 
268
318
  defineSlots<{
@@ -280,12 +330,29 @@ const ribbonLayout = computed(() => {
280
330
  return layoutMap[ribbonPosition] || CONTAINER_RIBBON_LAYOUTS.HORIZONTAL;
281
331
  });
282
332
 
333
+ // `isFlat` is a shorthand that lowers the defaults of the three visual axes to `none`.
334
+ // An axis explicitly set to a non-default value still wins, so `isFlat` combines with
335
+ // e.g. `radius="top"`.
336
+ const effectiveElevation = computed(() =>
337
+ isFlat && elevation === CARD_ELEVATIONS.DEFAULT ? CARD_ELEVATIONS.NONE : elevation,
338
+ );
339
+
340
+ const effectiveRadius = computed(() =>
341
+ isFlat && radius === CARD_RADIUSES.ALL ? CARD_RADIUSES.NONE : radius,
342
+ );
343
+
344
+ const effectiveBackgroundColor = computed(() =>
345
+ isFlat && backgroundColor === CARD_BACKGROUND_COLORS.DEFAULT
346
+ ? CARD_BACKGROUND_COLORS.NONE
347
+ : backgroundColor,
348
+ );
349
+
283
350
  const ribbonRadius = computed(() => {
284
351
  const ribbonPositionToRibbonRadiusMap = {
285
352
  [CARD_RIBBON_POSITIONS.TOP]: CONTAINER_RIBBON_RADIUSES.BOTTOM,
286
353
  [CARD_RIBBON_POSITIONS.LEFT]: CONTAINER_RIBBON_RADIUSES.RIGHT,
287
354
  };
288
- return hasRibbonRadius && (isFlat || !hasRadius)
355
+ return hasRibbonRadius && effectiveRadius.value === CARD_RADIUSES.NONE
289
356
  ? ribbonPositionToRibbonRadiusMap[ribbonPosition]
290
357
  : CONTAINER_RIBBON_RADIUSES.NONE;
291
358
  });
@@ -36,30 +36,26 @@
36
36
  </template>
37
37
 
38
38
  <style lang="scss" scoped>
39
- @import '../../../../lib/styles/settings/colors/tokens';
40
- @import '../../../../lib/styles/settings/radiuses';
41
39
  @import '../../../../lib/styles/settings/spacings';
42
- @import '../../../../lib/styles/settings/shadows';
40
+ @import '../../../../lib/styles/mixins/dropdown-surface';
43
41
 
44
42
  .ds-dropdown {
45
- background-color: $color-default-background;
43
+ @include dropdownSurface;
44
+
45
+ // Resets for the styles vue-popperjs ships in vue-popper.css.
46
46
  border: 0;
47
47
  border-radius: 0;
48
- box-shadow: $shadow-m;
49
48
  max-width: 100%;
50
49
  min-width: 128px;
51
- overflow: hidden;
52
50
  padding: 0;
53
51
  text-align: left;
54
52
 
55
53
  &.-ds-radiusBottom {
56
- border-bottom-left-radius: $radius-s;
57
- border-bottom-right-radius: $radius-s;
54
+ @include dropdownSurfaceRadiusBottom;
58
55
  }
59
56
 
60
57
  &.-ds-radiusTop {
61
- border-top-left-radius: $radius-s;
62
- border-top-right-radius: $radius-s;
58
+ @include dropdownSurfaceRadiusTop;
63
59
  }
64
60
 
65
61
  &[x-placement^='bottom'] {
@@ -104,6 +104,21 @@ describe('CheckboxGroupField', () => {
104
104
  );
105
105
  });
106
106
 
107
+ it('sets aria-describedby only when a message is rendered', () => {
108
+ const wrapper = mount(CheckboxGroupField, {
109
+ props: {
110
+ label: 'Test Label',
111
+ },
112
+ slots: {
113
+ field: '<div>Content</div>',
114
+ },
115
+ });
116
+
117
+ expect(
118
+ wrapper.find('.ds-checkboxGroupField').attributes('aria-describedby'),
119
+ ).toBeUndefined();
120
+ });
121
+
107
122
  describe('vee-validate integration', () => {
108
123
  it('integrates with vee-validate form context and reflects initial values', async () => {
109
124
  // eslint-disable-next-line vue/one-component-per-file
@@ -5,7 +5,7 @@
5
5
  v-model="value"
6
6
  class="ds-checkboxGroupField"
7
7
  role="group"
8
- :aria-describedby="messageId"
8
+ :aria-describedby="hasMessage ? messageId : undefined"
9
9
  :aria-labelledby="labelId"
10
10
  :disabled="formFieldProps.state === FORM_FIELD_STATES.DISABLED"
11
11
  loop
@@ -64,7 +64,7 @@ const {
64
64
  ...rest
65
65
  } = defineProps<CheckboxGroupFieldProps>();
66
66
 
67
- defineSlots<CheckboxGroupFieldSlots>();
67
+ const slots = defineSlots<CheckboxGroupFieldSlots>();
68
68
 
69
69
  const modelValue = defineModel<Array<string>>({
70
70
  default: [],
@@ -77,6 +77,9 @@ const formFieldProps = computed<FormFieldProps>(() => {
77
77
  return extractFormFieldProps(rest, errors.value);
78
78
  });
79
79
 
80
+ // Avoids pointing `aria-describedby` at a message element that is never rendered.
81
+ const hasMessage = computed(() => !!(formFieldProps.value.messageText || slots.message));
82
+
80
83
  const mappedState = computed(() => {
81
84
  const stateMap: Record<FormFieldState, CheckboxState> = {
82
85
  [FORM_FIELD_STATES.DEFAULT]: CHECKBOX_STATES.DEFAULT,