@pithyjs/pidl-catalog 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/specs/atoms/actions/button.pidl.json +289 -0
- package/dist/specs/atoms/actions/icon-group.pidl.json +141 -0
- package/dist/specs/atoms/actions/segmented-control.pidl.json +137 -0
- package/dist/specs/atoms/content/action-tile.pidl.json +135 -0
- package/dist/specs/atoms/content/badge.pidl.json +189 -0
- package/dist/specs/atoms/content/callout.pidl.json +170 -0
- package/dist/specs/atoms/content/code-block.pidl.json +170 -0
- package/dist/specs/atoms/content/heading.pidl.json +175 -0
- package/dist/specs/atoms/content/icon.pidl.json +127 -0
- package/dist/specs/atoms/content/image.pidl.json +227 -0
- package/dist/specs/atoms/content/prose.pidl.json +183 -0
- package/dist/specs/atoms/content/stat.pidl.json +158 -0
- package/dist/specs/atoms/content/tabs.pidl.json +207 -0
- package/dist/specs/atoms/content/text.pidl.json +212 -0
- package/dist/specs/atoms/content/tile.pidl.json +156 -0
- package/dist/specs/atoms/data/donut.pidl.json +352 -0
- package/dist/specs/atoms/data/gauge.pidl.json +274 -0
- package/dist/specs/atoms/forms/checkbox-group.pidl.json +170 -0
- package/dist/specs/atoms/forms/checkbox.pidl.json +323 -0
- package/dist/specs/atoms/forms/file-picker.pidl.json +161 -0
- package/dist/specs/atoms/forms/form-field.pidl.json +106 -0
- package/dist/specs/atoms/forms/input.pidl.json +466 -0
- package/dist/specs/atoms/forms/radio-group.pidl.json +174 -0
- package/dist/specs/atoms/forms/radio.pidl.json +312 -0
- package/dist/specs/atoms/forms/search-input.pidl.json +265 -0
- package/dist/specs/atoms/forms/select.pidl.json +420 -0
- package/dist/specs/atoms/forms/textarea.pidl.json +422 -0
- package/dist/specs/atoms/forms/toggle.pidl.json +314 -0
- package/dist/specs/layout/grid-item.pidl.json +120 -0
- package/dist/specs/layout/grid.pidl.json +157 -0
- package/dist/specs/layout/page-section.pidl.json +105 -0
- package/dist/specs/layout/smart-container.pidl.json +148 -0
- package/dist/specs/layout/stack.pidl.json +144 -0
- package/dist/specs/organisms/content/accordion.pidl.json +196 -0
- package/dist/specs/organisms/content/card.pidl.json +283 -0
- package/dist/specs/organisms/feedback/alert-dialog.pidl.json +158 -0
- package/dist/specs/organisms/feedback/modal.pidl.json +133 -0
- package/dist/specs/organisms/feedback/toast.pidl.json +150 -0
- package/dist/specs/organisms/forms/dynamic-form.pidl.json +314 -0
- package/dist/specs/organisms/forms/form.pidl.json +135 -0
- package/dist/specs/organisms/forms/subscribe-form.pidl.json +188 -0
- package/dist/specs/organisms/navigation/command-palette.pidl.json +263 -0
- package/dist/specs/organisms/navigation/docs-sidebar.pidl.json +264 -0
- package/dist/specs/organisms/navigation/navbar.pidl.json +356 -0
- package/dist/specs/organisms/navigation/table-of-contents.pidl.json +197 -0
- package/dist/specs/sections/contact-section.pidl.json +183 -0
- package/dist/specs/sections/cta-section.pidl.json +168 -0
- package/dist/specs/sections/faq-section.pidl.json +202 -0
- package/dist/specs/sections/features-section.pidl.json +214 -0
- package/dist/specs/sections/footer-section.pidl.json +187 -0
- package/dist/specs/sections/hero-section.pidl.json +202 -0
- package/dist/src/borders-psychology.d.ts +17 -0
- package/dist/src/borders-psychology.d.ts.map +1 -0
- package/dist/src/borders-psychology.js +42 -0
- package/dist/src/borders-psychology.js.map +1 -0
- package/dist/src/color-psychology.d.ts +20 -0
- package/dist/src/color-psychology.d.ts.map +1 -0
- package/dist/src/color-psychology.js +57 -0
- package/dist/src/color-psychology.js.map +1 -0
- package/dist/src/design-intelligence.d.ts +13 -0
- package/dist/src/design-intelligence.d.ts.map +1 -0
- package/dist/src/design-intelligence.js +36 -0
- package/dist/src/design-intelligence.js.map +1 -0
- package/dist/src/index.d.ts +6238 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +105 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/motion-psychology.d.ts +19 -0
- package/dist/src/motion-psychology.d.ts.map +1 -0
- package/dist/src/motion-psychology.js +47 -0
- package/dist/src/motion-psychology.js.map +1 -0
- package/dist/src/mutation-vocabulary.d.ts +13 -0
- package/dist/src/mutation-vocabulary.d.ts.map +1 -0
- package/dist/src/mutation-vocabulary.js +67 -0
- package/dist/src/mutation-vocabulary.js.map +1 -0
- package/dist/src/radius-psychology.d.ts +17 -0
- package/dist/src/radius-psychology.d.ts.map +1 -0
- package/dist/src/radius-psychology.js +42 -0
- package/dist/src/radius-psychology.js.map +1 -0
- package/dist/src/shadows-psychology.d.ts +17 -0
- package/dist/src/shadows-psychology.d.ts.map +1 -0
- package/dist/src/shadows-psychology.js +44 -0
- package/dist/src/shadows-psychology.js.map +1 -0
- package/dist/src/spacing-psychology.d.ts +22 -0
- package/dist/src/spacing-psychology.d.ts.map +1 -0
- package/dist/src/spacing-psychology.js +59 -0
- package/dist/src/spacing-psychology.js.map +1 -0
- package/dist/src/typography-psychology.d.ts +22 -0
- package/dist/src/typography-psychology.d.ts.map +1 -0
- package/dist/src/typography-psychology.js +68 -0
- package/dist/src/typography-psychology.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "component",
|
|
3
|
+
"id": "ui.form-field",
|
|
4
|
+
"name": "FormField",
|
|
5
|
+
"displayName": "Form Field",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"level": "molecule",
|
|
8
|
+
"domain": "forms",
|
|
9
|
+
"bidi": true,
|
|
10
|
+
"description": "Form field molecule — wraps a form atom (Input, Select, Textarea) with a visible label and optional required asterisk indicator. Provides label-to-input association via <label for>.",
|
|
11
|
+
"props": [
|
|
12
|
+
{
|
|
13
|
+
"name": "label",
|
|
14
|
+
"label": "Label",
|
|
15
|
+
"type": "string",
|
|
16
|
+
"required": true,
|
|
17
|
+
"placeholder": "Field label",
|
|
18
|
+
"description": "Visible label text displayed above the input"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "required",
|
|
22
|
+
"label": "Required",
|
|
23
|
+
"type": "boolean",
|
|
24
|
+
"default": false,
|
|
25
|
+
"description": "Whether the field is required — shows red asterisk indicator after label"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "inputId",
|
|
29
|
+
"label": "Input ID",
|
|
30
|
+
"editor": { "tab": "settings" },
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Explicit input ID — passed to <label for> and should match child input's id prop. Auto-generated if omitted."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "size",
|
|
36
|
+
"label": "Size",
|
|
37
|
+
"type": "enum",
|
|
38
|
+
"enum": ["sm", "md", "lg"],
|
|
39
|
+
"default": "md",
|
|
40
|
+
"description": "Control size — matches label font to input size via shared control-font-sizes"
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"slots": [{ "name": "default", "displayName": "Content" }],
|
|
44
|
+
"tokens": {
|
|
45
|
+
"gap.label-to-input": "--space-sm (close friends)",
|
|
46
|
+
"text.label": "--color-text, --font-weight-medium",
|
|
47
|
+
"text.required": "--color-danger, --font-weight-bold",
|
|
48
|
+
"font-size": "shared.$control-font-sizes per size"
|
|
49
|
+
},
|
|
50
|
+
"spacing": {
|
|
51
|
+
"internal": {
|
|
52
|
+
"label-to-input": { "relationship": "close_friends", "token": "sm" },
|
|
53
|
+
"label-to-asterisk": { "relationship": "lovers", "token": "xs" }
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"constraints": [
|
|
57
|
+
"Label is required — every form field needs a visible label",
|
|
58
|
+
"Required asterisk is aria-hidden (label text conveys required status via aria-required on the input)",
|
|
59
|
+
"Child input must receive the same id via its id prop for label association",
|
|
60
|
+
"Uses shared $control-font-sizes map for consistent sizing across form atoms"
|
|
61
|
+
],
|
|
62
|
+
"a11y": [
|
|
63
|
+
"Visible <label for='...'> provides accessible name for the input",
|
|
64
|
+
"Required asterisk has aria-hidden='true' — decorative, input's aria-required conveys state",
|
|
65
|
+
"Label is clickable — focuses the associated input"
|
|
66
|
+
],
|
|
67
|
+
"ai": {
|
|
68
|
+
"synonyms": [
|
|
69
|
+
"form field",
|
|
70
|
+
"field wrapper",
|
|
71
|
+
"label wrapper",
|
|
72
|
+
"form group",
|
|
73
|
+
"field group"
|
|
74
|
+
],
|
|
75
|
+
"avoid": [
|
|
76
|
+
"Wrapping input without matching id — label association will break",
|
|
77
|
+
"Using FormField with floatingLabel Input — redundant visible labels"
|
|
78
|
+
],
|
|
79
|
+
"patterns": [
|
|
80
|
+
"Basic: <FormField label=\"'Email'\"><Input label=\"'Email'\" type=\"'email'\" /></FormField>",
|
|
81
|
+
"Required: <FormField label=\"'Email'\" required=\"true\"><Input label=\"'Email'\" required=\"true\" /></FormField>",
|
|
82
|
+
"With helper: <FormField label=\"'Email'\"><Input label=\"'Email'\" helperText=\"'We will never share it'\" /></FormField>",
|
|
83
|
+
"With error: <FormField label=\"'Email'\"><Input label=\"'Email'\" error=\"hasError\" errorMessage=\"'Invalid'\" /></FormField>"
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"examples": [
|
|
87
|
+
{
|
|
88
|
+
"id": "basic",
|
|
89
|
+
"title": "Basic form field",
|
|
90
|
+
"pithy": "<FormField label=\"'Email'\"><Input label=\"'Email'\" type=\"'email'\" /></FormField>",
|
|
91
|
+
"assertions": ["visible label", "label for matches input id"]
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "required",
|
|
95
|
+
"title": "Required field with asterisk",
|
|
96
|
+
"pithy": "<FormField label=\"'Email'\" required=\"true\"><Input label=\"'Email'\" required=\"true\" /></FormField>",
|
|
97
|
+
"assertions": ["asterisk visible", "asterisk aria-hidden"]
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"designGuidance": {
|
|
101
|
+
"surface": "No background — transparent wrapper, label inherits text color.",
|
|
102
|
+
"borders": "No borders — the child input handles its own border styling.",
|
|
103
|
+
"interactivity": "Label click focuses the associated input. Required asterisk is purely visual.",
|
|
104
|
+
"bestPractices": "Always pair FormField with Input's label prop (for aria-label fallback). Don't combine with floatingLabel — use one or the other. Pass the same id to both FormField.inputId and Input.id for label association."
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "component",
|
|
3
|
+
"id": "ui.input",
|
|
4
|
+
"name": "Input",
|
|
5
|
+
"displayName": "Input",
|
|
6
|
+
"version": "0.3.0",
|
|
7
|
+
"level": "atom",
|
|
8
|
+
"domain": "forms",
|
|
9
|
+
"bidi": true,
|
|
10
|
+
"description": "Text input atom with floating label, success/error states with shake animation and state icons, helper text, error message with aria-describedby, character counter, clear button, password visibility toggle, loading spinner, and polished prefix/suffix grouping. No validation logic — states driven externally.",
|
|
11
|
+
"props": [
|
|
12
|
+
{
|
|
13
|
+
"name": "type",
|
|
14
|
+
"label": "Type",
|
|
15
|
+
"type": "enum",
|
|
16
|
+
"enum": ["text", "email", "password", "number", "tel", "url"],
|
|
17
|
+
"default": "text",
|
|
18
|
+
"description": "HTML input type — standard text-like types only (date/file deferred)"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "id",
|
|
22
|
+
"label": "ID",
|
|
23
|
+
"editor": { "tab": "settings" },
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Explicit input ID — overrides auto-generated ID. Used by FormField for label association."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "name",
|
|
29
|
+
"label": "Name",
|
|
30
|
+
"editor": { "tab": "settings" },
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Input name attribute for form submission"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "value",
|
|
36
|
+
"reactive": true,
|
|
37
|
+
"label": "Value",
|
|
38
|
+
"type": "string | Signal<string>",
|
|
39
|
+
"description": "Input value — static string or signal accessor for controlled input"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "placeholder",
|
|
43
|
+
"label": "Placeholder",
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "Placeholder text shown when input is empty"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "disabled",
|
|
49
|
+
"label": "Disabled",
|
|
50
|
+
"editor": { "tab": "settings" },
|
|
51
|
+
"type": "boolean | Signal<boolean>",
|
|
52
|
+
"default": false,
|
|
53
|
+
"description": "Disabled state — uses native disabled attribute. Accepts signal accessor."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "readonly",
|
|
57
|
+
"label": "Read-only",
|
|
58
|
+
"editor": { "tab": "settings" },
|
|
59
|
+
"type": "boolean | Signal<boolean>",
|
|
60
|
+
"default": false,
|
|
61
|
+
"description": "Readonly state — input is not editable but remains focusable. Accepts signal accessor."
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "required",
|
|
65
|
+
"label": "Required",
|
|
66
|
+
"type": "boolean",
|
|
67
|
+
"default": false,
|
|
68
|
+
"description": "Sets aria-required on the input"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "error",
|
|
72
|
+
"label": "Error",
|
|
73
|
+
"editor": { "tab": "settings" },
|
|
74
|
+
"type": "boolean | Signal<boolean>",
|
|
75
|
+
"default": false,
|
|
76
|
+
"description": "Visual error state — sets aria-invalid, shows red border + alert icon, shakes on transition. No validation logic."
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "errorMessage",
|
|
80
|
+
"label": "Error message",
|
|
81
|
+
"editor": { "tab": "settings" },
|
|
82
|
+
"type": "string | Signal<string>",
|
|
83
|
+
"description": "Error message shown below input when error is truthy. Linked via aria-describedby with role='alert'. Replaces helper text when visible."
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "helperText",
|
|
87
|
+
"label": "Helper text",
|
|
88
|
+
"type": "string",
|
|
89
|
+
"description": "Persistent hint text below input. Hidden when error message is visible. Linked via aria-describedby."
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "success",
|
|
93
|
+
"label": "Success",
|
|
94
|
+
"editor": { "tab": "settings" },
|
|
95
|
+
"type": "boolean | Signal<boolean>",
|
|
96
|
+
"default": false,
|
|
97
|
+
"description": "Visual success state — shows green border + checkmark icon. Error takes precedence when both are true."
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "size",
|
|
101
|
+
"label": "Size",
|
|
102
|
+
"type": "enum",
|
|
103
|
+
"enum": ["sm", "md", "lg"],
|
|
104
|
+
"default": "md",
|
|
105
|
+
"description": "Control size — maps to u-control-* utilities (padding, min-height, gap)"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "label",
|
|
109
|
+
"label": "Label",
|
|
110
|
+
"type": "string",
|
|
111
|
+
"required": true,
|
|
112
|
+
"placeholder": "Email",
|
|
113
|
+
"description": "Accessible label — used as aria-label or as floating label text when floatingLabel is true"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"name": "floatingLabel",
|
|
117
|
+
"label": "Floating label",
|
|
118
|
+
"type": "boolean",
|
|
119
|
+
"default": false,
|
|
120
|
+
"description": "Enable floating label — label starts centered inside the input (like a placeholder), floats to top-inside on focus/fill. Uses visible <label for> instead of aria-label."
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "autocomplete",
|
|
124
|
+
"label": "Autocomplete",
|
|
125
|
+
"editor": { "tab": "settings" },
|
|
126
|
+
"type": "string",
|
|
127
|
+
"description": "Autocomplete hint (e.g., 'email', 'current-password', 'off')"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "maxLength",
|
|
131
|
+
"label": "Max length",
|
|
132
|
+
"type": "number",
|
|
133
|
+
"description": "Maximum character length — also enables character counter display with tabular-nums"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"name": "min",
|
|
137
|
+
"label": "Min",
|
|
138
|
+
"editor": { "type": "number" },
|
|
139
|
+
"type": "number | string",
|
|
140
|
+
"description": "Minimum value — primarily for type='number'"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"name": "max",
|
|
144
|
+
"label": "Max",
|
|
145
|
+
"editor": { "type": "number" },
|
|
146
|
+
"type": "number | string",
|
|
147
|
+
"description": "Maximum value — primarily for type='number'"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"name": "step",
|
|
151
|
+
"label": "Step",
|
|
152
|
+
"editor": { "type": "number" },
|
|
153
|
+
"type": "number | string",
|
|
154
|
+
"description": "Step value — primarily for type='number'"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "clearable",
|
|
158
|
+
"label": "Clearable",
|
|
159
|
+
"type": "boolean",
|
|
160
|
+
"default": false,
|
|
161
|
+
"description": "Show clear button when input has value. Hidden when disabled, readonly, or loading."
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "showPasswordToggle",
|
|
165
|
+
"label": "Show password toggle",
|
|
166
|
+
"type": "boolean",
|
|
167
|
+
"description": "Show password visibility toggle (eye icon). Auto-enabled for password inputs, can be opted out — true when type='password', false otherwise. Conditional on `type`, so there is no static default."
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"name": "loading",
|
|
171
|
+
"label": "Loading",
|
|
172
|
+
"editor": { "tab": "settings" },
|
|
173
|
+
"type": "boolean | Signal<boolean>",
|
|
174
|
+
"default": false,
|
|
175
|
+
"description": "Loading state — shows spinner, sets aria-busy, hides suffix, clear button, and password toggle."
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "prefix",
|
|
179
|
+
"type": "() => ComponentHandle",
|
|
180
|
+
"description": "Leading content slot — renders with tinted background and separator border"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "suffix",
|
|
184
|
+
"type": "() => ComponentHandle",
|
|
185
|
+
"description": "Trailing content slot — renders with tinted background and separator border. Hidden during loading."
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"name": "onInput",
|
|
189
|
+
"type": "(value: string, e: InputEvent) => void",
|
|
190
|
+
"description": "Fires on every keystroke with current value and native InputEvent"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "onChange",
|
|
194
|
+
"type": "(value: string, e: Event) => void",
|
|
195
|
+
"description": "Fires on commit (blur/Enter) with current value and native Event"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"name": "onBlur",
|
|
199
|
+
"type": "(e: FocusEvent) => void",
|
|
200
|
+
"description": "Fires when input loses focus"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"name": "onFocus",
|
|
204
|
+
"type": "(e: FocusEvent) => void",
|
|
205
|
+
"description": "Fires when input gains focus"
|
|
206
|
+
}
|
|
207
|
+
],
|
|
208
|
+
"events": ["input", "change", "blur", "focus"],
|
|
209
|
+
"slots": [
|
|
210
|
+
{
|
|
211
|
+
"name": "prefix",
|
|
212
|
+
"displayName": "Prefix",
|
|
213
|
+
"description": "Icon or label at the inline-start of the input.",
|
|
214
|
+
"accepts": ["ui.icon", "ui.text"],
|
|
215
|
+
"max": 1
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"name": "suffix",
|
|
219
|
+
"displayName": "Suffix",
|
|
220
|
+
"description": "Icon or action button at the inline-end of the input.",
|
|
221
|
+
"accepts": ["ui.icon", "ui.button"],
|
|
222
|
+
"max": 1
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
"tokens": {
|
|
226
|
+
"border.default": "border(control) — stable width, color-only changes",
|
|
227
|
+
"border.hover": "--color-primary (border-color) + --color-surface-bright (bg tint)",
|
|
228
|
+
"border.focus": "--color-focus-ring + ring() box-shadow (--ring-offset: 0px, no double-border)",
|
|
229
|
+
"border.error": "--color-danger + shake animation on transition",
|
|
230
|
+
"border.success": "--color-success + --ring-color: --color-success",
|
|
231
|
+
"border.disabled": "opacity via --state-disabled-opacity",
|
|
232
|
+
"bg.default": "--color-surface (via --_scroll-bg)",
|
|
233
|
+
"bg.hover": "--color-surface-bright (subtle tint on hover)",
|
|
234
|
+
"bg.focus": "--color-surface-bright (tint on active field)",
|
|
235
|
+
"bg.readonly": "--color-surface-variant",
|
|
236
|
+
"bg.error": "color-mix(--color-danger 3%, --color-surface) — faint danger wash",
|
|
237
|
+
"bg.prefix-suffix": "--color-surface-variant (tinted grouping)",
|
|
238
|
+
"text.placeholder": "--color-text-muted",
|
|
239
|
+
"text.prefix-suffix": "--color-text-muted",
|
|
240
|
+
"text.counter": "--color-text-muted → --color-warning → --color-danger",
|
|
241
|
+
"text.helper": "--color-text-muted (--font-size-xs)",
|
|
242
|
+
"text.error-message": "--color-danger (--font-size-xs)",
|
|
243
|
+
"text.label-resting": "--color-text-muted",
|
|
244
|
+
"text.label-floating": "--color-primary (focus) / --color-text-muted (filled)",
|
|
245
|
+
"text.label-error": "--color-danger",
|
|
246
|
+
"text.label-success": "--color-success",
|
|
247
|
+
"radius": "--radius-md (via u-radius-md)",
|
|
248
|
+
"focus.ring": "ring() with --ring-offset: 0px — no double-border gap",
|
|
249
|
+
"disabled.opacity": "--state-disabled-opacity",
|
|
250
|
+
"font-size.sm": "--font-size-xs",
|
|
251
|
+
"font-size.md": "--font-size-sm",
|
|
252
|
+
"font-size.lg": "--font-size-body",
|
|
253
|
+
"font-size.label-floating": "--font-size-xs (scaled 0.85)",
|
|
254
|
+
"font-size.counter": "--font-size-xs (tabular-nums)",
|
|
255
|
+
"icon.size": "--_input-icon-size mapped per size (--icon-size-xs / --icon-size-sm / --icon-size-md)",
|
|
256
|
+
"icon.password-toggle": "--icon-size-md (always, interactive control needs readable detail)",
|
|
257
|
+
"icon.state-success": "--color-success (checkmark)",
|
|
258
|
+
"icon.state-error": "--color-danger (triangle alert)",
|
|
259
|
+
"gap.prefix-suffix": "--space-xs",
|
|
260
|
+
"gap.sub-text": "--space-xs (helper/error to input)",
|
|
261
|
+
"spinner.border": "--border-width-thin",
|
|
262
|
+
"caret.default": "--color-primary",
|
|
263
|
+
"caret.error": "--color-danger",
|
|
264
|
+
"caret.success": "--color-success",
|
|
265
|
+
"selection.default": "color-mix(--color-primary, --state-selected-opacity)",
|
|
266
|
+
"selection.error": "color-mix(--color-danger, --state-selected-opacity)",
|
|
267
|
+
"selection.success": "color-mix(--color-success, --state-selected-opacity)",
|
|
268
|
+
"spinner.animation": "--motion-animation-spin",
|
|
269
|
+
"spinner.adaptive": "--color-text-muted default, --color-danger on error, --color-success on success",
|
|
270
|
+
"required.indicator": "--color-danger asterisk on floating label via [data-required]",
|
|
271
|
+
"shake.animation": "ui-input-shake with --motion-distance-subtle",
|
|
272
|
+
"success-pop.animation": "--motion-animation-fade-scale-in on false→true transition",
|
|
273
|
+
"error-message.animation": "--motion-animation-fade-in on appear",
|
|
274
|
+
"helper-text.animation": "--motion-animation-fade-in on reappear (after error clears)"
|
|
275
|
+
},
|
|
276
|
+
"spacing": {
|
|
277
|
+
"internal": {
|
|
278
|
+
"prefix-to-input": { "relationship": "lovers", "token": "xs" },
|
|
279
|
+
"input-to-suffix": { "relationship": "lovers", "token": "xs" }
|
|
280
|
+
},
|
|
281
|
+
"external": {
|
|
282
|
+
"input-to-helper": { "relationship": "lovers", "token": "xs" },
|
|
283
|
+
"input-to-error": { "relationship": "lovers", "token": "xs" },
|
|
284
|
+
"label-to-input": { "relationship": "close_friends", "token": "sm" },
|
|
285
|
+
"field-to-field": { "relationship": "friends", "token": "md" }
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
"constraints": [
|
|
289
|
+
"Never use raw hex colors — use design system tokens",
|
|
290
|
+
"Use logical CSS properties only (margin-inline-start, not margin-left)",
|
|
291
|
+
"All transitions wrapped in @include a11y.motion-safe",
|
|
292
|
+
"Must handle forced-colors mode — add visible border for ButtonText",
|
|
293
|
+
"No validation logic — error/success props are visual only",
|
|
294
|
+
"Label prop is required — used as aria-label or floating label text",
|
|
295
|
+
"Uses native disabled (not aria-disabled) — disabled inputs don't need to stay focusable",
|
|
296
|
+
"Error takes precedence over success visually (CSS source order + state icon priority)",
|
|
297
|
+
"Floating label + prefix = DEV warning (position conflict)",
|
|
298
|
+
"Error shake skips initial render (firstRun guard)",
|
|
299
|
+
"Character counter is aria-hidden (decorative) with tabular-nums",
|
|
300
|
+
"Clear button is a tabbable type='button' — keyboard-accessible, participates in tab order",
|
|
301
|
+
"Password toggle is a tabbable type='button' — keyboard-accessible, participates in tab order",
|
|
302
|
+
"Loading hides suffix, clear button, and password toggle with aria-busy",
|
|
303
|
+
"Readonly suppresses focus styling (no ring, no border change, no bg tint) — label always floated",
|
|
304
|
+
"InputProps extends FormControlBaseProps — shared form control interface for Input, Select, Textarea",
|
|
305
|
+
"Helper text hidden when error message is visible (error replaces helper)",
|
|
306
|
+
"State icons auto-injected: checkmark for success (pop-in animation), triangle alert for error",
|
|
307
|
+
"Error message and helper text fade in using --motion-animation-fade-in",
|
|
308
|
+
"Success icon uses --motion-animation-fade-scale-in on false→true transition (skips initial render)",
|
|
309
|
+
"Icon sizes use --_input-icon-size custom prop mapped to --icon-size-* per control size"
|
|
310
|
+
],
|
|
311
|
+
"a11y": [
|
|
312
|
+
"aria-label from required label prop — set when floatingLabel is false",
|
|
313
|
+
"Visible <label for='...'> when floatingLabel is true — replaces aria-label",
|
|
314
|
+
"aria-invalid='true' when error prop is truthy — triggers red border + shake",
|
|
315
|
+
"aria-describedby dynamically updated — references error ID only when error is active, helper ID when present",
|
|
316
|
+
"Error message has role='alert' (assertive live region) for screen reader announcement",
|
|
317
|
+
"aria-required='true' when required prop is true",
|
|
318
|
+
"aria-busy='true' when loading is true",
|
|
319
|
+
"Native disabled attribute — removes from tab order (correct for inputs)",
|
|
320
|
+
"Focus ring via box-shadow ring() on wrapper :focus-within (--ring-offset: 0px)",
|
|
321
|
+
"Forced-colors: visible border, GrayText for disabled, Highlight outline on focus",
|
|
322
|
+
"Auto-generated id on <input> — enables FormField <label for='...'> association",
|
|
323
|
+
"Clicking wrapper focuses the inner input",
|
|
324
|
+
"Clear button has aria-label='Clear', type='button', and :focus-visible ring",
|
|
325
|
+
"Password toggle has aria-label='Show password'/'Hide password', aria-pressed toggle, type='button', and :focus-visible ring",
|
|
326
|
+
"Character counter has aria-hidden='true' (browser enforces maxLength)",
|
|
327
|
+
"State icons have aria-hidden='true' (decorative, state conveyed via border + aria-invalid)",
|
|
328
|
+
"Error shake respects prefers-reduced-motion"
|
|
329
|
+
],
|
|
330
|
+
"ai": {
|
|
331
|
+
"synonyms": [
|
|
332
|
+
"text field",
|
|
333
|
+
"text input",
|
|
334
|
+
"form field",
|
|
335
|
+
"text box",
|
|
336
|
+
"input field",
|
|
337
|
+
"form input"
|
|
338
|
+
],
|
|
339
|
+
"avoid": [
|
|
340
|
+
"div or span as input — use <Input> component",
|
|
341
|
+
"hardcoded border colors — use design tokens",
|
|
342
|
+
"built-in validation — validation belongs in Form/DynamicForm",
|
|
343
|
+
"visible label in Input without floatingLabel — use FormField molecule",
|
|
344
|
+
"outline: none without alternative — focus ring handled by ring()",
|
|
345
|
+
"manual eye icon for password — use built-in showPasswordToggle"
|
|
346
|
+
],
|
|
347
|
+
"patterns": [
|
|
348
|
+
"Floating label: <Input label=\"'Email'\" floatingLabel=\"true\" type=\"'email'\" />",
|
|
349
|
+
"Clearable: <Input label=\"'Search'\" clearable=\"true\" />",
|
|
350
|
+
"Character counter: <Input label=\"'Bio'\" maxLength=\"200\" />",
|
|
351
|
+
"Loading: <Input label=\"'Username'\" loading=\"isChecking\" />",
|
|
352
|
+
"Success state: <Input label=\"'Email'\" success=\"isValid\" />",
|
|
353
|
+
"Error state: <Input label=\"'Email'\" error=\"hasError\" />",
|
|
354
|
+
"Error with message: <Input label=\"'Email'\" error=\"hasError\" errorMessage=\"emailError\" />",
|
|
355
|
+
"Helper text: <Input label=\"'Email'\" helperText=\"'We will never share your email'\" />",
|
|
356
|
+
"Helper + error swap: <Input label=\"'Email'\" helperText=\"'Enter your email'\" error=\"hasError\" errorMessage=\"'Invalid email'\" />",
|
|
357
|
+
"Password: <Input label=\"'Password'\" type=\"'password'\" />",
|
|
358
|
+
"Password no toggle: <Input label=\"'API Key'\" type=\"'password'\" showPasswordToggle=\"false\" />",
|
|
359
|
+
"With prefix: <Input label=\"'Price'\" prefix=\"currencyPrefix\" type=\"'number'\" />",
|
|
360
|
+
"All features: <Input label=\"'Name'\" floatingLabel=\"true\" clearable=\"true\" maxLength=\"50\" helperText=\"'Your display name'\" />"
|
|
361
|
+
]
|
|
362
|
+
},
|
|
363
|
+
"examples": [
|
|
364
|
+
{
|
|
365
|
+
"id": "floating-label",
|
|
366
|
+
"title": "Floating label input",
|
|
367
|
+
"pithy": "<Input label=\"'Email Address'\" floatingLabel=\"true\" type=\"'email'\" />",
|
|
368
|
+
"assertions": [
|
|
369
|
+
"visible <label>",
|
|
370
|
+
"no aria-label",
|
|
371
|
+
"label floats on focus",
|
|
372
|
+
"no violations in axe-core"
|
|
373
|
+
]
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"id": "clearable",
|
|
377
|
+
"title": "Clearable input",
|
|
378
|
+
"pithy": "<Input label=\"'Search'\" clearable=\"true\" placeholder=\"'Search...'\" />",
|
|
379
|
+
"assertions": [
|
|
380
|
+
"clear button hidden when empty",
|
|
381
|
+
"clear button visible with value",
|
|
382
|
+
"clears on click"
|
|
383
|
+
]
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"id": "character-counter",
|
|
387
|
+
"title": "Input with character counter",
|
|
388
|
+
"pithy": "<Input label=\"'Bio'\" maxLength=\"200\" placeholder=\"'Tell us about yourself'\" />",
|
|
389
|
+
"assertions": [
|
|
390
|
+
"counter shows 0/200",
|
|
391
|
+
"warn color at 80%",
|
|
392
|
+
"danger at limit",
|
|
393
|
+
"tabular-nums"
|
|
394
|
+
]
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
"id": "loading",
|
|
398
|
+
"title": "Loading state",
|
|
399
|
+
"pithy": "<Input label=\"'Username'\" loading=\"isChecking\" />",
|
|
400
|
+
"assertions": [
|
|
401
|
+
"spinner visible",
|
|
402
|
+
"aria-busy='true'",
|
|
403
|
+
"suffix hidden",
|
|
404
|
+
"password toggle hidden"
|
|
405
|
+
]
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"id": "success",
|
|
409
|
+
"title": "Success state with checkmark",
|
|
410
|
+
"pithy": "<Input label=\"'Email'\" success=\"true\" value=\"'valid@email.com'\" />",
|
|
411
|
+
"assertions": [
|
|
412
|
+
"data-success on wrapper",
|
|
413
|
+
"green border",
|
|
414
|
+
"checkmark icon visible"
|
|
415
|
+
]
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"id": "error-shake",
|
|
419
|
+
"title": "Error with shake animation and alert icon",
|
|
420
|
+
"pithy": "<Input label=\"'Email'\" error=\"hasError\" />",
|
|
421
|
+
"assertions": [
|
|
422
|
+
"aria-invalid on error",
|
|
423
|
+
"shake animation on transition",
|
|
424
|
+
"no shake on initial render",
|
|
425
|
+
"alert triangle icon visible"
|
|
426
|
+
]
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
"id": "error-message",
|
|
430
|
+
"title": "Error with message",
|
|
431
|
+
"pithy": "<Input label=\"'Email'\" error=\"hasError\" errorMessage=\"'Invalid email'\" />",
|
|
432
|
+
"assertions": [
|
|
433
|
+
"error message visible when error=true",
|
|
434
|
+
"aria-describedby links to error",
|
|
435
|
+
"role='alert'"
|
|
436
|
+
]
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
"id": "helper-text",
|
|
440
|
+
"title": "Helper text with error swap",
|
|
441
|
+
"pithy": "<Input label=\"'Email'\" helperText=\"'We will never share your email'\" error=\"hasError\" errorMessage=\"'Invalid email'\" />",
|
|
442
|
+
"assertions": [
|
|
443
|
+
"helper visible by default",
|
|
444
|
+
"helper hidden when error active",
|
|
445
|
+
"error replaces helper"
|
|
446
|
+
]
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"id": "password-toggle",
|
|
450
|
+
"title": "Password with visibility toggle",
|
|
451
|
+
"pithy": "<Input label=\"'Password'\" type=\"'password'\" />",
|
|
452
|
+
"assertions": [
|
|
453
|
+
"eye icon visible",
|
|
454
|
+
"toggles input type on click",
|
|
455
|
+
"aria-label updates"
|
|
456
|
+
]
|
|
457
|
+
}
|
|
458
|
+
],
|
|
459
|
+
"designGuidance": {
|
|
460
|
+
"surface": "Transparent inner input on --color-surface wrapper. Hover brightens to --color-surface-bright. Focus also uses --color-surface-bright. Readonly uses --color-surface-variant. Prefix/suffix have tinted --color-surface-variant background.",
|
|
461
|
+
"depth": "No shadow by default. Focus ring via box-shadow ring() with --ring-offset: 0px (no double-border gap).",
|
|
462
|
+
"borders": "Stable border width — only border-color changes across states. Prefix/suffix have hairline separator borders.",
|
|
463
|
+
"interactivity": "Hover changes border color + subtle bg tint. Focus shows ring + bg tint. Error shakes on transition + shows alert icon. Success shows checkmark icon with pop-in bounce. Error message fades in. Helper text fades in when reappearing. Loading shows spinner. Clear button fades in when value present. Password toggle auto-appears for type='password'.",
|
|
464
|
+
"bestPractices": "Use floatingLabel for polished forms. Combine clearable + maxLength for best UX. Use loading for async validation feedback. Use helperText for persistent hints, errorMessage for validation feedback — error replaces helper automatically. Error/success are mutually exclusive (error wins). Password toggle is automatic — opt out only when needed (e.g., API key fields)."
|
|
465
|
+
}
|
|
466
|
+
}
|