@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,42 @@
1
+ import { FormFieldProps, FormFieldSlots } from '../FormField';
2
+ import { PinInputFieldState, PinInputFieldType } from './PinInputField.consts';
3
+
4
+ export interface PinInputFieldProps extends Omit<FormFieldProps, 'state'> {
5
+ /**
6
+ * `loading` replaces the boxes with a spinner.
7
+ *
8
+ * @default 'default'
9
+ */
10
+ state?: PinInputFieldState;
11
+ /**
12
+ * Number of boxes.
13
+ *
14
+ * @default 6
15
+ */
16
+ length?: number;
17
+ /**
18
+ * reka `otp` — `autocomplete="one-time-code"` and focus jumps to the first empty box. Pass
19
+ * `false` for a stored PIN rather than a one-time code.
20
+ *
21
+ * @default true
22
+ */
23
+ otp?: boolean;
24
+ /**
25
+ * `number` sets `inputmode="numeric"`. Non-digits typed or pasted into a numeric field are
26
+ * silently discarded by reka — pass `'text'` for alphanumeric codes.
27
+ *
28
+ * @default 'number'
29
+ */
30
+ type?: PinInputFieldType;
31
+ /**
32
+ * vee-validate field name. Requires a surrounding `useForm()` context.
33
+ */
34
+ name?: string;
35
+ /**
36
+ * Used as `aria-label` when no visible `label` is provided, so the field always has an
37
+ * accessible name.
38
+ */
39
+ ariaLabel?: string;
40
+ }
41
+
42
+ export type PinInputFieldSlots = Omit<FormFieldSlots, 'field'>;
@@ -0,0 +1,5 @@
1
+ export type PinChars = Array<string | number>;
2
+
3
+ export function toPinChars(value: string | undefined, length: number): PinChars {
4
+ return [...(value ?? '')].slice(0, length);
5
+ }
@@ -0,0 +1,211 @@
1
+ <template>
2
+ <form-field v-bind="formFieldProps">
3
+ <template #field="{ fieldId, messageId, labelId }">
4
+ <div v-if="isLoading" class="ds-pinInputField -ds-loading" aria-busy="true">
5
+ <ds-icon
6
+ class="ds-pinInputField__spinner"
7
+ :icon="ICONS.FAD_SPINNER_THIRD"
8
+ :size="ICON_SIZES.LARGE"
9
+ spinning
10
+ />
11
+ </div>
12
+ <!--
13
+ `required` reaches a 1px-clipped, `aria-hidden` input inside reka's root, so in a
14
+ native `<form>` the browser blocks `submit` while the pin is empty and
15
+ `@submit.prevent` never runs. vee-validate forms need `novalidate`. `SelectField`
16
+ forwards it the same way.
17
+ -->
18
+ <pin-input-root
19
+ v-else
20
+ :id="fieldId"
21
+ class="ds-pinInputField"
22
+ role="group"
23
+ :model-value="chars as Array<string>"
24
+ :otp="otp"
25
+ :type="type"
26
+ :required="formFieldProps.hasRequiredIndicator"
27
+ :aria-labelledby="formFieldProps.label ? labelId : undefined"
28
+ :aria-label="formFieldProps.label ? undefined : ariaLabel"
29
+ :aria-describedby="hasMessage ? messageId : undefined"
30
+ @complete="onPinComplete"
31
+ @focusout="onFieldFocusOut"
32
+ @update:model-value="onCharsUpdate"
33
+ >
34
+ <pin-input-input
35
+ v-for="index in length"
36
+ :key="index"
37
+ class="ds-pinInputField__item"
38
+ :index="index - 1"
39
+ />
40
+ </pin-input-root>
41
+ </template>
42
+ <!-- begin: FormField slots -->
43
+ <template v-if="$slots.help" #help>
44
+ <slot name="help" />
45
+ </template>
46
+ <template v-if="$slots.labelAside" #labelAside>
47
+ <slot name="labelAside" />
48
+ </template>
49
+ <template v-if="$slots.message" #message="{ messageId }">
50
+ <slot name="message" :message-id="messageId" />
51
+ </template>
52
+ <template v-if="$slots.fieldStatus" #fieldStatus>
53
+ <slot name="fieldStatus" />
54
+ </template>
55
+ <!-- end: FormField slots -->
56
+ </form-field>
57
+ </template>
58
+
59
+ <style lang="scss" scoped>
60
+ @import '../../../../styles/settings/spacings';
61
+ @import '../../../../styles/settings/radiuses';
62
+ @import '../../../../styles/settings/borders';
63
+ @import '../../../../styles/settings/colors/tokens';
64
+ @import '../../../../styles/settings/typography/tokens';
65
+ @import '../../../../styles/settings/shadows';
66
+
67
+ .ds-pinInputField {
68
+ align-items: center;
69
+ align-self: stretch;
70
+ display: flex;
71
+ gap: $space-4;
72
+
73
+ &.-ds-loading {
74
+ flex-direction: column;
75
+ min-height: 48px;
76
+ }
77
+
78
+ &__item {
79
+ @include formText-m-default-bold;
80
+
81
+ background: $color-default-background;
82
+ border: $border-xs solid $color-neutral-border-strong;
83
+ border-radius: $radius-s;
84
+ box-shadow: $shadow-inset-m;
85
+ color: $color-neutral-text-heavy;
86
+ flex: 0 1 48px;
87
+ height: 48px;
88
+ max-width: 48px;
89
+ min-width: 24px;
90
+ outline: none;
91
+ padding: 0 $space-3;
92
+ text-align: center;
93
+
94
+ &:focus {
95
+ border-color: $color-primary-border;
96
+ }
97
+
98
+ &:hover:not(:focus) {
99
+ border-color: $color-neutral-border-strong-hovered;
100
+ }
101
+ }
102
+
103
+ &__spinner {
104
+ color: $color-neutral-icon;
105
+ }
106
+ }
107
+ </style>
108
+
109
+ <script lang="ts" setup>
110
+ import { computed, ref, watch } from 'vue';
111
+ import { PinInputInput, PinInputRoot } from 'reka-ui';
112
+ import FormField, { FORM_FIELD_STATES, FormFieldProps } from '../FormField';
113
+ import { extractFormFieldProps } from '../FormField/FormField.utils';
114
+ import DsIcon, { ICON_SIZES, ICONS } from '../../Icons/Icon';
115
+ import {
116
+ PIN_INPUT_FIELD_DEFAULT_LENGTH,
117
+ PIN_INPUT_FIELD_STATES,
118
+ PIN_INPUT_FIELD_TYPES,
119
+ } from './PinInputField.consts';
120
+ import { PinInputFieldProps, PinInputFieldSlots } from './PinInputField.types';
121
+ import { PinChars, toPinChars } from './PinInputField.utils';
122
+ import { usePinInputFieldWithinForm } from './usePinInputFieldWithinForm';
123
+
124
+ const {
125
+ length = PIN_INPUT_FIELD_DEFAULT_LENGTH,
126
+ state = PIN_INPUT_FIELD_STATES.DEFAULT,
127
+ otp = true,
128
+ type = PIN_INPUT_FIELD_TYPES.NUMBER,
129
+ ariaLabel,
130
+ name,
131
+ ...rest
132
+ } = defineProps<PinInputFieldProps>();
133
+
134
+ const slots = defineSlots<PinInputFieldSlots>();
135
+
136
+ const emit = defineEmits<{
137
+ /** Fires whenever the pin becomes complete, including programmatic fills. */
138
+ complete: [value: string];
139
+ }>();
140
+
141
+ /** The filled boxes joined, in order. A cleared middle box shortens it (`1 2 _ 4 5 6` → `'12456'`). */
142
+ const modelValue = defineModel<string>();
143
+
144
+ const { value, errors, onComplete, onBlur } = usePinInputFieldWithinForm(() => name, modelValue);
145
+
146
+ // The array, not the joined string, is the source of truth: clearing a middle box leaves a hole in
147
+ // it (`delete` in `type: 'number'` mode, `''` in `type: 'text'` mode), so rebuilding it from the
148
+ // string would shift the later boxes leftwards. In numeric mode reka also stores numbers rather than
149
+ // strings, which its own `modelValue` type does not admit — hence the cast on the binding.
150
+ const chars = ref<PinChars>(toPinChars(value.value, length));
151
+
152
+ // reka rewrites the whole array on every keystroke; `join('')` flattens the numbers it stores in
153
+ // numeric mode and the holes a cleared box leaves behind.
154
+ function onCharsUpdate(next: PinChars) {
155
+ chars.value = next;
156
+ value.value = next.join('');
157
+ }
158
+
159
+ // Inbound only: parent `v-model`, a vee-validate reset, or a shrinking `length`. The write from
160
+ // `onCharsUpdate` lands here too, but is already in sync — rebuilding would collapse a cleared
161
+ // middle box onto its neighbours.
162
+ watch([value, () => length], ([next]) => {
163
+ const nextValue = next ?? '';
164
+
165
+ if (chars.value.length <= length && chars.value.join('') === nextValue) {
166
+ return;
167
+ }
168
+
169
+ chars.value = toPinChars(next, length);
170
+
171
+ // A shrink drops the surplus characters, and the model has to follow: they no longer have a box
172
+ // to edit them in.
173
+ const joined = chars.value.join('');
174
+
175
+ if (joined !== nextValue) {
176
+ value.value = joined;
177
+ }
178
+ });
179
+
180
+ const formFieldProps = computed<FormFieldProps>(() => {
181
+ // this is needed to avoid passing modelValue to FormField as prop
182
+ return extractFormFieldProps(
183
+ {
184
+ ...rest,
185
+ state: state === PIN_INPUT_FIELD_STATES.ERROR ? FORM_FIELD_STATES.ERROR : undefined,
186
+ },
187
+ errors.value,
188
+ );
189
+ });
190
+
191
+ const isLoading = computed(() => state === PIN_INPUT_FIELD_STATES.LOADING);
192
+
193
+ // Avoids pointing `aria-describedby` at a message element that is never rendered.
194
+ const hasMessage = computed(() => !!(formFieldProps.value.messageText || slots.message));
195
+
196
+ // Boxes blur on every hop between boxes; only focus leaving the field is a real blur.
197
+ function onFieldFocusOut(event: FocusEvent) {
198
+ const root = event.currentTarget as HTMLElement | null;
199
+
200
+ if (root?.contains(event.relatedTarget as Node | null)) {
201
+ return;
202
+ }
203
+
204
+ onBlur(event);
205
+ }
206
+
207
+ function onPinComplete() {
208
+ onComplete(new Event('blur'));
209
+ emit('complete', chars.value.join(''));
210
+ }
211
+ </script>
@@ -0,0 +1,6 @@
1
+ import PinInputField from './PinInputField.vue';
2
+
3
+ export * from './PinInputField.consts';
4
+ export * from './PinInputField.types';
5
+
6
+ export default PinInputField;
@@ -0,0 +1,29 @@
1
+ import { computed, MaybeRefOrGetter, Ref } from 'vue';
2
+ import { useFormFieldWithinForm } from '../../../composables/useFormFieldWithinForm';
3
+
4
+ /**
5
+ * Pin counterpart of `useTextFieldWithinForm`. A pin has no single blur — its boxes blur on every
6
+ * hop between boxes — so the field is marked touched when the pin is completed or when focus leaves
7
+ * the field entirely.
8
+ */
9
+ export function usePinInputFieldWithinForm(
10
+ name: MaybeRefOrGetter<string | undefined>,
11
+ modelValue: Ref<string | undefined>,
12
+ ) {
13
+ const { value, errors, field } = useFormFieldWithinForm(name, modelValue);
14
+
15
+ const markTouched = (event: Event) => {
16
+ field?.handleBlur(event, true);
17
+ };
18
+
19
+ // `meta.touched` covers both callers below and `handleSubmit`, so a half-typed pin is never
20
+ // marked invalid but a submitted incomplete one still shows its error.
21
+ const visibleErrors = computed(() => (field?.meta.touched ? errors.value : []));
22
+
23
+ return {
24
+ value,
25
+ errors: visibleErrors,
26
+ onComplete: markTouched,
27
+ onBlur: markTouched,
28
+ };
29
+ }