@bethinkpl/design-system 44.0.0 → 45.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 (53) hide show
  1. package/dist/design-system.css +1 -1
  2. package/dist/design-system.js +9623 -9592
  3. package/dist/design-system.js.map +1 -1
  4. package/dist/lib/js/components/Buttons/IconButton/IconButton.vue.d.ts +1 -1
  5. package/dist/lib/js/components/Callout/Callout.consts.d.ts +32 -0
  6. package/dist/lib/js/components/{ActionContent/ActionContent.vue.d.ts → Callout/Callout.vue.d.ts} +17 -6
  7. package/dist/lib/js/components/Callout/index.d.ts +4 -0
  8. package/dist/lib/js/components/DatePickers/DateBox/DateBox.vue.d.ts +1 -1
  9. package/dist/lib/js/components/DatePickers/DatePicker/DatePicker.vue.d.ts +5 -5
  10. package/dist/lib/js/components/DatePickers/DateRangePicker/DateRangePicker.vue.d.ts +2 -2
  11. package/dist/lib/js/components/Divider/Divider.vue.d.ts +1 -1
  12. package/dist/lib/js/components/Drawer/DrawerDivider/DrawerDivider.vue.d.ts +1 -1
  13. package/dist/lib/js/components/Drawer/DrawerSection/DrawerSection.vue.d.ts +4 -4
  14. package/dist/lib/js/components/Drawer/DrawerTile/DrawerTile.vue.d.ts +4 -4
  15. package/dist/lib/js/components/Dropdown/Dropdown.vue.d.ts +1 -1
  16. package/dist/lib/js/components/Form/FormField/FormField.utils.d.ts +1 -1
  17. package/dist/lib/js/components/Form/InputField/InputField.vue.d.ts +2 -2
  18. package/dist/lib/js/components/Form/PasswordField/PasswordField.vue.d.ts +2 -2
  19. package/dist/lib/js/components/Form/SelectField/SelectField.vue.d.ts +2 -2
  20. package/dist/lib/js/components/Form/TextAreaField/TextAreaField.vue.d.ts +2 -2
  21. package/dist/lib/js/components/Headers/OverlayHeader/OverlayHeader.vue.d.ts +5 -5
  22. package/dist/lib/js/components/Headers/PageHeader/PageHeader.vue.d.ts +1 -1
  23. package/dist/lib/js/components/Headers/SectionHeader/SectionHeader.vue.d.ts +3 -3
  24. package/dist/lib/js/components/IconText/IconText.vue.d.ts +1 -1
  25. package/dist/lib/js/components/Image/Image.vue.d.ts +1 -1
  26. package/dist/lib/js/components/Menu/MenuDivider/MenuDivider.vue.d.ts +1 -1
  27. package/dist/lib/js/components/Modals/Modal/Modal.vue.d.ts +2 -2
  28. package/dist/lib/js/components/Modals/ModalDialog/ModalDialog.vue.d.ts +2 -2
  29. package/dist/lib/js/components/Pagination/Pagination.vue.d.ts +2 -2
  30. package/dist/lib/js/components/RichList/BasicRichListItem/BasicRichListItem.vue.d.ts +7 -7
  31. package/dist/lib/js/components/RichList/RichListItem/RichListItem.vue.d.ts +3 -3
  32. package/dist/lib/js/components/SelectList/SelectListItemDivider/SelectListItemDivider.vue.d.ts +1 -1
  33. package/dist/lib/js/components/Skeleton/Skeleton.vue.d.ts +1 -1
  34. package/dist/lib/js/components/SurveyQuestions/SurveyQuestionOpenEnded/SurveyQuestionOpenEnded.vue.d.ts +3 -3
  35. package/dist/lib/js/components/SurveyQuestions/SurveyQuestionScale/SurveyQuestionScale.vue.d.ts +3 -3
  36. package/dist/lib/js/components/Tile/Tile.sb.shared.d.ts +2 -2
  37. package/dist/lib/js/components/Tile/Tile.vue.d.ts +2 -2
  38. package/dist/lib/js/components/Toggles/ToggleButton/ToggleButton.vue.d.ts +1 -1
  39. package/dist/lib/js/index.d.ts +2 -1
  40. package/lib/js/components/Callout/Callout.consts.ts +25 -0
  41. package/lib/js/components/Callout/Callout.spec.ts +257 -0
  42. package/lib/js/components/Callout/Callout.stories.ts +209 -0
  43. package/lib/js/components/Callout/Callout.vue +161 -0
  44. package/lib/js/components/Callout/index.ts +4 -0
  45. package/lib/js/components/Cards/Card/Card.vue +1 -1
  46. package/lib/js/components/Form/PasswordField/PasswordField.vue +1 -1
  47. package/lib/js/index.ts +2 -1
  48. package/package.json +1 -1
  49. package/dist/lib/js/components/ActionContent/index.d.ts +0 -3
  50. package/lib/js/components/ActionContent/ActionContent.spec.ts +0 -99
  51. package/lib/js/components/ActionContent/ActionContent.stories.ts +0 -141
  52. package/lib/js/components/ActionContent/ActionContent.vue +0 -104
  53. package/lib/js/components/ActionContent/index.ts +0 -3
@@ -1046,9 +1046,9 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
1046
1046
  type: string;
1047
1047
  touchableDeprecated: boolean;
1048
1048
  color: string;
1049
+ elevation: string;
1049
1050
  radius: string;
1050
1051
  state: string;
1051
- elevation: string;
1052
1052
  colorScheme: string;
1053
1053
  }, {}, {
1054
1054
  WnlIcon: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
@@ -1655,7 +1655,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
1655
1655
  };
1656
1656
  }, {}, {
1657
1657
  calcHeaderFeatureIconColor(): any;
1658
- calcFooterPrimaryButtonColor(): "neutral" | "primary";
1658
+ calcFooterPrimaryButtonColor(): "primary" | "neutral";
1659
1659
  calcFooterSecondaryButtonColor(): "danger" | "neutral";
1660
1660
  calcSingleColumn(): boolean;
1661
1661
  displayFooter(): any;
@@ -2351,9 +2351,9 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
2351
2351
  type: string;
2352
2352
  touchableDeprecated: boolean;
2353
2353
  color: string;
2354
+ elevation: string;
2354
2355
  radius: string;
2355
2356
  state: string;
2356
- elevation: string;
2357
2357
  colorScheme: string;
2358
2358
  }, {}, {
2359
2359
  WnlIcon: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
@@ -1107,9 +1107,9 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
1107
1107
  type: string;
1108
1108
  touchableDeprecated: boolean;
1109
1109
  color: string;
1110
+ elevation: string;
1110
1111
  radius: string;
1111
1112
  state: string;
1112
- elevation: string;
1113
1113
  colorScheme: string;
1114
1114
  }, {}, {
1115
1115
  WnlIcon: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
@@ -1889,7 +1889,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
1889
1889
  };
1890
1890
  }, {}, {
1891
1891
  calcHeaderFeatureIconColor(): any;
1892
- calcFooterPrimaryButtonColor(): "neutral" | "primary";
1892
+ calcFooterPrimaryButtonColor(): "primary" | "neutral";
1893
1893
  calcFooterSecondaryButtonColor(): "danger" | "neutral";
1894
1894
  calcSingleColumn(): boolean;
1895
1895
  displayFooter(): any;
@@ -2585,9 +2585,9 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
2585
2585
  type: string;
2586
2586
  touchableDeprecated: boolean;
2587
2587
  color: string;
2588
+ elevation: string;
2588
2589
  radius: string;
2589
2590
  state: string;
2590
- elevation: string;
2591
2591
  colorScheme: string;
2592
2592
  }, {}, {
2593
2593
  WnlIcon: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
@@ -299,8 +299,8 @@ export declare const data: () => {
299
299
  readonly ACCENT: "accent";
300
300
  }>;
301
301
  allowedColorsToBorderColorsMap: {
302
- neutral: ("neutral" | "neutralWeak" | "primary" | "primaryWeak")[];
303
- neutralWeak: ("neutral" | "neutralWeak" | "primary" | "primaryWeak")[];
302
+ neutral: ("primary" | "neutral" | "neutralWeak" | "primaryWeak")[];
303
+ neutralWeak: ("primary" | "neutral" | "neutralWeak" | "primaryWeak")[];
304
304
  primary: ("primary" | "primaryWeak")[];
305
305
  success: ("success" | "successWeak")[];
306
306
  fail: ("fail" | "failWeak")[];
@@ -126,11 +126,11 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
126
126
  };
127
127
  }>> & Readonly<{}>, {
128
128
  color: import('./Tile.consts').TileColor;
129
- eyebrowText: string;
130
- isEyebrowTextUppercase: boolean;
131
129
  state: import('./Tile.consts').TileState;
132
130
  iconLeft: Record<string, any>;
133
131
  iconRight: Record<string, any>;
132
+ eyebrowText: string;
133
+ isEyebrowTextUppercase: boolean;
134
134
  interactive: boolean;
135
135
  additionalText: string;
136
136
  eyebrowEllipsis: boolean;
@@ -433,10 +433,10 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
433
433
  label: string;
434
434
  color: ToggleButtonColor;
435
435
  radius: ToggleButtonRadius;
436
- isInteractive: boolean;
437
436
  state: ToggleButtonState;
438
437
  iconLeft: any;
439
438
  iconRight: any;
439
+ isInteractive: boolean;
440
440
  isLabelUppercase: boolean;
441
441
  isSelected: boolean;
442
442
  hasSmallHorizontalPadding: boolean;
@@ -1,4 +1,3 @@
1
- export { default as DsActionContent } from './components/ActionContent';
2
1
  export { default as Avatar } from './components/Avatar';
3
2
  export * from './components/Avatar/Avatar.consts';
4
3
  export { default as Badge } from './components/Badge';
@@ -15,6 +14,8 @@ export * from './components/Buttons/Button/Button.consts';
15
14
  export { default as ToggleButton } from './components/Toggles/ToggleButton';
16
15
  export { default as DsToggleButton } from './components/Toggles/ToggleButton';
17
16
  export * from './components/Toggles/ToggleButton/ToggleButton.consts';
17
+ export { default as DsCallout } from './components/Callout';
18
+ export * from './components/Callout/Callout.consts';
18
19
  export { default as Card } from './components/Cards/Card/';
19
20
  export * from './components/Cards/Card';
20
21
  export { default as DsCard } from './components/Cards/Card';
@@ -0,0 +1,25 @@
1
+ import { Value } from '../../utils/type.utils';
2
+ import { FEATURE_ICON_COLOR, FeatureIconColor } from '../Icons/FeatureIcon/FeatureIcon.consts';
3
+ import { TEXT_GROUP_MAIN_TEXT_COLORS, TextGroupMainTextColor } from '../TextGroup/TextGroup.consts';
4
+
5
+ export const CALLOUT_LAYOUTS = {
6
+ VERTICAL: 'vertical',
7
+ HORIZONTAL: 'horizontal',
8
+ } as const;
9
+
10
+ export type CalloutLayout = Value<typeof CALLOUT_LAYOUTS>;
11
+
12
+ export const CALLOUT_SIZES = {
13
+ SMALL: 'small',
14
+ MEDIUM: 'medium',
15
+ LARGE: 'large',
16
+ } as const;
17
+
18
+ export type CalloutSize = Value<typeof CALLOUT_SIZES>;
19
+
20
+ // Aliased so the sets can never drift from the components they are forwarded to.
21
+ export const CALLOUT_ICON_COLORS = FEATURE_ICON_COLOR;
22
+ export type CalloutIconColor = FeatureIconColor;
23
+
24
+ export const CALLOUT_MAIN_TEXT_COLORS = TEXT_GROUP_MAIN_TEXT_COLORS;
25
+ export type CalloutMainTextColor = TextGroupMainTextColor;
@@ -0,0 +1,257 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import { ComponentProps } from 'vue-component-type-helpers';
4
+ import { h } from 'vue';
5
+ import Callout from './Callout.vue';
6
+ import {
7
+ CALLOUT_ICON_COLORS,
8
+ CALLOUT_LAYOUTS,
9
+ CALLOUT_MAIN_TEXT_COLORS,
10
+ CALLOUT_SIZES,
11
+ } from './Callout.consts';
12
+ import DsFeatureIcon from '../Icons/FeatureIcon';
13
+ import { FEATURE_ICON_SIZES } from '../Icons/FeatureIcon/FeatureIcon.consts';
14
+ import DsTextGroup from '../TextGroup/TextGroup.vue';
15
+ import { TEXT_GROUP_ALIGNS, TEXT_GROUP_SIZES } from '../TextGroup/TextGroup.consts';
16
+ import DsButton from '../Buttons/Button';
17
+ import { BUTTON_COLORS, BUTTON_RADIUSES, BUTTON_SIZES, BUTTON_TYPES } from '../Buttons/Button';
18
+ import { ICONS } from '../Icons/Icon';
19
+
20
+ type MountOptions = Omit<NonNullable<Parameters<typeof mount<typeof Callout>>[1]>, 'props'> & {
21
+ props?: Partial<ComponentProps<typeof Callout>>;
22
+ };
23
+
24
+ const mountCallout = (options: MountOptions = {}) =>
25
+ mount(Callout, {
26
+ ...options,
27
+ props: { icon: ICONS.FA_COMMENT_DOTS, ...options.props },
28
+ });
29
+
30
+ describe('Callout', () => {
31
+ it('renders', () => {
32
+ const wrapper = mountCallout();
33
+
34
+ expect(wrapper.exists()).toBe(true);
35
+ });
36
+
37
+ it('renders the feature icon', () => {
38
+ const wrapper = mountCallout();
39
+
40
+ expect(wrapper.findComponent(DsFeatureIcon).exists()).toBe(true);
41
+ expect(wrapper.findComponent(DsFeatureIcon).props('icon')).toStrictEqual(
42
+ ICONS.FA_COMMENT_DOTS,
43
+ );
44
+ });
45
+
46
+ it('passes main and supporting text to the text group', () => {
47
+ const wrapper = mountCallout({
48
+ props: {
49
+ mainText: 'Main text',
50
+ supportingText: 'Supporting text',
51
+ },
52
+ });
53
+
54
+ expect(wrapper.find('.ds-callout__text').text()).toContain('Main text');
55
+ expect(wrapper.find('.ds-callout__text').text()).toContain('Supporting text');
56
+ });
57
+
58
+ describe('layout', () => {
59
+ it.each(Object.values(CALLOUT_LAYOUTS))('applies the layout class for "%s"', (layout) => {
60
+ const wrapper = mountCallout({ props: { layout } });
61
+
62
+ expect(wrapper.find('.ds-callout').classes()).toContain(`-ds-layout-${layout}`);
63
+ });
64
+
65
+ it('is vertical by default', () => {
66
+ const wrapper = mountCallout();
67
+
68
+ expect(wrapper.find('.ds-callout').classes()).toContain('-ds-layout-vertical');
69
+ });
70
+
71
+ it.each([
72
+ [CALLOUT_LAYOUTS.VERTICAL, TEXT_GROUP_ALIGNS.CENTER],
73
+ [CALLOUT_LAYOUTS.HORIZONTAL, TEXT_GROUP_ALIGNS.LEFT],
74
+ ])('aligns the text group for layout "%s"', (layout, expectedAlign) => {
75
+ const wrapper = mountCallout({ props: { layout } });
76
+
77
+ expect(wrapper.findComponent(DsTextGroup).props('align')).toBe(expectedAlign);
78
+ });
79
+ });
80
+
81
+ describe('size', () => {
82
+ it('is medium by default', () => {
83
+ const wrapper = mountCallout();
84
+
85
+ expect(wrapper.findComponent(DsFeatureIcon).props('size')).toBe(
86
+ FEATURE_ICON_SIZES.MEDIUM,
87
+ );
88
+ expect(wrapper.findComponent(DsTextGroup).props('size')).toBe(TEXT_GROUP_SIZES.LARGE);
89
+ });
90
+
91
+ it.each([
92
+ [CALLOUT_SIZES.SMALL, FEATURE_ICON_SIZES.MEDIUM, TEXT_GROUP_SIZES.MEDIUM],
93
+ [CALLOUT_SIZES.MEDIUM, FEATURE_ICON_SIZES.MEDIUM, TEXT_GROUP_SIZES.LARGE],
94
+ [CALLOUT_SIZES.LARGE, FEATURE_ICON_SIZES.LARGE, TEXT_GROUP_SIZES.LARGE],
95
+ ])(
96
+ 'maps size "%s" to feature icon "%s" and text group "%s"',
97
+ (size, featureIconSize, textGroupSize) => {
98
+ const wrapper = mountCallout({ props: { size } });
99
+
100
+ expect(wrapper.findComponent(DsFeatureIcon).props('size')).toBe(featureIconSize);
101
+ expect(wrapper.findComponent(DsTextGroup).props('size')).toBe(textGroupSize);
102
+ },
103
+ );
104
+ });
105
+
106
+ describe('icon color', () => {
107
+ it.each(Object.values(CALLOUT_ICON_COLORS))(
108
+ 'passes icon color "%s" to the feature icon',
109
+ (iconColor) => {
110
+ const wrapper = mountCallout({ props: { iconColor } });
111
+
112
+ expect(wrapper.findComponent(DsFeatureIcon).props('color')).toBe(iconColor);
113
+ },
114
+ );
115
+
116
+ it('is primary by default', () => {
117
+ const wrapper = mountCallout();
118
+
119
+ expect(wrapper.findComponent(DsFeatureIcon).props('color')).toBe(
120
+ CALLOUT_ICON_COLORS.PRIMARY,
121
+ );
122
+ });
123
+ });
124
+
125
+ describe('main text color', () => {
126
+ it.each(Object.values(CALLOUT_MAIN_TEXT_COLORS))(
127
+ 'passes main text color "%s" to the text group',
128
+ (mainTextColor) => {
129
+ const wrapper = mountCallout({ props: { mainTextColor } });
130
+
131
+ expect(wrapper.findComponent(DsTextGroup).props('mainTextColor')).toBe(
132
+ mainTextColor,
133
+ );
134
+ },
135
+ );
136
+
137
+ it('is primary by default', () => {
138
+ const wrapper = mountCallout();
139
+
140
+ expect(wrapper.findComponent(DsTextGroup).props('mainTextColor')).toBe(
141
+ CALLOUT_MAIN_TEXT_COLORS.PRIMARY,
142
+ );
143
+ });
144
+ });
145
+
146
+ describe('eyebrow text', () => {
147
+ it('does not render the eyebrow by default', () => {
148
+ const wrapper = mountCallout();
149
+
150
+ expect(wrapper.findComponent(DsTextGroup).props('eyebrowText')).toBe(null);
151
+ expect(wrapper.find('.ds-textGroup__eyebrow').exists()).toBe(false);
152
+ });
153
+
154
+ it('renders the eyebrow when eyebrowText is provided', () => {
155
+ const wrapper = mountCallout({ props: { eyebrowText: 'Eyebrow text' } });
156
+
157
+ expect(wrapper.find('.ds-textGroup__eyebrow').text()).toBe('Eyebrow text');
158
+ });
159
+
160
+ it('never uppercases the eyebrow', () => {
161
+ const wrapper = mountCallout({ props: { eyebrowText: 'Eyebrow text' } });
162
+
163
+ expect(wrapper.findComponent(DsTextGroup).props('isEyebrowTextUppercase')).toBe(false);
164
+ expect(wrapper.find('.ds-textGroup__eyebrow').classes()).not.toContain('-ds-uppercase');
165
+ });
166
+ });
167
+
168
+ describe('actions', () => {
169
+ it('renders a default primary button with the button label and icon', () => {
170
+ const wrapper = mountCallout({
171
+ props: {
172
+ buttonLabel: 'Confirm',
173
+ buttonIcon: ICONS.FA_COMMENT_DOTS,
174
+ },
175
+ });
176
+
177
+ const button = wrapper.findComponent(DsButton);
178
+ expect(button.exists()).toBe(true);
179
+ expect(button.text()).toBe('Confirm');
180
+ expect(button.props('iconLeft')).toStrictEqual(ICONS.FA_COMMENT_DOTS);
181
+ expect(button.props('color')).toBe(BUTTON_COLORS.PRIMARY);
182
+ expect(button.props('type')).toBe(BUTTON_TYPES.FILLED);
183
+ expect(button.props('size')).toBe(BUTTON_SIZES.SMALL);
184
+ expect(button.props('radius')).toBe(BUTTON_RADIUSES.ROUNDED);
185
+ });
186
+
187
+ it.each(Object.values(CALLOUT_ICON_COLORS))(
188
+ 'keeps the filled primary button for icon color "%s"',
189
+ (iconColor) => {
190
+ const wrapper = mountCallout({ props: { buttonLabel: 'Confirm', iconColor } });
191
+
192
+ const button = wrapper.findComponent(DsButton);
193
+ expect(button.props('color')).toBe(BUTTON_COLORS.PRIMARY);
194
+ expect(button.props('type')).toBe(BUTTON_TYPES.FILLED);
195
+ },
196
+ );
197
+
198
+ it('emits button-clicked when the default button is clicked', async () => {
199
+ const wrapper = mountCallout({
200
+ props: {
201
+ buttonLabel: 'Confirm',
202
+ },
203
+ });
204
+
205
+ await wrapper.findComponent(DsButton).trigger('click');
206
+
207
+ expect(wrapper.emitted('button-clicked')).toHaveLength(1);
208
+ });
209
+
210
+ it('renders the actions slot instead of the default button', () => {
211
+ const wrapper = mountCallout({
212
+ props: {
213
+ buttonLabel: 'Default button',
214
+ },
215
+ slots: {
216
+ actions: () => h('span', { class: 'custom-action' }, 'Custom action'),
217
+ },
218
+ });
219
+
220
+ expect(wrapper.find('.custom-action').exists()).toBe(true);
221
+ expect(wrapper.findComponent(DsButton).exists()).toBe(false);
222
+ });
223
+
224
+ it('does not render the actions block when there is no button label and no slot', () => {
225
+ const wrapper = mountCallout();
226
+
227
+ expect(wrapper.find('.ds-callout__actions').exists()).toBe(false);
228
+ expect(wrapper.findComponent(DsButton).exists()).toBe(false);
229
+ });
230
+
231
+ it('renders the actions block when only the slot is provided', () => {
232
+ const wrapper = mountCallout({
233
+ slots: {
234
+ actions: () => h('span', { class: 'custom-action' }, 'Custom action'),
235
+ },
236
+ });
237
+
238
+ expect(wrapper.find('.ds-callout__actions').exists()).toBe(true);
239
+ });
240
+
241
+ it('adds the stacked actions modifier on the root when isActionVertical is set', () => {
242
+ const wrapper = mountCallout({
243
+ props: {
244
+ isActionVertical: true,
245
+ },
246
+ });
247
+
248
+ expect(wrapper.find('.ds-callout').classes()).toContain('-ds-actions-vertical');
249
+ });
250
+
251
+ it('does not add the stacked actions modifier by default', () => {
252
+ const wrapper = mountCallout();
253
+
254
+ expect(wrapper.find('.ds-callout').classes()).not.toContain('-ds-actions-vertical');
255
+ });
256
+ });
257
+ });
@@ -0,0 +1,209 @@
1
+ import { ComponentProps } from 'vue-component-type-helpers';
2
+ import { Meta, StoryObj } from '@storybook/vue3';
3
+ import { action } from '@storybook/addon-actions';
4
+
5
+ import Callout from './Callout.vue';
6
+ import {
7
+ CALLOUT_ICON_COLORS,
8
+ CALLOUT_LAYOUTS,
9
+ CALLOUT_MAIN_TEXT_COLORS,
10
+ CALLOUT_SIZES,
11
+ } from './Callout.consts';
12
+ import DsButton from '../Buttons/Button';
13
+ import { BUTTON_COLORS, BUTTON_SIZES, BUTTON_TYPES } from '../Buttons/Button/Button.consts';
14
+ import { ICONS } from '../Icons/Icon';
15
+
16
+ type CalloutProps = ComponentProps<typeof Callout>;
17
+
18
+ const VERTICAL_FIGMA_URL =
19
+ 'https://www.figma.com/design/izQdYyiBR1GQgFkaOIfIJI/LMS---DS-Components?node-id=17359-1009&m=dev';
20
+ const HORIZONTAL_FIGMA_URL =
21
+ 'https://www.figma.com/design/izQdYyiBR1GQgFkaOIfIJI/LMS---DS-Components?node-id=17760-416&m=dev';
22
+
23
+ /**
24
+ * Clearing a text control yields `''`, which TextGroup renders as a `&nbsp;` placeholder row.
25
+ * Mapping it to `null` removes the row instead, which is what an empty control should mean.
26
+ */
27
+ const emptyToNull = (value?: string | null) => value || null;
28
+
29
+ const mapArgs = (args: CalloutProps) => ({
30
+ ...args,
31
+ icon: ICONS[args.icon],
32
+ buttonIcon: ICONS[args.buttonIcon],
33
+ eyebrowText: emptyToNull(args.eyebrowText),
34
+ mainText: emptyToNull(args.mainText),
35
+ supportingText: emptyToNull(args.supportingText),
36
+ buttonLabel: emptyToNull(args.buttonLabel),
37
+ });
38
+
39
+ const meta: Meta<typeof Callout> = {
40
+ title: 'Components/Callout',
41
+ component: Callout,
42
+ render: (args: CalloutProps) => ({
43
+ components: { Callout },
44
+ setup() {
45
+ return { onButtonClicked: action('button-clicked') };
46
+ },
47
+ computed: {
48
+ props() {
49
+ return mapArgs(args);
50
+ },
51
+ },
52
+ template: `<div style="max-width: 500px;"><Callout v-bind="props" @button-clicked="onButtonClicked" /></div>`,
53
+ }),
54
+ argTypes: {
55
+ layout: {
56
+ control: 'select',
57
+ options: Object.values(CALLOUT_LAYOUTS),
58
+ },
59
+ size: {
60
+ control: 'select',
61
+ options: Object.values(CALLOUT_SIZES),
62
+ },
63
+ iconColor: {
64
+ control: 'select',
65
+ options: Object.values(CALLOUT_ICON_COLORS),
66
+ },
67
+ mainTextColor: {
68
+ control: 'select',
69
+ options: Object.values(CALLOUT_MAIN_TEXT_COLORS),
70
+ },
71
+ icon: {
72
+ control: 'select',
73
+ options: Object.keys(ICONS),
74
+ },
75
+ buttonIcon: {
76
+ control: 'select',
77
+ options: [null, ...Object.keys(ICONS)],
78
+ },
79
+ eyebrowText: { control: 'text' },
80
+ mainText: { control: 'text' },
81
+ supportingText: { control: 'text' },
82
+ buttonLabel: { control: 'text' },
83
+ isActionVertical: { control: 'boolean' },
84
+ },
85
+ };
86
+ export default meta;
87
+
88
+ type Story = StoryObj<typeof Callout>;
89
+
90
+ export const Interactive: Story = {
91
+ args: {
92
+ layout: CALLOUT_LAYOUTS.VERTICAL,
93
+ size: CALLOUT_SIZES.MEDIUM,
94
+ iconColor: CALLOUT_ICON_COLORS.PRIMARY,
95
+ mainTextColor: CALLOUT_MAIN_TEXT_COLORS.PRIMARY,
96
+ icon: 'FA_COMMENT_DOTS' as unknown as CalloutProps['icon'],
97
+ eyebrowText: '',
98
+ mainText: 'Main text tutaj sobie będzie',
99
+ supportingText: 'Supporting text tutaj będzie',
100
+ buttonLabel: 'Button label',
101
+ buttonIcon: null,
102
+ isActionVertical: false,
103
+ },
104
+ };
105
+
106
+ Interactive.parameters = {
107
+ design: {
108
+ type: 'figma',
109
+ url: VERTICAL_FIGMA_URL,
110
+ },
111
+ };
112
+
113
+ export const WithActionsSlot: Story = {
114
+ render: (args: CalloutProps) => ({
115
+ components: { Callout, DsButton },
116
+ setup() {
117
+ return { args, ICONS, BUTTON_COLORS, BUTTON_SIZES, BUTTON_TYPES };
118
+ },
119
+ computed: {
120
+ props() {
121
+ return mapArgs(args);
122
+ },
123
+ },
124
+ template: `
125
+ <div style="max-width: 500px;">
126
+ <Callout v-bind="props">
127
+ <template #actions>
128
+ <ds-button :color="BUTTON_COLORS.NEUTRAL" :type="BUTTON_TYPES.OUTLINED" :size="BUTTON_SIZES.SMALL">Cancel</ds-button>
129
+ <ds-button :color="BUTTON_COLORS.PRIMARY" :type="BUTTON_TYPES.FILLED" :size="BUTTON_SIZES.SMALL">Confirm</ds-button>
130
+ </template>
131
+ </Callout>
132
+ </div>`,
133
+ }),
134
+ args: {
135
+ layout: CALLOUT_LAYOUTS.VERTICAL,
136
+ size: CALLOUT_SIZES.MEDIUM,
137
+ iconColor: CALLOUT_ICON_COLORS.PRIMARY,
138
+ mainTextColor: CALLOUT_MAIN_TEXT_COLORS.PRIMARY,
139
+ icon: 'FA_COMMENT_DOTS' as unknown as CalloutProps['icon'],
140
+ mainText: 'Main text tutaj sobie będzie',
141
+ supportingText: 'Supporting text tutaj będzie',
142
+ isActionVertical: false,
143
+ },
144
+ };
145
+
146
+ WithActionsSlot.parameters = {
147
+ design: {
148
+ type: 'figma',
149
+ url: VERTICAL_FIGMA_URL,
150
+ },
151
+ };
152
+
153
+ /**
154
+ * The full variant matrix, laid out for side-by-side comparison with the Figma component set
155
+ * (node 17696:2260).
156
+ */
157
+ export const Variants: Story = {
158
+ render: () => ({
159
+ components: { Callout },
160
+ setup() {
161
+ return {
162
+ ICONS,
163
+ CALLOUT_LAYOUTS,
164
+ CALLOUT_SIZES,
165
+ sizes: Object.values(CALLOUT_SIZES),
166
+ };
167
+ },
168
+ template: `
169
+ <div style="display: flex; flex-direction: column; gap: 48px;">
170
+ <section v-for="layout in [CALLOUT_LAYOUTS.VERTICAL, CALLOUT_LAYOUTS.HORIZONTAL]" :key="layout">
171
+ <h3 style="margin-bottom: 16px;">{{ layout }}</h3>
172
+ <div style="display: flex; flex-direction: column; gap: 32px; max-width: 500px;">
173
+ <Callout
174
+ v-for="size in sizes"
175
+ :key="size"
176
+ :layout="layout"
177
+ :size="size"
178
+ :icon="ICONS.FA_COMMENT_DOTS"
179
+ :eyebrow-text="size === CALLOUT_SIZES.SMALL ? 'Eyebrow text' : null"
180
+ :main-text="'Main text tutaj sobie będzie (' + size + ')'"
181
+ supporting-text="Supporting text tutaj będzie"
182
+ button-label="Button small"
183
+ />
184
+ <Callout
185
+ :layout="layout"
186
+ :icon="ICONS.FA_COMMENT_DOTS"
187
+ main-text="Main text tutaj sobie będzie (stacked actions)"
188
+ supporting-text="Supporting text tutaj będzie"
189
+ button-label="Button small"
190
+ is-action-vertical
191
+ />
192
+ <Callout
193
+ :layout="layout"
194
+ :icon="ICONS.FA_COMMENT_DOTS"
195
+ main-text="Main text tutaj sobie będzie (no actions)"
196
+ supporting-text="Supporting text tutaj będzie"
197
+ />
198
+ </div>
199
+ </section>
200
+ </div>`,
201
+ }),
202
+ };
203
+
204
+ Variants.parameters = {
205
+ design: {
206
+ type: 'figma',
207
+ url: HORIZONTAL_FIGMA_URL,
208
+ },
209
+ };