@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.
- package/dist/design-system.css +1 -1
- package/dist/design-system.js +17470 -13135
- package/dist/design-system.js.map +1 -1
- package/dist/lib/js/components/Cards/Card/Card.consts.d.ts +14 -0
- package/dist/lib/js/components/Cards/Card/Card.vue.d.ts +10 -5
- package/dist/lib/js/components/Cards/CardExpandable/CardExpandable.vue.d.ts +33 -15
- package/dist/lib/js/components/Form/PinInputField/PinInputField.consts.d.ts +14 -0
- package/dist/lib/js/components/Form/PinInputField/PinInputField.types.d.ts +12 -0
- package/dist/lib/js/components/Form/PinInputField/PinInputField.utils.d.ts +2 -0
- package/dist/lib/js/components/Form/PinInputField/PinInputField.vue.d.ts +91 -0
- package/dist/lib/js/components/Form/PinInputField/index.d.ts +5 -0
- package/dist/lib/js/components/Form/PinInputField/usePinInputFieldWithinForm.d.ts +8 -0
- package/dist/lib/js/components/Form/SelectField/SelectField.types.d.ts +26 -0
- package/dist/lib/js/components/Form/SelectField/SelectField.utils.d.ts +7 -0
- package/dist/lib/js/components/Form/SelectField/SelectField.vue.d.ts +107 -0
- package/dist/lib/js/components/Form/SelectField/SelectFieldOption.vue.d.ts +19 -0
- package/dist/lib/js/components/Form/SelectField/index.d.ts +4 -0
- package/dist/lib/js/components/Form/SelectField/useSelectFieldWithinForm.d.ts +8 -0
- package/dist/lib/js/components/Form/TextAreaField/TextAreaField.types.d.ts +9 -0
- package/dist/lib/js/components/Form/TextAreaField/TextAreaField.vue.d.ts +76 -0
- package/dist/lib/js/components/Form/TextAreaField/TextAreaFieldAutosized.vue.d.ts +6 -0
- package/dist/lib/js/components/Form/TextAreaField/index.d.ts +4 -0
- package/dist/lib/js/components/LabelValue/LabelValueItem/LabelValueItem.vue.d.ts +12 -1
- package/dist/lib/js/components/Pagination/Pagination.vue.d.ts +2 -0
- package/dist/lib/js/components/SelectList/SelectListItem/SelectListItem.vue.d.ts +2 -0
- package/dist/lib/js/components/SelectList/SelectListItemToggle/SelectListItemToggle.vue.d.ts +2 -0
- package/dist/lib/js/components/SurveyQuestions/SurveyQuestionOpenEnded/SurveyQuestionOpenEnded.vue.d.ts +33 -15
- package/dist/lib/js/components/SurveyQuestions/SurveyQuestionScale/SurveyQuestionScale.vue.d.ts +33 -15
- package/dist/lib/js/components/Toast/Toast.consts.d.ts +11 -9
- package/dist/lib/js/components/Toast/Toast.vue.d.ts +51 -415
- package/dist/lib/js/composables/useFormFieldWithinForm.d.ts +3 -3
- package/dist/lib/js/composables/useTextFieldWithinForm.d.ts +8 -0
- package/dist/lib/js/index.d.ts +6 -0
- package/lib/js/components/Cards/Card/Card.consts.ts +18 -0
- package/lib/js/components/Cards/Card/Card.spec.ts +159 -33
- package/lib/js/components/Cards/Card/Card.stories.ts +21 -2
- package/lib/js/components/Cards/Card/Card.vue +90 -23
- package/lib/js/components/Dropdown/Dropdown.vue +6 -10
- package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.spec.ts +15 -0
- package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.vue +5 -2
- package/lib/js/components/Form/Form.stories.ts +37 -2
- package/lib/js/components/Form/InputField/InputField.spec.ts +11 -1
- package/lib/js/components/Form/InputField/InputField.vue +7 -4
- package/lib/js/components/Form/PinInputField/PinInputField.consts.ts +18 -0
- package/lib/js/components/Form/PinInputField/PinInputField.spec.ts +394 -0
- package/lib/js/components/Form/PinInputField/PinInputField.stories.ts +124 -0
- package/lib/js/components/Form/PinInputField/PinInputField.types.ts +42 -0
- package/lib/js/components/Form/PinInputField/PinInputField.utils.ts +5 -0
- package/lib/js/components/Form/PinInputField/PinInputField.vue +211 -0
- package/lib/js/components/Form/PinInputField/index.ts +6 -0
- package/lib/js/components/Form/PinInputField/usePinInputFieldWithinForm.ts +29 -0
- package/lib/js/components/Form/SelectField/SelectField.spec.ts +495 -0
- package/lib/js/components/Form/SelectField/SelectField.stories.ts +282 -0
- package/lib/js/components/Form/SelectField/SelectField.types.ts +65 -0
- package/lib/js/components/Form/SelectField/SelectField.utils.ts +83 -0
- package/lib/js/components/Form/SelectField/SelectField.vue +342 -0
- package/lib/js/components/Form/SelectField/SelectFieldOption.vue +51 -0
- package/lib/js/components/Form/SelectField/index.ts +5 -0
- package/lib/js/components/Form/SelectField/useSelectFieldWithinForm.ts +27 -0
- package/lib/js/components/Form/TextAreaField/TextAreaField.spec.ts +341 -0
- package/lib/js/components/Form/TextAreaField/TextAreaField.stories.ts +99 -0
- package/lib/js/components/Form/TextAreaField/TextAreaField.types.ts +23 -0
- package/lib/js/components/Form/TextAreaField/TextAreaField.vue +190 -0
- package/lib/js/components/Form/TextAreaField/TextAreaFieldAutosized.vue +19 -0
- package/lib/js/components/Form/TextAreaField/index.ts +5 -0
- package/lib/js/components/LabelValue/LabelValueItem/LabelValueItem.spec.ts +26 -0
- package/lib/js/components/LabelValue/LabelValueItem/LabelValueItem.stories.ts +16 -3
- package/lib/js/components/LabelValue/LabelValueItem/LabelValueItem.vue +5 -0
- package/lib/js/components/LoadingBar/LoadingBar.spec.ts +84 -0
- package/lib/js/components/LoadingBar/LoadingBar.vue +30 -19
- package/lib/js/components/SelectList/SelectListItem/SelectListItem.spec.ts +77 -0
- package/lib/js/components/SelectList/SelectListItem/SelectListItem.vue +4 -1
- package/lib/js/components/Toast/Toast.consts.ts +4 -2
- package/lib/js/components/Toast/Toast.spec.ts +177 -0
- package/lib/js/components/Toast/Toast.stories.ts +65 -31
- package/lib/js/components/Toast/Toast.vue +176 -195
- package/lib/js/composables/useFormFieldWithinForm.ts +4 -4
- package/lib/js/{components/Form/InputField/useInputFieldWithinForm.ts → composables/useTextFieldWithinForm.ts} +7 -2
- package/lib/js/index.ts +6 -0
- package/lib/js/styles/Shadows/Shadows.stories.scss +16 -0
- package/lib/js/styles/Shadows/Shadows.stories.ts +4 -0
- package/lib/styles/mixins/_dropdown-surface.scss +24 -0
- package/lib/styles/settings/_shadows.scss +18 -0
- package/package.json +1 -1
- 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,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,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
|
+
}
|