@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
@@ -0,0 +1,342 @@
1
+ <template>
2
+ <form-field v-bind="formFieldProps">
3
+ <template #field="{ fieldId, messageId }">
4
+ <select-root
5
+ v-model="value"
6
+ :autocomplete="autocomplete"
7
+ :disabled="isDisabled"
8
+ :required="formFieldProps.hasRequiredIndicator"
9
+ @update:open="onOpenChange"
10
+ >
11
+ <select-trigger
12
+ :id="fieldId"
13
+ :aria-describedby="hasMessage ? messageId : undefined"
14
+ :aria-label="formFieldProps.label ? undefined : ariaLabel"
15
+ :class="[
16
+ 'ds-selectField__trigger',
17
+ {
18
+ '-ds-error': formFieldProps.state === FORM_FIELD_STATES.ERROR,
19
+ '-ds-disabled': isDisabled,
20
+ },
21
+ ]"
22
+ >
23
+ <ds-icon
24
+ v-if="leftIcon"
25
+ class="ds-selectField__leftIcon"
26
+ :icon="leftIcon"
27
+ :size="ICON_SIZES.X_SMALL"
28
+ />
29
+ <select-value class="ds-selectField__value" :placeholder="placeholder" />
30
+ <select-icon as-child>
31
+ <ds-icon
32
+ class="ds-selectField__icon"
33
+ :icon="ICONS.FA_CHEVRON_DOWN"
34
+ :size="ICON_SIZES.X_SMALL"
35
+ />
36
+ </select-icon>
37
+ </select-trigger>
38
+
39
+ <select-portal>
40
+ <select-content
41
+ class="ds-selectField__content"
42
+ position="popper"
43
+ align="start"
44
+ :side-offset="4"
45
+ :style="contentStyle"
46
+ >
47
+ <select-viewport as-child>
48
+ <ds-select-list>
49
+ <template
50
+ v-for="(group, groupIndex) in normalizedOptions"
51
+ :key="groupIndex"
52
+ >
53
+ <select-separator v-if="groupIndex > 0" as-child>
54
+ <ds-select-list-item-divider />
55
+ </select-separator>
56
+
57
+ <!-- A labelled group gets real `role="group"` semantics. -->
58
+ <select-group v-if="group.label">
59
+ <select-label as-child>
60
+ <ds-select-list-section-title
61
+ :label="group.label"
62
+ :is-uppercase="isGroupLabelUppercase"
63
+ />
64
+ </select-label>
65
+ <select-field-option
66
+ v-for="option in group.options"
67
+ :key="option.value"
68
+ :option="option"
69
+ :is-selected="option.value === value"
70
+ />
71
+ </select-group>
72
+
73
+ <!--
74
+ Ungrouped options render as direct children of the listbox:
75
+ an unlabelled SelectGroup would emit `role="group"` with a
76
+ dangling `aria-labelledby`.
77
+ -->
78
+ <template v-else>
79
+ <select-field-option
80
+ v-for="option in group.options"
81
+ :key="option.value"
82
+ :option="option"
83
+ :is-selected="option.value === value"
84
+ />
85
+ </template>
86
+ </template>
87
+ </ds-select-list>
88
+ </select-viewport>
89
+ </select-content>
90
+ </select-portal>
91
+ </select-root>
92
+ </template>
93
+ <!-- begin: FormField slots -->
94
+ <template v-if="$slots.help" #help>
95
+ <slot name="help" />
96
+ </template>
97
+ <template v-if="$slots.labelAside" #labelAside>
98
+ <slot name="labelAside" />
99
+ </template>
100
+ <template v-if="$slots.message" #message="{ messageId }">
101
+ <slot name="message" :message-id="messageId" />
102
+ </template>
103
+ <template v-if="$slots.fieldStatus" #fieldStatus>
104
+ <slot name="fieldStatus" />
105
+ </template>
106
+ <!-- end: FormField slots -->
107
+ </form-field>
108
+ </template>
109
+
110
+ <style lang="scss" scoped>
111
+ @import '../../../../styles/settings/spacings';
112
+ @import '../../../../styles/settings/radiuses';
113
+ @import '../../../../styles/settings/borders';
114
+ @import '../../../../styles/settings/colors/tokens';
115
+ @import '../../../../styles/settings/typography/tokens';
116
+ @import '../../../../styles/settings/shadows';
117
+ @import '../../../../styles/settings/animations';
118
+
119
+ .ds-selectField {
120
+ $root: &;
121
+
122
+ // The element rules come before `&__trigger` so that the trigger's state overrides, which
123
+ // target the same elements at a higher specificity, stay in ascending order.
124
+ &__leftIcon {
125
+ color: $color-neutral-icon-weak;
126
+ }
127
+
128
+ &__value {
129
+ @include formText-s-default-regular;
130
+
131
+ color: $color-neutral-text-heavy;
132
+ flex: 1;
133
+ // override the default min-width so long values can ellipsize
134
+ min-width: 0;
135
+ overflow: hidden;
136
+ text-align: left;
137
+ text-overflow: ellipsis;
138
+ white-space: nowrap;
139
+
140
+ &[data-placeholder] {
141
+ color: $color-neutral-text-weak;
142
+ }
143
+ }
144
+
145
+ &__icon {
146
+ color: $color-neutral-icon;
147
+ transition: $default-cubic-bezier-transition;
148
+ }
149
+
150
+ &__trigger {
151
+ align-items: center;
152
+ // FormField__field is `align-items: flex-start`, so the trigger must opt into full width.
153
+ align-self: stretch;
154
+ background: $color-default-background;
155
+ border: $border-xs solid $color-neutral-border-strong;
156
+ border-radius: $radius-s;
157
+ box-shadow: $shadow-inset-m;
158
+ cursor: pointer;
159
+ display: flex;
160
+ font-family: inherit;
161
+ gap: $space-4;
162
+ min-height: 32px;
163
+ // Focus is conveyed by the border color below, so the UA ring is suppressed
164
+ outline: none;
165
+ padding: 0 $space-4;
166
+ text-align: left;
167
+
168
+ // The trigger is a button, so `[data-state='open']` replaces InputField's `:focus-within`.
169
+ &[data-state='open'] {
170
+ border-color: $color-primary-border;
171
+
172
+ #{$root}__icon {
173
+ transform: rotate(180deg);
174
+ }
175
+ }
176
+
177
+ &:focus-visible {
178
+ border-color: $color-primary-border;
179
+ }
180
+
181
+ &.-ds-disabled {
182
+ border-color: $color-neutral-border-strong-disabled;
183
+ box-shadow: none;
184
+ cursor: default;
185
+
186
+ #{$root}__leftIcon {
187
+ color: $color-neutral-icon-weak-disabled;
188
+ }
189
+
190
+ #{$root}__value {
191
+ color: $color-neutral-text-heavy-disabled;
192
+
193
+ &[data-placeholder] {
194
+ color: $color-neutral-text-weak-disabled;
195
+ }
196
+ }
197
+
198
+ #{$root}__icon {
199
+ color: $color-neutral-icon-disabled;
200
+ }
201
+ }
202
+
203
+ // `:focus-visible` is excluded so that focusing an errored trigger shows the primary
204
+ // border, the same way InputField's error rule yields to `:focus-within`.
205
+ &.-ds-error:not([data-state='open']):not(:focus-visible) {
206
+ border-color: $color-danger-border;
207
+ }
208
+
209
+ &:hover:not(.-ds-disabled):not([data-state='open']) {
210
+ border-color: $color-neutral-border-strong-hovered;
211
+
212
+ #{$root}__icon {
213
+ color: $color-neutral-icon-hovered;
214
+ }
215
+ }
216
+ }
217
+ }
218
+ </style>
219
+
220
+ <!--
221
+ `ds-selectField__content` is styled unscoped on purpose. SelectContent renders through
222
+ Presence's slot rather than as its root, so Vue never forwards this component's scope id to
223
+ that element — a scoped rule would not match it. It is also portalled to `<body>`, so
224
+ `:deep()` has no ancestor to hang off either. Same approach as DatePicker.vue.
225
+ -->
226
+ <style lang="scss">
227
+ @import '../../../../styles/settings/z-indexes';
228
+ @import '../../../../styles/mixins/dropdown-surface';
229
+
230
+ .ds-selectField__content {
231
+ @include dropdownSurface;
232
+ @include dropdownSurfaceRadiusBottom;
233
+
234
+ // SelectViewport owns the scrolling; this is the flex column that caps the height.
235
+ display: flex;
236
+ flex-direction: column;
237
+ max-height: min(
238
+ var(--select-field-max-height, 320px),
239
+ var(--reka-select-content-available-height)
240
+ );
241
+ // Widens to fit the longest option but never narrower than the trigger, and never wider
242
+ // than the space available in the viewport.
243
+ max-width: var(--reka-select-content-available-width);
244
+ min-width: var(--reka-select-trigger-width);
245
+ // Portalled to <body>, so it needs to clear modal content. It sits on the modal layer
246
+ // rather than above it: the listbox is appended after the (also teleported) modal, so an
247
+ // equal z-index still paints it on top, and tooltips at 16777271 stay above both.
248
+ z-index: $z-index-modal;
249
+ }
250
+ </style>
251
+
252
+ <script lang="ts" setup>
253
+ import { computed, CSSProperties } from 'vue';
254
+ import {
255
+ SelectContent,
256
+ SelectGroup,
257
+ SelectIcon,
258
+ SelectLabel,
259
+ SelectPortal,
260
+ SelectRoot,
261
+ SelectSeparator,
262
+ SelectTrigger,
263
+ SelectValue,
264
+ SelectViewport,
265
+ } from 'reka-ui';
266
+ import FormField, { FORM_FIELD_STATES, FormFieldProps } from '../FormField';
267
+ import { extractFormFieldProps } from '../FormField/FormField.utils';
268
+ import DsIcon, { ICON_SIZES, ICONS } from '../../Icons/Icon';
269
+ import DsSelectList from '../../SelectList/SelectList.vue';
270
+ import DsSelectListItemDivider from '../../SelectList/SelectListItemDivider/SelectListItemDivider.vue';
271
+ import DsSelectListSectionTitle from '../../SelectList/SelectListSectionTitle/SelectListSectionTitle.vue';
272
+ import SelectFieldOption from './SelectFieldOption.vue';
273
+ import { SelectFieldProps, SelectFieldSlots, SelectFieldValue } from './SelectField.types';
274
+ import { assertOptionValues, normalizeOptions, toCssLength } from './SelectField.utils';
275
+ import { useSelectFieldWithinForm } from './useSelectFieldWithinForm';
276
+
277
+ const {
278
+ options,
279
+ placeholder,
280
+ leftIcon,
281
+ ariaLabel,
282
+ maxHeight,
283
+ name,
284
+ autocomplete,
285
+ isGroupLabelUppercase = true,
286
+ ...rest
287
+ } = defineProps<SelectFieldProps>();
288
+
289
+ const slots = defineSlots<SelectFieldSlots>();
290
+
291
+ const emit = defineEmits<{
292
+ 'open-change': [isOpen: boolean];
293
+ }>();
294
+
295
+ const modelValue = defineModel<SelectFieldValue>();
296
+
297
+ const { value, errors, onClose } = useSelectFieldWithinForm(() => name, modelValue);
298
+
299
+ const { formFieldProps, isDisabled, hasMessage } = useFormFieldState();
300
+ const { normalizedOptions } = useOptions();
301
+
302
+ const contentStyle = computed<CSSProperties | undefined>(() => {
303
+ const resolvedMaxHeight = toCssLength(maxHeight);
304
+
305
+ return resolvedMaxHeight
306
+ ? ({ '--select-field-max-height': resolvedMaxHeight } as CSSProperties)
307
+ : undefined;
308
+ });
309
+
310
+ function onOpenChange(isOpen: boolean) {
311
+ // Closing the listbox is a select's equivalent of blur.
312
+ if (!isOpen) {
313
+ onClose(new Event('blur'));
314
+ }
315
+
316
+ emit('open-change', isOpen);
317
+ }
318
+
319
+ function useFormFieldState() {
320
+ const formFieldProps = computed<FormFieldProps>(() =>
321
+ // this is needed to avoid passing modelValue to FormField as prop
322
+ extractFormFieldProps(rest, errors.value),
323
+ );
324
+
325
+ const isDisabled = computed(() => formFieldProps.value.state === FORM_FIELD_STATES.DISABLED);
326
+
327
+ // Avoids pointing `aria-describedby` at a message element that is never rendered.
328
+ const hasMessage = computed(() => !!(formFieldProps.value.messageText || slots.message));
329
+
330
+ return { formFieldProps, isDisabled, hasMessage };
331
+ }
332
+
333
+ function useOptions() {
334
+ const normalizedOptions = computed(() => normalizeOptions(options));
335
+
336
+ // Dev-time guard, called synchronously so invalid options fail at mount instead of
337
+ // surfacing as an unhandled rejection while the offscreen content fragment renders.
338
+ assertOptionValues(normalizedOptions.value);
339
+
340
+ return { normalizedOptions };
341
+ }
342
+ </script>
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <select-item
3
+ as-child
4
+ :value="option.value"
5
+ :disabled="option.isDisabled"
6
+ :text-value="option.label"
7
+ :aria-label="option.eyebrowText ? getOptionAccessibleName(option) : undefined"
8
+ class="ds-selectFieldOption"
9
+ >
10
+ <ds-select-list-item
11
+ :label="option.label"
12
+ :icon-left="option.iconLeft"
13
+ :eyebrow-text="option.eyebrowText"
14
+ :is-selected="isSelected"
15
+ :state="
16
+ option.isDisabled
17
+ ? SELECT_LIST_ITEM_STATES.DISABLED
18
+ : SELECT_LIST_ITEM_STATES.DEFAULT
19
+ "
20
+ >
21
+ <!--
22
+ reka-ui names each option via `aria-labelledby` pointing at the id rendered by
23
+ SelectItemText, so the visible label must live inside it.
24
+ -->
25
+ <template #text>
26
+ <select-item-text>{{ option.label }}</select-item-text>
27
+ </template>
28
+ </ds-select-list-item>
29
+ </select-item>
30
+ </template>
31
+
32
+ <style lang="scss">
33
+ .ds-selectFieldOption {
34
+ // The highlighted state is conveyed by SelectListItem's own styling, so the UA focus ring
35
+ // is suppressed
36
+ outline: none;
37
+ }
38
+ </style>
39
+
40
+ <script lang="ts" setup>
41
+ import { SelectItem, SelectItemText } from 'reka-ui';
42
+ import DsSelectListItem from '../../SelectList/SelectListItem/SelectListItem.vue';
43
+ import { SELECT_LIST_ITEM_STATES } from '../../SelectList/SelectListItem/SelectListItem.consts';
44
+ import { SelectFieldOption as SelectFieldOptionType } from './SelectField.types';
45
+ import { getOptionAccessibleName } from './SelectField.utils';
46
+
47
+ const { option, isSelected = false } = defineProps<{
48
+ option: SelectFieldOptionType;
49
+ isSelected?: boolean;
50
+ }>();
51
+ </script>
@@ -0,0 +1,5 @@
1
+ import SelectField from './SelectField.vue';
2
+
3
+ export * from './SelectField.types';
4
+
5
+ export default SelectField;
@@ -0,0 +1,27 @@
1
+ import { MaybeRefOrGetter, Ref } from 'vue';
2
+ import { useFormFieldWithinForm } from '../../../composables/useFormFieldWithinForm';
3
+ import { SelectFieldValue } from './SelectField.types';
4
+
5
+ /**
6
+ * Select counterpart of `useTextFieldWithinForm`. A select has no `input` event, so closing
7
+ * the listbox stands in for blur — that is when the field is marked touched.
8
+ *
9
+ * Revalidation on selection needs no handler here: vee-validate's `useField` defaults to
10
+ * `validateOnValueUpdate: true`, so changing the value already re-runs validation.
11
+ */
12
+ export function useSelectFieldWithinForm(
13
+ name: MaybeRefOrGetter<string | undefined>,
14
+ modelValue: Ref<SelectFieldValue | undefined>,
15
+ ) {
16
+ const { value, errors, field } = useFormFieldWithinForm(name, modelValue);
17
+
18
+ const onClose = (event: Event) => {
19
+ field?.handleBlur(event, true);
20
+ };
21
+
22
+ return {
23
+ value,
24
+ errors,
25
+ onClose,
26
+ };
27
+ }