srcdev-nuxt-components 9.6.8 → 9.6.10
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 +28 -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 +17 -12
- 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/css-nesting-conventions.md +22 -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 +7 -7
- 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
|
@@ -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
|
+
};
|