srcdev-nuxt-components 9.6.7 → 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.
Files changed (152) hide show
  1. package/.claude/rules/consumer/ids-from-useid.md +14 -0
  2. package/.claude/skills/component-compliance-checklist.md +18 -1
  3. package/.claude/skills/component-inline-action-button.md +1 -1
  4. package/.claude/skills/component-local-style-override.md +1 -1
  5. package/.claude/skills/component-naming.md +13 -13
  6. package/.claude/skills/components/container-glow.md +2 -2
  7. package/.claude/skills/components/form-field.md +2 -2
  8. package/.claude/skills/components/form-wrapper.md +1 -1
  9. package/.claude/skills/components/glowing-border.md +1 -1
  10. package/.claude/skills/components/input-checkbox-radio.md +19 -3
  11. package/.claude/skills/components/input-checkbox.md +14 -3
  12. package/.claude/skills/components/input-description.md +7 -7
  13. package/.claude/skills/components/input-error.md +3 -3
  14. package/.claude/skills/components/input-label.md +7 -8
  15. package/.claude/skills/components/input-number.md +60 -29
  16. package/.claude/skills/components/input-radio.md +15 -1
  17. package/.claude/skills/components/{input-range-core.md → input-range.md} +53 -18
  18. package/.claude/skills/components/input-select.md +153 -0
  19. package/.claude/skills/components/input-text.md +220 -0
  20. package/.claude/skills/components/input-textarea.md +147 -0
  21. package/.claude/skills/components/select-menu.md +4 -4
  22. package/.claude/skills/components/toggle-switch.md +143 -0
  23. package/.claude/skills/composable-zod-validation.md +1 -1
  24. package/.claude/skills/icon-sets.md +1 -1
  25. package/.claude/skills/icon-sizing.md +1 -1
  26. package/.claude/skills/index.md +10 -10
  27. package/.claude/skills/storybook-add-story.md +1 -1
  28. package/.claude/skills/testing-add-unit-test.md +17 -0
  29. package/.claude/skills/theming-component-token-pattern.md +8 -8
  30. package/.claude/skills/theming-form-geometry-tokens.md +1 -1
  31. package/.vscode/srcdev-component-input-checkbox-radio.code-snippets +0 -1
  32. package/.vscode/srcdev-component-input-range.code-snippets +38 -18
  33. package/.vscode/srcdev-component-input-select.code-snippets +20 -14
  34. package/.vscode/srcdev-component-input-text.code-snippets +46 -40
  35. package/.vscode/srcdev-component-input-textarea.code-snippets +19 -14
  36. package/.vscode/srcdev-component-toggle-switch.code-snippets +19 -29
  37. package/app/assets/styles/setup/04.elements/forms/00.element-defaults.css +2 -2
  38. package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +1 -1
  39. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +2 -2
  40. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +4 -4
  41. package/app/components/02.molecules/progress-tracker/stories/ProgressTracker.stories.ts +4 -4
  42. package/app/components/05.forms/form-errors/CONSUMER-STYLING.md +1 -1
  43. package/app/components/05.forms/form-field/CONSUMER-STYLING.md +2 -2
  44. package/app/components/05.forms/input-button/stories/InputButton.stories.ts +3 -3
  45. package/app/components/05.forms/input-checkbox/CONSUMER-STYLING.md +10 -0
  46. package/app/components/05.forms/input-checkbox/MultipleCheckboxes.vue +18 -4
  47. package/app/components/05.forms/input-checkbox/SingleCheckbox.vue +14 -2
  48. package/app/components/05.forms/input-checkbox/stories/MultipleCheckboxes.stories.ts +146 -37
  49. package/app/components/05.forms/input-checkbox/stories/SingleCheckbox.stories.ts +107 -30
  50. package/app/components/05.forms/input-checkbox/tests/MultipleCheckboxes.spec.ts +38 -4
  51. package/app/components/05.forms/input-checkbox/tests/SingleCheckbox.spec.ts +5 -0
  52. package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +10 -3
  53. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadio.vue +1 -0
  54. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +11 -10
  55. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioField.vue +3 -0
  56. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadio.stories.ts +44 -17
  57. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioButton.stories.ts +164 -58
  58. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioField.stories.ts +94 -21
  59. package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadio.spec.ts +14 -3
  60. package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioButton.spec.ts +38 -3
  61. package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioField.spec.ts +14 -3
  62. package/app/components/05.forms/input-description/CONSUMER-STYLING.md +7 -7
  63. package/app/components/05.forms/input-label/CONSUMER-STYLING.md +6 -6
  64. package/app/components/05.forms/input-number/CONSUMER-STYLING.md +50 -6
  65. package/app/components/05.forms/input-number/InputNumber.vue +39 -27
  66. package/app/components/05.forms/input-number/InputNumberField.vue +29 -16
  67. package/app/components/05.forms/input-number/stories/InputNumber.stories.ts +76 -25
  68. package/app/components/05.forms/input-number/stories/InputNumberField.stories.ts +116 -50
  69. package/app/components/05.forms/input-number/tests/InputNumber.spec.ts +49 -13
  70. package/app/components/05.forms/input-number/tests/InputNumberField.spec.ts +59 -5
  71. package/app/components/05.forms/input-radio/CONSUMER-STYLING.md +11 -0
  72. package/app/components/05.forms/input-radio/MultipleRadiobuttons.vue +14 -3
  73. package/app/components/05.forms/input-radio/stories/MultipleRadiobuttons.stories.ts +138 -38
  74. package/app/components/05.forms/input-radio/tests/MultipleRadioButtons.spec.ts +50 -3
  75. package/app/components/05.forms/input-range/CONSUMER-STYLING.md +68 -26
  76. package/app/components/05.forms/input-range/InputRange.vue +247 -0
  77. package/app/components/05.forms/input-range/{InputRangeDefault.vue → InputRangeField.vue} +41 -18
  78. package/app/components/05.forms/input-range/stories/InputRange.stories.ts +187 -0
  79. package/app/components/05.forms/input-range/stories/InputRangeField.stories.ts +196 -0
  80. package/app/components/05.forms/input-range/tests/{InputRangeCore.spec.ts → InputRange.spec.ts} +39 -15
  81. package/app/components/05.forms/input-range/tests/{InputRangeDefault.spec.ts → InputRangeField.spec.ts} +71 -7
  82. package/app/components/05.forms/input-select/CONSUMER-STYLING.md +80 -10
  83. package/app/components/05.forms/input-select/InputSelect.vue +198 -0
  84. package/app/components/05.forms/input-select/InputSelectField.vue +130 -0
  85. package/app/components/05.forms/input-select/stories/InputSelect.stories.ts +184 -0
  86. package/app/components/05.forms/input-select/stories/InputSelectField.stories.ts +187 -0
  87. package/app/components/05.forms/input-select/tests/{InputSelectCore.spec.ts → InputSelect.spec.ts} +75 -12
  88. package/app/components/05.forms/input-select/tests/InputSelectField.spec.ts +121 -0
  89. package/app/components/05.forms/input-text/CONSUMER-STYLING.md +85 -15
  90. package/app/components/05.forms/input-text/{InputPasswordWithLabel.vue → InputPasswordField.vue} +16 -19
  91. package/app/components/05.forms/input-text/{InputTextCore.vue → InputText.vue} +42 -78
  92. package/app/components/05.forms/input-text/{InputTextAsNumberWithLabel.vue → InputTextAsNumberField.vue} +66 -62
  93. package/app/components/05.forms/input-text/InputTextField.vue +168 -0
  94. package/app/components/05.forms/input-text/stories/InputPasswordField.stories.ts +163 -0
  95. package/app/components/05.forms/input-text/stories/InputText.stories.ts +194 -0
  96. package/app/components/05.forms/input-text/stories/InputTextAsNumberField.stories.ts +182 -0
  97. package/app/components/05.forms/input-text/stories/InputTextField.stories.ts +206 -0
  98. package/app/components/05.forms/input-text/tests/{InputPasswordWithLabel.spec.ts → InputPasswordField.spec.ts} +42 -3
  99. package/app/components/05.forms/input-text/tests/InputText.spec.ts +166 -0
  100. package/app/components/05.forms/input-text/tests/InputTextAsNumberField.spec.ts +135 -0
  101. package/app/components/05.forms/input-text/tests/InputTextField.spec.ts +130 -0
  102. package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +94 -12
  103. package/app/components/05.forms/input-textarea/InputTextarea.vue +165 -0
  104. package/app/components/05.forms/input-textarea/InputTextareaField.vue +159 -0
  105. package/app/components/05.forms/input-textarea/stories/InputTextarea.stories.ts +198 -0
  106. package/app/components/05.forms/input-textarea/stories/InputTextareaField.stories.ts +161 -0
  107. package/app/components/05.forms/input-textarea/tests/InputTextarea.spec.ts +149 -0
  108. package/app/components/05.forms/input-textarea/tests/InputTextareaField.spec.ts +122 -0
  109. package/app/components/05.forms/patterns/stories/ContactForm.stories.ts +13 -13
  110. package/app/components/05.forms/patterns/stories/LoginForm.stories.ts +6 -6
  111. package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +34 -36
  112. package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +71 -50
  113. package/app/components/05.forms/toggle-switch/{ToggleSwitchCore.vue → ToggleSwitch.vue} +58 -73
  114. package/app/components/05.forms/toggle-switch/{ToggleSwitchWithLabel.vue → ToggleSwitchField.vue} +65 -19
  115. package/app/components/05.forms/toggle-switch/stories/ToggleSwitch.stories.ts +148 -0
  116. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchField.stories.ts +176 -0
  117. package/app/components/05.forms/toggle-switch/tests/ToggleSwitch.spec.ts +145 -0
  118. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchField.spec.ts +155 -0
  119. package/package.json +1 -1
  120. package/.claude/skills/components/input-select-core.md +0 -151
  121. package/.claude/skills/components/input-text-core.md +0 -204
  122. package/.claude/skills/components/input-textarea-core.md +0 -154
  123. package/.claude/skills/components/toggle-switch-core.md +0 -169
  124. package/app/components/05.forms/input-range/InputRangeCore.vue +0 -169
  125. package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +0 -126
  126. package/app/components/05.forms/input-range/stories/InputRangeDefault.stories.ts +0 -108
  127. package/app/components/05.forms/input-select/InputSelectCore.vue +0 -188
  128. package/app/components/05.forms/input-select/InputSelectWithLabel.vue +0 -118
  129. package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +0 -69
  130. package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +0 -115
  131. package/app/components/05.forms/input-select/tests/InputSelectWithLabel.spec.ts +0 -87
  132. package/app/components/05.forms/input-text/InputTextWithLabel.vue +0 -154
  133. package/app/components/05.forms/input-text/stories/InputPasswordWithLabel.stories.ts +0 -368
  134. package/app/components/05.forms/input-text/stories/InputTextAsNumberWithLabel.stories.ts +0 -480
  135. package/app/components/05.forms/input-text/stories/InputTextCore.stories.ts +0 -267
  136. package/app/components/05.forms/input-text/stories/InputTextWithLabel.stories.ts +0 -319
  137. package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +0 -75
  138. package/app/components/05.forms/input-text/tests/InputTextCore.spec.ts +0 -60
  139. package/app/components/05.forms/input-text/tests/InputTextWithLabel.spec.ts +0 -71
  140. package/app/components/05.forms/input-textarea/InputTextareaCore.vue +0 -159
  141. package/app/components/05.forms/input-textarea/InputTextareaWithLabel.vue +0 -136
  142. package/app/components/05.forms/input-textarea/stories/InputTextareaCore.stories.ts +0 -247
  143. package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +0 -458
  144. package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +0 -119
  145. package/app/components/05.forms/input-textarea/tests/InputTextareaWithLabel.spec.ts +0 -93
  146. package/app/components/05.forms/toggle-switch/ToggleSwitchWithLabelInline.vue +0 -81
  147. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +0 -251
  148. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +0 -251
  149. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +0 -283
  150. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +0 -101
  151. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabel.spec.ts +0 -133
  152. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabelInline.spec.ts +0 -66
@@ -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
- });
@@ -1,154 +0,0 @@
1
- <template>
2
- <div>
3
- <div
4
- class="input-text-with-label"
5
- :data-theme="theme"
6
- :data-invalid="fieldHasError ? '' : null"
7
- :class="[elementClasses, inputVariant, { dirty: isDirty }, { active: isActive }]"
8
- >
9
- <InputLabel
10
- :id
11
- :required
12
- :input-variant
13
- :field-has-error
14
- :style-class-passthrough="['input-text-label']"
15
- >
16
- <template #textLabel>{{ label }}</template>
17
- </InputLabel>
18
-
19
- <InputDescription
20
- v-if="inputVariant !== 'outlined'"
21
- :description-id
22
- :input-variant
23
- :field-has-error="fieldHasError"
24
- :style-class-passthrough="['input-text-description']"
25
- >
26
- <template v-if="slots.descriptionHtml" #descriptionHtml>
27
- <slot name="descriptionHtml"></slot>
28
- </template>
29
- <template v-if="slots.descriptionText" #descriptionText>
30
- <slot name="descriptionText"></slot>
31
- </template>
32
- </InputDescription>
33
-
34
- <InputTextCore
35
- :id
36
- v-model="modelValue"
37
- v-model:is-dirty="isDirty"
38
- v-model:is-active="isActive"
39
- :type
40
- :inputmode
41
- :maxlength
42
- :min
43
- :max
44
- :name
45
- :placeholder
46
- :label
47
- :error-message
48
- :field-has-error
49
- :required
50
- :style-class-passthrough
51
- :theme
52
- :aria-describedby="ariaDescribedby()"
53
- :input-variant
54
- >
55
- <template v-if="slots.left" #left>
56
- <slot name="left"></slot>
57
- </template>
58
- <template v-if="slots.right" #right>
59
- <slot name="right"></slot>
60
- </template>
61
- </InputTextCore>
62
-
63
- <InputError :id="errorId" :error-message :show-error="fieldHasError" :is-detached="false" :input-variant />
64
- </div>
65
-
66
- <InputDescription
67
- v-if="inputVariant === 'outlined'"
68
- :description-id
69
- :input-variant
70
- :field-has-error="fieldHasError"
71
- :style-class-passthrough="['input-text-description']"
72
- >
73
- <template v-if="slots.descriptionHtml" #descriptionHtml>
74
- <slot name="descriptionHtml"></slot>
75
- </template>
76
- <template v-if="slots.descriptionText" #descriptionText>
77
- <slot name="descriptionText"></slot>
78
- </template>
79
- </InputDescription>
80
- </div>
81
- </template>
82
-
83
- <script setup lang="ts">
84
- import type { InputTypesText, FormUiTheme, InputUiVariant, InputMode } from "~/types/forms/types.forms";
85
-
86
- interface Props {
87
- maxlength?: number;
88
- /** Passed straight through to the native input — meaningful for type="date"/"number" etc. */
89
- min?: string | number;
90
- /** Passed straight through to the native input — meaningful for type="date"/"number" etc. */
91
- max?: string | number;
92
- type: InputTypesText;
93
- inputmode?: InputMode;
94
- name: string;
95
- placeholder?: string;
96
- label: string;
97
- errorMessage: object | string;
98
- fieldHasError?: boolean;
99
- required?: boolean;
100
- styleClassPassthrough?: string | string[];
101
- theme?: FormUiTheme;
102
- inputVariant?: InputUiVariant;
103
- }
104
-
105
- const props = withDefaults(defineProps<Props>(), {
106
- maxlength: 255,
107
- min: undefined,
108
- max: undefined,
109
- inputmode: "text",
110
- placeholder: "",
111
- fieldHasError: false,
112
- required: false,
113
- styleClassPassthrough: () => [],
114
- theme: "default",
115
- inputVariant: "normal",
116
- });
117
-
118
- const slots = useSlots();
119
-
120
- const FormUiTheme = computed(() => {
121
- return props.fieldHasError ? "error" : props.theme;
122
- });
123
-
124
- const { id, errorId, descriptionId, ariaDescribedby } = useAriaDescribedById(
125
- props.name,
126
- toRef(props, "fieldHasError"),
127
- slots
128
- );
129
-
130
- const modelValue = defineModel<string>({ required: true });
131
- const isActive = ref<boolean>(false);
132
- const isDirty = ref<boolean>(false);
133
-
134
- const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
135
-
136
- const testDirty = () => {
137
- const watchValue = modelValue.value ?? "";
138
-
139
- if (!isDirty.value && typeof watchValue === "string" && watchValue.length > 0) {
140
- isDirty.value = true;
141
- }
142
- };
143
-
144
- onMounted(() => {
145
- testDirty();
146
- });
147
-
148
- watch(
149
- () => modelValue.value,
150
- () => {
151
- testDirty();
152
- }
153
- );
154
- </script>