srcdev-nuxt-components 9.6.8 → 9.6.9
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/.claude/skills/component-compliance-checklist.md +12 -1
- package/.claude/skills/component-inline-action-button.md +1 -1
- package/.claude/skills/component-local-style-override.md +1 -1
- package/.claude/skills/component-naming.md +11 -11
- package/.claude/skills/components/container-glow.md +2 -2
- package/.claude/skills/components/form-field.md +2 -2
- package/.claude/skills/components/form-wrapper.md +1 -1
- package/.claude/skills/components/glowing-border.md +1 -1
- package/.claude/skills/components/input-description.md +7 -7
- package/.claude/skills/components/input-error.md +3 -3
- package/.claude/skills/components/input-label.md +7 -8
- package/.claude/skills/components/input-number.md +17 -17
- package/.claude/skills/components/input-select.md +153 -0
- package/.claude/skills/components/input-text.md +220 -0
- package/.claude/skills/components/input-textarea.md +147 -0
- package/.claude/skills/components/select-menu.md +4 -4
- package/.claude/skills/components/toggle-switch.md +143 -0
- package/.claude/skills/composable-zod-validation.md +1 -1
- package/.claude/skills/icon-sets.md +1 -1
- package/.claude/skills/icon-sizing.md +1 -1
- package/.claude/skills/index.md +5 -5
- package/.claude/skills/storybook-add-story.md +1 -1
- package/.claude/skills/theming-component-token-pattern.md +8 -8
- package/.claude/skills/theming-form-geometry-tokens.md +1 -1
- package/.vscode/srcdev-component-input-select.code-snippets +20 -14
- package/.vscode/srcdev-component-input-text.code-snippets +46 -40
- package/.vscode/srcdev-component-input-textarea.code-snippets +19 -14
- package/.vscode/srcdev-component-toggle-switch.code-snippets +19 -29
- package/app/assets/styles/setup/04.elements/forms/00.element-defaults.css +2 -2
- package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +1 -1
- package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +2 -2
- package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +4 -4
- package/app/components/02.molecules/progress-tracker/stories/ProgressTracker.stories.ts +4 -4
- package/app/components/05.forms/form-errors/CONSUMER-STYLING.md +1 -1
- package/app/components/05.forms/form-field/CONSUMER-STYLING.md +2 -2
- package/app/components/05.forms/input-button/stories/InputButton.stories.ts +3 -3
- package/app/components/05.forms/input-description/CONSUMER-STYLING.md +6 -6
- package/app/components/05.forms/input-label/CONSUMER-STYLING.md +5 -5
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +9 -5
- package/app/components/05.forms/input-number/InputNumber.vue +2 -2
- package/app/components/05.forms/input-select/CONSUMER-STYLING.md +80 -10
- package/app/components/05.forms/input-select/InputSelect.vue +198 -0
- package/app/components/05.forms/input-select/InputSelectField.vue +130 -0
- package/app/components/05.forms/input-select/stories/InputSelect.stories.ts +184 -0
- package/app/components/05.forms/input-select/stories/InputSelectField.stories.ts +187 -0
- package/app/components/05.forms/input-select/tests/{InputSelectCore.spec.ts → InputSelect.spec.ts} +75 -12
- package/app/components/05.forms/input-select/tests/InputSelectField.spec.ts +121 -0
- package/app/components/05.forms/input-text/CONSUMER-STYLING.md +85 -15
- package/app/components/05.forms/input-text/{InputPasswordWithLabel.vue → InputPasswordField.vue} +16 -19
- package/app/components/05.forms/input-text/{InputTextCore.vue → InputText.vue} +42 -78
- package/app/components/05.forms/input-text/{InputTextAsNumberWithLabel.vue → InputTextAsNumberField.vue} +66 -62
- package/app/components/05.forms/input-text/InputTextField.vue +168 -0
- package/app/components/05.forms/input-text/stories/InputPasswordField.stories.ts +163 -0
- package/app/components/05.forms/input-text/stories/InputText.stories.ts +194 -0
- package/app/components/05.forms/input-text/stories/InputTextAsNumberField.stories.ts +182 -0
- package/app/components/05.forms/input-text/stories/InputTextField.stories.ts +206 -0
- package/app/components/05.forms/input-text/tests/{InputPasswordWithLabel.spec.ts → InputPasswordField.spec.ts} +42 -3
- package/app/components/05.forms/input-text/tests/InputText.spec.ts +166 -0
- package/app/components/05.forms/input-text/tests/InputTextAsNumberField.spec.ts +135 -0
- package/app/components/05.forms/input-text/tests/InputTextField.spec.ts +130 -0
- package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +94 -12
- package/app/components/05.forms/input-textarea/InputTextarea.vue +165 -0
- package/app/components/05.forms/input-textarea/InputTextareaField.vue +159 -0
- package/app/components/05.forms/input-textarea/stories/InputTextarea.stories.ts +198 -0
- package/app/components/05.forms/input-textarea/stories/InputTextareaField.stories.ts +161 -0
- package/app/components/05.forms/input-textarea/tests/InputTextarea.spec.ts +149 -0
- package/app/components/05.forms/input-textarea/tests/InputTextareaField.spec.ts +122 -0
- package/app/components/05.forms/patterns/stories/ContactForm.stories.ts +13 -13
- package/app/components/05.forms/patterns/stories/LoginForm.stories.ts +6 -6
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +30 -32
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +71 -50
- package/app/components/05.forms/toggle-switch/{ToggleSwitchCore.vue → ToggleSwitch.vue} +58 -73
- package/app/components/05.forms/toggle-switch/{ToggleSwitchWithLabel.vue → ToggleSwitchField.vue} +65 -19
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitch.stories.ts +148 -0
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchField.stories.ts +176 -0
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitch.spec.ts +145 -0
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchField.spec.ts +155 -0
- package/package.json +1 -1
- package/.claude/skills/components/input-select-core.md +0 -151
- package/.claude/skills/components/input-text-core.md +0 -204
- package/.claude/skills/components/input-textarea-core.md +0 -154
- package/.claude/skills/components/toggle-switch-core.md +0 -169
- package/app/components/05.forms/input-select/InputSelectCore.vue +0 -188
- package/app/components/05.forms/input-select/InputSelectWithLabel.vue +0 -118
- package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +0 -69
- package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +0 -115
- package/app/components/05.forms/input-select/tests/InputSelectWithLabel.spec.ts +0 -87
- package/app/components/05.forms/input-text/InputTextWithLabel.vue +0 -154
- package/app/components/05.forms/input-text/stories/InputPasswordWithLabel.stories.ts +0 -368
- package/app/components/05.forms/input-text/stories/InputTextAsNumberWithLabel.stories.ts +0 -480
- package/app/components/05.forms/input-text/stories/InputTextCore.stories.ts +0 -267
- package/app/components/05.forms/input-text/stories/InputTextWithLabel.stories.ts +0 -319
- package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +0 -75
- package/app/components/05.forms/input-text/tests/InputTextCore.spec.ts +0 -60
- package/app/components/05.forms/input-text/tests/InputTextWithLabel.spec.ts +0 -71
- package/app/components/05.forms/input-textarea/InputTextareaCore.vue +0 -159
- package/app/components/05.forms/input-textarea/InputTextareaWithLabel.vue +0 -136
- package/app/components/05.forms/input-textarea/stories/InputTextareaCore.stories.ts +0 -247
- package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +0 -458
- package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +0 -119
- package/app/components/05.forms/input-textarea/tests/InputTextareaWithLabel.spec.ts +0 -93
- package/app/components/05.forms/toggle-switch/ToggleSwitchWithLabelInline.vue +0 -81
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +0 -251
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +0 -251
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +0 -283
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +0 -101
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabel.spec.ts +0 -133
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabelInline.spec.ts +0 -66
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
class="input-select-wrapper"
|
|
4
|
-
:data-theme="theme"
|
|
5
|
-
:data-invalid="fieldHasError ? '' : null"
|
|
6
|
-
:class="[inputVariant, { dirty: isDirty }, { active: isActive }, { error: fieldHasError }]"
|
|
7
|
-
>
|
|
8
|
-
<select
|
|
9
|
-
:id
|
|
10
|
-
v-model="modelValue"
|
|
11
|
-
:aria-invalid="fieldHasError ? 'true' : undefined"
|
|
12
|
-
:class="['input-select-core', elementClasses]"
|
|
13
|
-
:name
|
|
14
|
-
:title
|
|
15
|
-
:required
|
|
16
|
-
:aria-describedby="ariaDescribedby"
|
|
17
|
-
@focusin="isActive = true"
|
|
18
|
-
@focusout="isActive = false"
|
|
19
|
-
@change="isDirty = true"
|
|
20
|
-
>
|
|
21
|
-
<option v-if="placeholder" value="" disabled :selected="!modelValue" class="input-select-core-option placeholder">
|
|
22
|
-
{{ placeholder }}
|
|
23
|
-
</option>
|
|
24
|
-
<option
|
|
25
|
-
v-for="item in fieldData.data"
|
|
26
|
-
:key="item.id"
|
|
27
|
-
:value="item.value"
|
|
28
|
-
:selected="String(item.value) === String(modelValue)"
|
|
29
|
-
class="input-select-core-option"
|
|
30
|
-
>
|
|
31
|
-
<Icon
|
|
32
|
-
v-if="item.icon"
|
|
33
|
-
:name="item.icon"
|
|
34
|
-
class="input-select-core-option-decorator-icon"
|
|
35
|
-
aria-hidden="true"
|
|
36
|
-
focusable="false"
|
|
37
|
-
/>
|
|
38
|
-
{{ item.label }}
|
|
39
|
-
</option>
|
|
40
|
-
</select>
|
|
41
|
-
</div>
|
|
42
|
-
</template>
|
|
43
|
-
|
|
44
|
-
<script setup lang="ts">
|
|
45
|
-
import type { IFormMultipleOptions, FormUiTheme, InputUiVariant } from "~/types/forms/types.forms";
|
|
46
|
-
|
|
47
|
-
interface Props {
|
|
48
|
-
id: string;
|
|
49
|
-
name: string;
|
|
50
|
-
placeholder?: string;
|
|
51
|
-
title?: string;
|
|
52
|
-
required?: boolean;
|
|
53
|
-
fieldHasError?: boolean;
|
|
54
|
-
styleClassPassthrough?: string | string[];
|
|
55
|
-
theme?: FormUiTheme;
|
|
56
|
-
inputVariant?: InputUiVariant;
|
|
57
|
-
ariaDescribedby?: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
61
|
-
placeholder: "",
|
|
62
|
-
title: "Please select an option",
|
|
63
|
-
required: false,
|
|
64
|
-
fieldHasError: false,
|
|
65
|
-
styleClassPassthrough: () => [],
|
|
66
|
-
theme: "default",
|
|
67
|
-
inputVariant: "normal",
|
|
68
|
-
ariaDescribedby: "",
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const modelValue = defineModel<string | number | readonly number[]>({ required: true });
|
|
72
|
-
const isDirty = defineModel<boolean>("isDirty");
|
|
73
|
-
const isActive = defineModel<boolean>("isActive");
|
|
74
|
-
const fieldData = defineModel<IFormMultipleOptions>("fieldData", { required: true });
|
|
75
|
-
|
|
76
|
-
const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
77
|
-
</script>
|
|
78
|
-
|
|
79
|
-
<style lang="css">
|
|
80
|
-
@layer components {
|
|
81
|
-
.input-select-wrapper {
|
|
82
|
-
/* Public --input-select-* tokens, inline-fallback to the shared --theme-* tokens (see
|
|
83
|
-
theming-component-token-pattern.md) — overriding one here doesn't touch every other themed
|
|
84
|
-
input/control that also reads --theme-input-surface/--theme-border. --_border is the one
|
|
85
|
-
genuinely reused value (border, underlined border-bottom, open-picker border); the rest of
|
|
86
|
-
this component's tokens are used at exactly one point each, so they're inlined directly
|
|
87
|
-
rather than wrapped in a private var with no composition or state-swap behind it (see
|
|
88
|
-
pitfall #20 in CLAUDE.md). */
|
|
89
|
-
--_border: var(--input-select-border, var(--theme-border));
|
|
90
|
-
/* Split so hover (mouse) and :focus-visible (keyboard/assistive) can diverge — both default to
|
|
91
|
-
the same --theme-border-focus today, identical appearance to before this token existed, but
|
|
92
|
-
each now has its own override point. */
|
|
93
|
-
--_border-focus: var(--input-select-border-focus, var(--theme-border-focus));
|
|
94
|
-
|
|
95
|
-
background-color: var(--input-select-surface, var(--theme-input-surface));
|
|
96
|
-
overflow: hidden;
|
|
97
|
-
|
|
98
|
-
z-index: 2;
|
|
99
|
-
position: relative;
|
|
100
|
-
transition: all var(--theme-form-transition-duration) ease-in-out;
|
|
101
|
-
|
|
102
|
-
&.normal {
|
|
103
|
-
border: var(--form-element-border-width) solid var(--_border);
|
|
104
|
-
border-radius: var(--form-input-border-radius);
|
|
105
|
-
outline: var(--form-element-outline-width) solid transparent;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
&.underlined {
|
|
109
|
-
border-bottom: var(--form-element-border-bottom-width-underlined) solid var(--_border);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
&:has(:hover) {
|
|
113
|
-
outline: var(--form-element-outline-width-focus) solid
|
|
114
|
-
var(--input-select-border-hover, var(--theme-border-focus));
|
|
115
|
-
outline-offset: var(--form-element-outline-offset-focus);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
&:has(select:focus-visible) {
|
|
119
|
-
outline: var(--form-element-outline-width-focus) solid var(--_border-focus);
|
|
120
|
-
outline-offset: var(--form-element-outline-offset-focus);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.input-select-core {
|
|
124
|
-
appearance: none;
|
|
125
|
-
background-color: transparent;
|
|
126
|
-
display: flex;
|
|
127
|
-
align-items: center;
|
|
128
|
-
|
|
129
|
-
/* Start modern Select CSS */
|
|
130
|
-
&,
|
|
131
|
-
&::picker(select) {
|
|
132
|
-
appearance: base-select;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
&::picker(select) {
|
|
136
|
-
transition:
|
|
137
|
-
display allow-discrete var(--theme-form-transition-duration),
|
|
138
|
-
opacity var(--theme-form-transition-duration),
|
|
139
|
-
overlay var(--theme-form-transition-duration) allow-discrete;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
&:not(:open)::picker(select) {
|
|
143
|
-
opacity: 0;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
&:open::picker(select) {
|
|
147
|
-
opacity: 1;
|
|
148
|
-
border: var(--form-element-border-width) solid var(--_border);
|
|
149
|
-
outline: var(--form-element-outline-width) solid var(--_border-focus);
|
|
150
|
-
|
|
151
|
-
@starting-style {
|
|
152
|
-
opacity: 0;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
border: none;
|
|
157
|
-
outline: none;
|
|
158
|
-
border-radius: 0;
|
|
159
|
-
width: 100%;
|
|
160
|
-
|
|
161
|
-
/* END modern Select CSS */
|
|
162
|
-
|
|
163
|
-
font-family: var(--font-family);
|
|
164
|
-
font-size: var(--input-font-size);
|
|
165
|
-
/* line-height: var(--input-element-line-height); */
|
|
166
|
-
padding-block: var(--input-padding-block);
|
|
167
|
-
padding-inline: var(--input-padding-inline);
|
|
168
|
-
min-height: var(--input-min-height);
|
|
169
|
-
|
|
170
|
-
.input-select-core-option {
|
|
171
|
-
min-height: var(--input-select-option-min-height);
|
|
172
|
-
gap: 1.5rem;
|
|
173
|
-
transition: all var(--theme-form-transition-duration) ease-in-out;
|
|
174
|
-
|
|
175
|
-
&:hover {
|
|
176
|
-
background-color: var(--input-select-surface-hover, var(--theme-input-surface-hover));
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.input-select-core-option-decorator-icon {
|
|
180
|
-
display: inline-block;
|
|
181
|
-
width: 1.5rem;
|
|
182
|
-
height: 1.5rem;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
</style>
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<div
|
|
4
|
-
class="input-select-with-label"
|
|
5
|
-
:class="[inputVariant, { dirty: isDirty }, { active: isActive }, { error: fieldHasError }]"
|
|
6
|
-
:data-testid
|
|
7
|
-
:data-theme="theme"
|
|
8
|
-
:data-invalid="fieldHasError ? '' : null"
|
|
9
|
-
>
|
|
10
|
-
<InputLabel
|
|
11
|
-
:id
|
|
12
|
-
:required
|
|
13
|
-
:input-variant
|
|
14
|
-
:field-has-error
|
|
15
|
-
:style-class-passthrough="['input-select-label']"
|
|
16
|
-
>
|
|
17
|
-
<template #textLabel>{{ label }}</template>
|
|
18
|
-
</InputLabel>
|
|
19
|
-
|
|
20
|
-
<InputDescription
|
|
21
|
-
v-if="inputVariant !== 'outlined'"
|
|
22
|
-
:description-id
|
|
23
|
-
:input-variant
|
|
24
|
-
:field-has-error="fieldHasError"
|
|
25
|
-
:style-class-passthrough="['input-text-description']"
|
|
26
|
-
>
|
|
27
|
-
<template v-if="slots.descriptionHtml" #descriptionHtml>
|
|
28
|
-
<slot name="descriptionHtml"></slot>
|
|
29
|
-
</template>
|
|
30
|
-
<template v-if="slots.descriptionText" #descriptionText>
|
|
31
|
-
<slot name="descriptionText"></slot>
|
|
32
|
-
</template>
|
|
33
|
-
</InputDescription>
|
|
34
|
-
|
|
35
|
-
<InputSelectCore
|
|
36
|
-
:id
|
|
37
|
-
v-model="modelValue"
|
|
38
|
-
v-model:field-data="fieldData"
|
|
39
|
-
v-model:is-dirty="isDirty"
|
|
40
|
-
v-model:is-active="isActive"
|
|
41
|
-
:name
|
|
42
|
-
:placeholder
|
|
43
|
-
:field-has-error
|
|
44
|
-
:required
|
|
45
|
-
:style-class-passthrough
|
|
46
|
-
:theme="FormUiTheme"
|
|
47
|
-
:aria-describedby="ariaDescribedby()"
|
|
48
|
-
:input-variant
|
|
49
|
-
/>
|
|
50
|
-
|
|
51
|
-
<InputError
|
|
52
|
-
:id="errorId"
|
|
53
|
-
:error-message="errorMessage"
|
|
54
|
-
:show-error="fieldHasError"
|
|
55
|
-
:is-detached="false"
|
|
56
|
-
:input-variant
|
|
57
|
-
/>
|
|
58
|
-
</div>
|
|
59
|
-
<InputDescription
|
|
60
|
-
v-if="inputVariant === 'outlined'"
|
|
61
|
-
:description-id
|
|
62
|
-
:input-variant
|
|
63
|
-
:field-has-error="fieldHasError"
|
|
64
|
-
:style-class-passthrough="['input-text-description']"
|
|
65
|
-
>
|
|
66
|
-
<template v-if="slots.descriptionHtml" #descriptionHtml>
|
|
67
|
-
<slot name="descriptionHtml"></slot>
|
|
68
|
-
</template>
|
|
69
|
-
<template v-if="slots.descriptionText" #descriptionText>
|
|
70
|
-
<slot name="descriptionText"></slot>
|
|
71
|
-
</template>
|
|
72
|
-
</InputDescription>
|
|
73
|
-
</div>
|
|
74
|
-
</template>
|
|
75
|
-
|
|
76
|
-
<script setup lang="ts">
|
|
77
|
-
import type { FormUiTheme, InputUiVariant, IFormMultipleOptions } from "~/types/forms/types.forms";
|
|
78
|
-
|
|
79
|
-
interface Props {
|
|
80
|
-
dataTestid?: string;
|
|
81
|
-
name: string;
|
|
82
|
-
label: string;
|
|
83
|
-
placeholder?: string;
|
|
84
|
-
errorMessage: object | string;
|
|
85
|
-
required?: boolean;
|
|
86
|
-
fieldHasError?: boolean;
|
|
87
|
-
styleClassPassthrough?: string | string[];
|
|
88
|
-
theme?: FormUiTheme;
|
|
89
|
-
inputVariant?: InputUiVariant;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
93
|
-
dataTestid: "input-select-with-label",
|
|
94
|
-
placeholder: "",
|
|
95
|
-
required: false,
|
|
96
|
-
fieldHasError: false,
|
|
97
|
-
styleClassPassthrough: () => [],
|
|
98
|
-
theme: "default",
|
|
99
|
-
inputVariant: "normal",
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const slots = useSlots();
|
|
103
|
-
|
|
104
|
-
const FormUiTheme = computed(() => {
|
|
105
|
-
return props.fieldHasError ? "error" : props.theme;
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const { id, errorId, descriptionId, ariaDescribedby } = useAriaDescribedById(
|
|
109
|
-
props.name,
|
|
110
|
-
toRef(props, "fieldHasError"),
|
|
111
|
-
slots
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
const modelValue = defineModel<string | number | readonly number[]>({ required: true });
|
|
115
|
-
const isDirty = defineModel<boolean>("isDirty");
|
|
116
|
-
const isActive = defineModel<boolean>("isActive");
|
|
117
|
-
const fieldData = defineModel<IFormMultipleOptions>("fieldData", { required: true });
|
|
118
|
-
</script>
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
-
import { ref } from "vue";
|
|
3
|
-
import StorybookComponent from "../InputSelectCore.vue";
|
|
4
|
-
import type { IFormMultipleOptions, InputUiVariant } from "~/types/forms/types.forms.d";
|
|
5
|
-
|
|
6
|
-
interface InputSelectCoreStoryArgs {
|
|
7
|
-
id: string;
|
|
8
|
-
name: string;
|
|
9
|
-
placeholder: string;
|
|
10
|
-
required: boolean;
|
|
11
|
-
fieldHasError: boolean;
|
|
12
|
-
inputVariant: InputUiVariant;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default {
|
|
16
|
-
title: "Components/Forms/Input Select/InputSelectCore",
|
|
17
|
-
component: StorybookComponent,
|
|
18
|
-
argTypes: {
|
|
19
|
-
id: { control: "text", table: { category: "Basic" } },
|
|
20
|
-
name: { control: "text", table: { category: "Basic" } },
|
|
21
|
-
placeholder: { control: "text", table: { category: "Basic" } },
|
|
22
|
-
required: { control: "boolean", table: { category: "Basic" } },
|
|
23
|
-
fieldHasError: { control: "boolean", table: { category: "States" } },
|
|
24
|
-
inputVariant: {
|
|
25
|
-
control: { type: "select" },
|
|
26
|
-
options: ["normal", "outlined", "underlined"],
|
|
27
|
-
table: { category: "Styling" },
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
args: {
|
|
31
|
-
id: "select-default",
|
|
32
|
-
name: "selectDefault",
|
|
33
|
-
placeholder: "Choose a service",
|
|
34
|
-
required: false,
|
|
35
|
-
fieldHasError: false,
|
|
36
|
-
inputVariant: "normal",
|
|
37
|
-
},
|
|
38
|
-
} as Meta<InputSelectCoreStoryArgs>;
|
|
39
|
-
|
|
40
|
-
const Template: StoryFn<InputSelectCoreStoryArgs> = (args) => ({
|
|
41
|
-
components: { StorybookComponent },
|
|
42
|
-
setup() {
|
|
43
|
-
const modelValue = ref<string | number>("");
|
|
44
|
-
const fieldData = ref<IFormMultipleOptions>({
|
|
45
|
-
data: [
|
|
46
|
-
{ id: "1", name: "balayage", value: "balayage", label: "Balayage" },
|
|
47
|
-
{ id: "2", name: "highlights", value: "highlights", label: "Highlights" },
|
|
48
|
-
{ id: "3", name: "full-colour", value: "full-colour", label: "Full Colour" },
|
|
49
|
-
],
|
|
50
|
-
total: 3,
|
|
51
|
-
skip: 0,
|
|
52
|
-
limit: 10,
|
|
53
|
-
});
|
|
54
|
-
return { args, modelValue, fieldData };
|
|
55
|
-
},
|
|
56
|
-
template: `
|
|
57
|
-
<div style="margin: 36px; max-width: 320px;">
|
|
58
|
-
<StorybookComponent v-bind="args" v-model="modelValue" v-model:field-data="fieldData" />
|
|
59
|
-
</div>
|
|
60
|
-
`,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
export const Default = Template.bind({});
|
|
64
|
-
|
|
65
|
-
export const ErrorState = Template.bind({});
|
|
66
|
-
ErrorState.args = { fieldHasError: true };
|
|
67
|
-
|
|
68
|
-
export const Underlined = Template.bind({});
|
|
69
|
-
Underlined.args = { inputVariant: "underlined" };
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
-
import { ref, computed } from "vue";
|
|
3
|
-
import StorybookComponent from "../InputSelectWithLabel.vue";
|
|
4
|
-
import type { FormUiTheme, InputUiVariant, IFormMultipleOptions } from "~/types/forms/types.forms.d";
|
|
5
|
-
|
|
6
|
-
interface InputSelectWithLabelStoryArgs {
|
|
7
|
-
modelValue: string | number;
|
|
8
|
-
name: string;
|
|
9
|
-
label: string;
|
|
10
|
-
placeholder: string;
|
|
11
|
-
errorMessage: string;
|
|
12
|
-
fieldHasError: boolean;
|
|
13
|
-
required: boolean;
|
|
14
|
-
theme: FormUiTheme;
|
|
15
|
-
inputVariant: InputUiVariant;
|
|
16
|
-
styleClassPassthrough: string[];
|
|
17
|
-
useDescriptionSlot: boolean;
|
|
18
|
-
descriptionContent: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default {
|
|
22
|
-
title: "Components/Forms/Input Select/InputSelectWithLabel",
|
|
23
|
-
component: StorybookComponent,
|
|
24
|
-
argTypes: {
|
|
25
|
-
modelValue: { control: "text", table: { category: "Model" } },
|
|
26
|
-
name: { control: "text", table: { category: "Basic" } },
|
|
27
|
-
label: { control: "text", table: { category: "Basic" } },
|
|
28
|
-
placeholder: { control: "text", table: { category: "Basic" } },
|
|
29
|
-
errorMessage: { control: "text", table: { category: "Validation" } },
|
|
30
|
-
fieldHasError: { control: "boolean", table: { category: "Validation" } },
|
|
31
|
-
required: { control: "boolean", table: { category: "Validation" } },
|
|
32
|
-
theme: {
|
|
33
|
-
control: { type: "select" },
|
|
34
|
-
options: ["default", "success", "error", "warning"],
|
|
35
|
-
table: { category: "Styling" },
|
|
36
|
-
},
|
|
37
|
-
inputVariant: {
|
|
38
|
-
control: { type: "select" },
|
|
39
|
-
options: ["normal", "outlined", "underlined"],
|
|
40
|
-
table: { category: "Styling" },
|
|
41
|
-
},
|
|
42
|
-
styleClassPassthrough: { control: "object", table: { category: "Styling" } },
|
|
43
|
-
useDescriptionSlot: { control: "boolean", table: { category: "Slots" } },
|
|
44
|
-
descriptionContent: { control: "text", table: { category: "Slots" } },
|
|
45
|
-
},
|
|
46
|
-
args: {
|
|
47
|
-
modelValue: "",
|
|
48
|
-
name: "colour",
|
|
49
|
-
label: "Favourite colour",
|
|
50
|
-
placeholder: "Choose a colour",
|
|
51
|
-
errorMessage: "",
|
|
52
|
-
fieldHasError: false,
|
|
53
|
-
required: false,
|
|
54
|
-
theme: "default",
|
|
55
|
-
inputVariant: "normal",
|
|
56
|
-
styleClassPassthrough: [],
|
|
57
|
-
useDescriptionSlot: false,
|
|
58
|
-
descriptionContent: "Pick the colour you like best",
|
|
59
|
-
},
|
|
60
|
-
} as Meta<InputSelectWithLabelStoryArgs>;
|
|
61
|
-
|
|
62
|
-
// `args` is Storybook's own reactive object — bind to it directly (`args.x`) rather than
|
|
63
|
-
// destructuring it into local variables/refs, which would snapshot the values once at setup()
|
|
64
|
-
// and stop reflecting later Controls-panel changes. `componentArgs` strips the non-prop
|
|
65
|
-
// slot-toggle args so they don't leak onto the component as unknown attributes.
|
|
66
|
-
const Template: StoryFn<InputSelectWithLabelStoryArgs> = (args) => ({
|
|
67
|
-
components: { StorybookComponent },
|
|
68
|
-
setup() {
|
|
69
|
-
const fieldData = ref<IFormMultipleOptions>({
|
|
70
|
-
data: [
|
|
71
|
-
{ id: "1", name: "red", value: "red", label: "Red" },
|
|
72
|
-
{ id: "2", name: "blue", value: "blue", label: "Blue" },
|
|
73
|
-
{ id: "3", name: "green", value: "green", label: "Green" },
|
|
74
|
-
],
|
|
75
|
-
total: 3,
|
|
76
|
-
skip: 0,
|
|
77
|
-
limit: 10,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const componentArgs = computed(() => {
|
|
81
|
-
const { useDescriptionSlot, descriptionContent, ...rest } = args;
|
|
82
|
-
return rest;
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
return { args, componentArgs, fieldData };
|
|
86
|
-
},
|
|
87
|
-
template: `
|
|
88
|
-
<div style="margin: 36px; max-width: 320px;">
|
|
89
|
-
<StorybookComponent v-model="args.modelValue" v-model:field-data="fieldData" v-bind="componentArgs">
|
|
90
|
-
<template v-if="args.useDescriptionSlot" #descriptionText>{{ args.descriptionContent }}</template>
|
|
91
|
-
</StorybookComponent>
|
|
92
|
-
</div>
|
|
93
|
-
`,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
export const Default = Template.bind({});
|
|
97
|
-
|
|
98
|
-
export const WithError = Template.bind({});
|
|
99
|
-
WithError.args = {
|
|
100
|
-
fieldHasError: true,
|
|
101
|
-
errorMessage: "Please choose a colour",
|
|
102
|
-
theme: "error",
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export const Required = Template.bind({});
|
|
106
|
-
Required.args = { required: true };
|
|
107
|
-
|
|
108
|
-
export const WithDescription = Template.bind({});
|
|
109
|
-
WithDescription.args = { useDescriptionSlot: true };
|
|
110
|
-
|
|
111
|
-
export const Underlined = Template.bind({});
|
|
112
|
-
Underlined.args = { inputVariant: "underlined" };
|
|
113
|
-
|
|
114
|
-
export const Outlined = Template.bind({});
|
|
115
|
-
Outlined.args = { inputVariant: "outlined", useDescriptionSlot: true };
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// https://nuxt.com/docs/getting-started/testing#unit-testing
|
|
2
|
-
import type { VueWrapper } from "@vue/test-utils";
|
|
3
|
-
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
4
|
-
import ComponentUnderTest from "../InputSelectWithLabel.vue";
|
|
5
|
-
|
|
6
|
-
const initialFieldData = {
|
|
7
|
-
data: [
|
|
8
|
-
{ id: "1", name: "red", value: "red", label: "Red" },
|
|
9
|
-
{ id: "2", name: "blue", value: "blue", label: "Blue" },
|
|
10
|
-
],
|
|
11
|
-
total: 2,
|
|
12
|
-
skip: 0,
|
|
13
|
-
limit: 10,
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const initialPropsData = {
|
|
17
|
-
name: "colour",
|
|
18
|
-
label: "Colour",
|
|
19
|
-
errorMessage: "",
|
|
20
|
-
modelValue: "red",
|
|
21
|
-
fieldData: initialFieldData,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
let wrapper: VueWrapper<InstanceType<typeof ComponentUnderTest>>;
|
|
25
|
-
const wrapperFactory = (propsData = {}, slots = {}) => {
|
|
26
|
-
const mockPropsData = { ...initialPropsData, ...propsData };
|
|
27
|
-
|
|
28
|
-
return mountSuspended(ComponentUnderTest, {
|
|
29
|
-
attachTo: document.body,
|
|
30
|
-
props: mockPropsData,
|
|
31
|
-
slots,
|
|
32
|
-
});
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
describe("InputSelectWithLabel", () => {
|
|
36
|
-
it("mounts without error", async () => {
|
|
37
|
-
wrapper = await wrapperFactory();
|
|
38
|
-
expect(wrapper.vm).toBeTruthy();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("renders the label text", async () => {
|
|
42
|
-
wrapper = await wrapperFactory({ label: "Favourite colour" });
|
|
43
|
-
expect(wrapper.text()).toContain("Favourite colour");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("renders a native select with an option per fieldData item", async () => {
|
|
47
|
-
wrapper = await wrapperFactory();
|
|
48
|
-
expect(wrapper.find("select").exists()).toBe(true);
|
|
49
|
-
expect(wrapper.findAll("option").map((o) => o.text())).toEqual(["Red", "Blue"]);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("sets data-invalid and the error class when fieldHasError is true", async () => {
|
|
53
|
-
wrapper = await wrapperFactory({ fieldHasError: true });
|
|
54
|
-
expect(wrapper.find(".input-select-with-label").attributes("data-invalid")).toBe("");
|
|
55
|
-
expect(wrapper.find(".input-select-with-label").classes()).toContain("error");
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("renders the error message when fieldHasError is true", async () => {
|
|
59
|
-
wrapper = await wrapperFactory({ fieldHasError: true, errorMessage: "Please choose a colour" });
|
|
60
|
-
expect(wrapper.text()).toContain("Please choose a colour");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("renders the descriptionText slot", async () => {
|
|
64
|
-
wrapper = await wrapperFactory({}, { descriptionText: () => "Pick your favourite" });
|
|
65
|
-
expect(wrapper.text()).toContain("Pick your favourite");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("applies inputVariant as a class on the wrapper", async () => {
|
|
69
|
-
wrapper = await wrapperFactory({ inputVariant: "underlined" });
|
|
70
|
-
expect(wrapper.find(".input-select-with-label").classes()).toContain("underlined");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("applies styleClassPassthrough", async () => {
|
|
74
|
-
wrapper = await wrapperFactory({ styleClassPassthrough: "custom-select" });
|
|
75
|
-
expect(wrapper.find("select").classes()).toContain("custom-select");
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("forwards required to the native select", async () => {
|
|
79
|
-
wrapper = await wrapperFactory({ required: true });
|
|
80
|
-
expect(wrapper.find("select").attributes("required")).toBeDefined();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("applies the error theme to InputSelectCore when fieldHasError is true", async () => {
|
|
84
|
-
wrapper = await wrapperFactory({ fieldHasError: true, theme: "default" });
|
|
85
|
-
expect(wrapper.find(".input-select-wrapper").attributes("data-theme")).toBe("error");
|
|
86
|
-
});
|
|
87
|
-
});
|