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
@@ -0,0 +1,168 @@
1
+ <template>
2
+ <div
3
+ class="input-text-field"
4
+ :class="elementClasses"
5
+ :data-theme="theme"
6
+ :data-input-variant="inputVariant"
7
+ :data-invalid="fieldHasError ? '' : null"
8
+ :data-dirty="isDirty ? '' : null"
9
+ :data-active="isActive ? '' : null"
10
+ >
11
+ <InputLabel
12
+ :id
13
+ :required
14
+ :input-variant
15
+ :field-has-error
16
+ :style-class-passthrough="['input-text-label']"
17
+ >
18
+ <template #textLabel>{{ label }}</template>
19
+ </InputLabel>
20
+
21
+ <InputDescription
22
+ v-if="inputVariant !== 'outlined'"
23
+ :description-id
24
+ :input-variant
25
+ :field-has-error="fieldHasError"
26
+ :style-class-passthrough="['input-text-description']"
27
+ >
28
+ <template v-if="slots.descriptionHtml" #descriptionHtml>
29
+ <slot name="descriptionHtml"></slot>
30
+ </template>
31
+ <template v-if="slots.descriptionText" #descriptionText>
32
+ <slot name="descriptionText"></slot>
33
+ </template>
34
+ </InputDescription>
35
+
36
+ <InputText
37
+ :id
38
+ v-model="modelValue"
39
+ v-model:is-dirty="isDirty"
40
+ v-model:is-active="isActive"
41
+ :type
42
+ :inputmode
43
+ :maxlength
44
+ :min
45
+ :max
46
+ :name
47
+ :placeholder
48
+ :field-has-error
49
+ :required
50
+ :readonly
51
+ :autocomplete
52
+ :theme
53
+ :aria-describedby="ariaDescribedby() ?? undefined"
54
+ :input-variant
55
+ >
56
+ <template v-if="slots.left" #left>
57
+ <slot name="left"></slot>
58
+ </template>
59
+ <template v-if="slots.right" #right>
60
+ <slot name="right"></slot>
61
+ </template>
62
+ </InputText>
63
+
64
+ <InputError :id="errorId" :error-message :show-error="fieldHasError" :is-detached="false" :input-variant />
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
+ min?: string | number;
89
+ max?: string | number;
90
+ type: InputTypesText;
91
+ inputmode?: InputMode;
92
+ name: string;
93
+ placeholder?: string;
94
+ label: string;
95
+ errorMessage: object | string;
96
+ fieldHasError?: boolean;
97
+ required?: boolean;
98
+ readonly?: boolean;
99
+ autocomplete?: string;
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
+ readonly: false,
114
+ autocomplete: undefined,
115
+ styleClassPassthrough: () => [],
116
+ theme: "default",
117
+ inputVariant: "normal",
118
+ });
119
+
120
+ const slots = useSlots();
121
+
122
+ const { id, errorId, descriptionId, ariaDescribedby } = useAriaDescribedById(
123
+ props.name,
124
+ toRef(props, "fieldHasError"),
125
+ slots
126
+ );
127
+
128
+ const modelValue = defineModel<string>({ required: true });
129
+ const isActive = ref<boolean>(false);
130
+ const isDirty = ref<boolean>(false);
131
+
132
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
133
+
134
+ watch(
135
+ () => props.styleClassPassthrough,
136
+ () => {
137
+ resetElementClasses(props.styleClassPassthrough);
138
+ }
139
+ );
140
+
141
+ const testDirty = () => {
142
+ const watchValue = modelValue.value ?? "";
143
+
144
+ if (!isDirty.value && typeof watchValue === "string" && watchValue.length > 0) {
145
+ isDirty.value = true;
146
+ }
147
+ };
148
+
149
+ onMounted(() => {
150
+ testDirty();
151
+ });
152
+
153
+ watch(
154
+ () => modelValue.value,
155
+ () => {
156
+ testDirty();
157
+ }
158
+ );
159
+ </script>
160
+
161
+ <style lang="css">
162
+ @layer components {
163
+ .input-text-field {
164
+ min-inline-size: 0;
165
+ overflow-wrap: anywhere;
166
+ }
167
+ }
168
+ </style>
@@ -0,0 +1,163 @@
1
+ import { computed, ref } from "vue";
2
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
3
+ import InputPasswordField from "../InputPasswordField.vue";
4
+ import CanvasSwitcher from "../../../01.atoms/canvas-switcher/CanvasSwitcher.vue";
5
+ import type { FormUiTheme, InputUiVariant } from "~/types/forms/types.forms.d";
6
+ import type { MediaCanvas } from "~/types/components";
7
+
8
+ interface StoryArgs {
9
+ modelValue: string;
10
+ name: string;
11
+ label: string;
12
+ placeholder: string;
13
+ maxlength: number;
14
+ autocomplete: string;
15
+ errorMessage: string;
16
+ fieldHasError: boolean;
17
+ required: boolean;
18
+ theme: FormUiTheme;
19
+ inputVariant: InputUiVariant;
20
+ styleClassPassthrough: string[];
21
+ showPasswordText: string;
22
+ hidePasswordText: string;
23
+ descriptionText: string;
24
+ }
25
+
26
+ const meta: Meta<StoryArgs> = {
27
+ title: "Components/Forms/Input Text/InputPasswordField",
28
+ component: InputPasswordField,
29
+ argTypes: {
30
+ modelValue: { control: "text", table: { category: "Model" } },
31
+ name: { control: "text", table: { category: "Basic" } },
32
+ label: { control: "text", table: { category: "Basic" } },
33
+ placeholder: { control: "text", table: { category: "Basic" } },
34
+ maxlength: { control: "number", table: { category: "Basic" } },
35
+ autocomplete: {
36
+ control: { type: "select" },
37
+ options: ["current-password", "new-password", "off"],
38
+ table: { category: "Basic" },
39
+ },
40
+ errorMessage: { control: "text", table: { category: "Validation" } },
41
+ fieldHasError: { control: "boolean", table: { category: "Validation" } },
42
+ required: { control: "boolean", table: { category: "Validation" } },
43
+ theme: {
44
+ control: { type: "select" },
45
+ options: ["default", "success", "error", "warning"],
46
+ table: { category: "Styling" },
47
+ },
48
+ inputVariant: {
49
+ control: { type: "select" },
50
+ options: ["normal", "outlined", "underlined"],
51
+ table: { category: "Styling" },
52
+ },
53
+ styleClassPassthrough: { control: "object", table: { category: "Styling" } },
54
+ showPasswordText: { control: "text", description: "Toggle's accessible name while hidden", table: { category: "Accessibility" } },
55
+ hidePasswordText: { control: "text", description: "Toggle's accessible name while shown", table: { category: "Accessibility" } },
56
+ descriptionText: { control: "text", description: "Story-only: fills the descriptionText slot", table: { category: "Slots" } },
57
+ },
58
+ args: {
59
+ modelValue: "",
60
+ name: "password",
61
+ label: "Password",
62
+ placeholder: "",
63
+ maxlength: 255,
64
+ autocomplete: "current-password",
65
+ errorMessage: "",
66
+ fieldHasError: false,
67
+ required: false,
68
+ theme: "default",
69
+ inputVariant: "normal",
70
+ styleClassPassthrough: [],
71
+ showPasswordText: "Show password",
72
+ hidePasswordText: "Hide password",
73
+ descriptionText: "",
74
+ },
75
+ decorators: [
76
+ (story, context) => ({
77
+ components: { story, CanvasSwitcher },
78
+ setup() {
79
+ const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "fullWidthCanvas");
80
+ return { canvasName };
81
+ },
82
+ template: `
83
+ <div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
84
+ <CanvasSwitcher v-model:canvas-name="canvasName" />
85
+ </div>
86
+ <div :class="canvasName" style="margin-inline: auto; padding: 2rem; outline: 1px dashed currentColor;">
87
+ <story />
88
+ </div>
89
+ `,
90
+ }),
91
+ ],
92
+ parameters: {
93
+ layout: "fullscreen",
94
+ docs: {
95
+ description: {
96
+ component:
97
+ "`InputTextField` with a show/hide toggle (an inline `InputButton` in the right slot) that switches the input between `password` and `text`. `showPasswordText`/`hidePasswordText` localise the toggle's accessible name. `autocomplete` defaults to `current-password`; use `new-password` on sign-up and change-password forms.",
98
+ },
99
+ },
100
+ },
101
+ };
102
+
103
+ export default meta;
104
+ type Story = StoryObj<StoryArgs>;
105
+
106
+ const render = (args: StoryArgs) => ({
107
+ components: { InputPasswordField },
108
+ setup() {
109
+ const componentArgs = computed(() => {
110
+ const { modelValue: _modelValue, descriptionText: _descriptionText, ...rest } = args;
111
+ return rest;
112
+ });
113
+ return { args, componentArgs };
114
+ },
115
+ template: `
116
+ <InputPasswordField v-model="args.modelValue" v-bind="componentArgs">
117
+ <template v-if="args.descriptionText" #descriptionText>{{ args.descriptionText }}</template>
118
+ </InputPasswordField>
119
+ `,
120
+ });
121
+
122
+ export const Default: Story = { render };
123
+
124
+ export const NewPassword: Story = {
125
+ args: { label: "Choose a password", autocomplete: "new-password", descriptionText: "At least 12 characters." },
126
+ render,
127
+ parameters: { docs: { description: { story: "Sign-up use: `autocomplete=\"new-password\"` lets password managers offer a generated one." } } },
128
+ };
129
+
130
+ export const WithError: Story = {
131
+ args: { fieldHasError: true, errorMessage: "Password is too short", modelValue: "weak" },
132
+ render,
133
+ };
134
+
135
+ export const LocalisedToggleText: Story = {
136
+ args: { label: "Mot de passe", showPasswordText: "Afficher le mot de passe", hidePasswordText: "Masquer le mot de passe" },
137
+ render,
138
+ };
139
+
140
+ export const Underlined: Story = { args: { inputVariant: "underlined" }, render };
141
+
142
+ export const StressTest: Story = {
143
+ name: "Stress Test (Worst-Case Data)",
144
+ args: {
145
+ modelValue: "correcthorsebatterystaplecorrecthorsebatterystaplecorrecthorsebatterystaple🔑<script>",
146
+ label: "🔐 Persönlicheskennwortfürdenzugangzumkundenkontoundallenverbundenendiensten <b>not bold</b> كلمة المرور",
147
+ descriptionText: "Mindestens zwölf Zeichen, davon eine Ziffer. يجب أن تتكون كلمة المرور من اثني عشر حرفًا على الأقل",
148
+ errorMessage: "DaskennwortentsprichtleidernichtdenSicherheitsanforderungen",
149
+ fieldHasError: true,
150
+ showPasswordText: "Kennwort im Klartext anzeigen",
151
+ hidePasswordText: "Kennwort wieder verbergen",
152
+ },
153
+ render,
154
+ parameters: {
155
+ initialCanvas: "mobileCanvas",
156
+ docs: {
157
+ description: {
158
+ story:
159
+ "A long unbroken value with emoji and HTML-like text, long German/RTL copy everywhere and long toggle labels. Check at every canvas width: the toggle button keeps its size at the right edge, the copy wraps inside the dashed outline, and toggling shows the value as plain text.",
160
+ },
161
+ },
162
+ },
163
+ };
@@ -0,0 +1,194 @@
1
+ import { computed, ref } from "vue";
2
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
3
+ import InputText from "../InputText.vue";
4
+ import CanvasSwitcher from "../../../01.atoms/canvas-switcher/CanvasSwitcher.vue";
5
+ import type { FormUiTheme, InputMode, InputTypesText, InputUiVariant } from "~/types/forms/types.forms.d";
6
+ import type { MediaCanvas } from "~/types/components";
7
+
8
+ interface StoryArgs {
9
+ modelValue: string;
10
+ id: string;
11
+ name: string;
12
+ type: InputTypesText;
13
+ inputmode: InputMode;
14
+ placeholder: string;
15
+ maxlength: number;
16
+ min: string;
17
+ max: string;
18
+ autocomplete: string;
19
+ required: boolean;
20
+ readonly: boolean;
21
+ fieldHasError: boolean;
22
+ theme: FormUiTheme;
23
+ inputVariant: InputUiVariant;
24
+ styleClassPassthrough: string[];
25
+ leftSlotContent: string;
26
+ rightSlotContent: string;
27
+ }
28
+
29
+ const meta: Meta<StoryArgs> = {
30
+ title: "Components/Forms/Input Text/InputText",
31
+ component: InputText,
32
+ argTypes: {
33
+ modelValue: { control: "text", table: { category: "Model" } },
34
+ id: { control: "text", table: { category: "Basic" } },
35
+ name: { control: "text", table: { category: "Basic" } },
36
+ type: {
37
+ control: { type: "select" },
38
+ options: ["text", "email", "password", "number", "tel", "url", "date"],
39
+ table: { category: "Basic" },
40
+ },
41
+ inputmode: {
42
+ control: { type: "select" },
43
+ options: ["text", "email", "tel", "url", "search", "numeric", "none", "decimal"],
44
+ description: "`numeric` also blocks typing anything but digits",
45
+ table: { category: "Basic" },
46
+ },
47
+ placeholder: { control: "text", table: { category: "Basic" } },
48
+ maxlength: { control: "number", table: { category: "Basic" } },
49
+ min: { control: "text", description: 'Native min, e.g. the earliest date for type="date"', table: { category: "Basic" } },
50
+ max: { control: "text", description: 'Native max, e.g. the latest date for type="date"', table: { category: "Basic" } },
51
+ autocomplete: { control: "text", description: "Native autocomplete token, e.g. `email`", table: { category: "Basic" } },
52
+ required: { control: "boolean", table: { category: "Validation" } },
53
+ readonly: { control: "boolean", table: { category: "States" } },
54
+ fieldHasError: { control: "boolean", table: { category: "Validation" } },
55
+ theme: {
56
+ control: { type: "select" },
57
+ options: ["default", "success", "error", "warning"],
58
+ table: { category: "Styling" },
59
+ },
60
+ inputVariant: {
61
+ control: { type: "select" },
62
+ options: ["normal", "outlined", "underlined"],
63
+ table: { category: "Styling" },
64
+ },
65
+ styleClassPassthrough: { control: "object", table: { category: "Styling" } },
66
+ leftSlotContent: { control: "text", description: "Story-only: fills the left slot", table: { category: "Slots" } },
67
+ rightSlotContent: { control: "text", description: "Story-only: fills the right slot", table: { category: "Slots" } },
68
+ },
69
+ args: {
70
+ modelValue: "",
71
+ id: "example-input",
72
+ name: "example",
73
+ type: "text",
74
+ inputmode: "text",
75
+ placeholder: "Enter text",
76
+ maxlength: 255,
77
+ min: "",
78
+ max: "",
79
+ autocomplete: "",
80
+ required: false,
81
+ readonly: false,
82
+ fieldHasError: false,
83
+ theme: "default",
84
+ inputVariant: "normal",
85
+ styleClassPassthrough: [],
86
+ leftSlotContent: "",
87
+ rightSlotContent: "",
88
+ },
89
+ decorators: [
90
+ (story, context) => ({
91
+ components: { story, CanvasSwitcher },
92
+ setup() {
93
+ const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "fullWidthCanvas");
94
+ return { canvasName };
95
+ },
96
+ template: `
97
+ <div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
98
+ <CanvasSwitcher v-model:canvas-name="canvasName" />
99
+ </div>
100
+ <div :class="canvasName" style="margin-inline: auto; padding: 2rem; outline: 1px dashed currentColor;">
101
+ <story />
102
+ </div>
103
+ `,
104
+ }),
105
+ ],
106
+ parameters: {
107
+ layout: "fullscreen",
108
+ docs: {
109
+ description: {
110
+ component:
111
+ "Bare native `<input>` for the `05.forms` text family: no label, description or error (use `InputTextField`, `InputPasswordField` or `InputTextAsNumberField` for those, or pair it with your own `<label for>`). `left`/`right` slots hold decorations or inline `InputButton`s. `inputmode=\"numeric\"` blocks non-digit typing. State is exposed as `data-*` attributes on `.input-text-wrapper`.",
112
+ },
113
+ },
114
+ },
115
+ };
116
+
117
+ export default meta;
118
+ type Story = StoryObj<StoryArgs>;
119
+
120
+ const render = (args: StoryArgs) => ({
121
+ components: { InputText },
122
+ setup() {
123
+ const componentArgs = computed(() => {
124
+ const {
125
+ modelValue: _modelValue,
126
+ leftSlotContent: _left,
127
+ rightSlotContent: _right,
128
+ min,
129
+ max,
130
+ autocomplete,
131
+ ...rest
132
+ } = args;
133
+ return { ...rest, min: min || undefined, max: max || undefined, autocomplete: autocomplete || undefined };
134
+ });
135
+ return { args, componentArgs };
136
+ },
137
+ template: `
138
+ <InputText v-model="args.modelValue" v-bind="componentArgs">
139
+ <template v-if="args.leftSlotContent" #left>{{ args.leftSlotContent }}</template>
140
+ <template v-if="args.rightSlotContent" #right>{{ args.rightSlotContent }}</template>
141
+ </InputText>
142
+ <p style="margin-block-start: 1.6rem; font-family: monospace;">modelValue: {{ JSON.stringify(args.modelValue) }}</p>
143
+ `,
144
+ });
145
+
146
+ export const Default: Story = { render };
147
+
148
+ export const WithSlots: Story = { args: { leftSlotContent: "📧", rightSlotContent: "🔍" }, render };
149
+
150
+ export const WithError: Story = { args: { fieldHasError: true, theme: "error" }, render };
151
+
152
+ export const Readonly: Story = {
153
+ args: { readonly: true, modelValue: "en-GB", inputVariant: "underlined" },
154
+ render,
155
+ parameters: { docs: { description: { story: "`readonly` reaches the native input and sets `data-readonly` on the wrapper." } } },
156
+ };
157
+
158
+ export const NumericOnly: Story = {
159
+ args: { inputmode: "numeric", placeholder: "Digits only" },
160
+ render,
161
+ parameters: {
162
+ docs: { description: { story: "`inputmode=\"numeric\"` shows a number keypad on phones and blocks typing anything but digits." } },
163
+ },
164
+ };
165
+
166
+ export const Underlined: Story = { args: { inputVariant: "underlined" }, render };
167
+
168
+ export const DateWithMinMax: Story = {
169
+ args: { type: "date", name: "appointmentDate", min: new Date().toISOString().split("T")[0], max: "2027-12-31" },
170
+ render,
171
+ parameters: { docs: { description: { story: "A date input whose picker can't go before today." } } },
172
+ };
173
+
174
+ export const StressTest: Story = {
175
+ name: "Stress Test (Worst-Case Data)",
176
+ args: {
177
+ modelValue:
178
+ "Donaudampfschifffahrtsgesellschaftskapitänswitwenrentenversicherung https://example.com/a/very/long/unbroken/url <b>not bold</b>",
179
+ placeholder: "Bitte geben Sie hier Ihre vollständige Lieferadresse einschließlich Postleitzahl ein",
180
+ leftSlotContent: "🔎🔎🔎",
181
+ rightSlotContent: "عربى",
182
+ fieldHasError: true,
183
+ },
184
+ render,
185
+ parameters: {
186
+ initialCanvas: "mobileCanvas",
187
+ docs: {
188
+ description: {
189
+ story:
190
+ "A long unbroken German, URL and HTML-like value with a long placeholder and text in both slots. Check at every canvas width: the input stays inside the dashed outline and scrolls its text instead of growing, the slots keep their size, and markup renders as text. Clear it to check the placeholder.",
191
+ },
192
+ },
193
+ },
194
+ };