@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,170 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "component",
|
|
3
|
+
"id": "ui.checkbox-group",
|
|
4
|
+
"name": "CheckboxGroup",
|
|
5
|
+
"displayName": "Checkbox Group",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"level": "atom",
|
|
8
|
+
"domain": "forms",
|
|
9
|
+
"bidi": true,
|
|
10
|
+
"description": "Accessible group wrapper for related <Checkbox> controls. Renders <fieldset><legend> with optional helper/error text and aria-describedby wiring. Native fieldset[disabled] cascades to all child checkboxes. Children are projected through the full PithyJS pipeline.",
|
|
11
|
+
"props": [
|
|
12
|
+
{
|
|
13
|
+
"name": "id",
|
|
14
|
+
"label": "ID",
|
|
15
|
+
"editor": { "tab": "settings" },
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Explicit fieldset ID — overrides auto-generated ID."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "label",
|
|
21
|
+
"label": "Label",
|
|
22
|
+
"type": "string",
|
|
23
|
+
"required": true,
|
|
24
|
+
"placeholder": "Pick your favorites",
|
|
25
|
+
"description": "Required group label rendered inside <legend>. Provides the accessible name for the group."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "hideLabel",
|
|
29
|
+
"label": "Hide label",
|
|
30
|
+
"type": "boolean",
|
|
31
|
+
"default": false,
|
|
32
|
+
"description": "Visually hide the legend (still announced by screen readers via .u-visually-hidden)."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "helperText",
|
|
36
|
+
"label": "Helper text",
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "Persistent helper text below the group. Wired via aria-describedby when no error is shown."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "errorMessage",
|
|
42
|
+
"label": "Error message",
|
|
43
|
+
"type": "string | Signal<string>",
|
|
44
|
+
"description": "Error message rendered in a span with role='alert'. Linked via aria-describedby when error is truthy. Accepts a signal accessor for reactive updates."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "error",
|
|
48
|
+
"label": "Error",
|
|
49
|
+
"type": "boolean | Signal<boolean>",
|
|
50
|
+
"default": false,
|
|
51
|
+
"description": "Visual error state — sets aria-invalid='true' and data-error on the fieldset. Error message only displays when both error and errorMessage are set. Accepts a signal accessor for reactive updates."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "disabled",
|
|
55
|
+
"label": "Disabled",
|
|
56
|
+
"type": "boolean | Signal<boolean>",
|
|
57
|
+
"default": false,
|
|
58
|
+
"description": "Disables the entire group via native fieldset[disabled], cascading to every child checkbox without extra plumbing. Accepts a signal accessor for reactive updates."
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"events": [],
|
|
62
|
+
"slots": [
|
|
63
|
+
{
|
|
64
|
+
"name": "default",
|
|
65
|
+
"displayName": "Checkboxes",
|
|
66
|
+
"description": "Children projected through renderChildren() — typically <Checkbox> elements but any markup is supported. The fieldset is marked as a site-builder drop zone.",
|
|
67
|
+
"accepts": ["ui.checkbox"]
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"tokens": {
|
|
71
|
+
"legend.color": "--color-text",
|
|
72
|
+
"helper.color": "--color-text-muted",
|
|
73
|
+
"error.color": "--color-danger",
|
|
74
|
+
"group.gap": "--space-sm"
|
|
75
|
+
},
|
|
76
|
+
"spacing": {
|
|
77
|
+
"internal": {
|
|
78
|
+
"legend-to-children": { "relationship": "close_friends", "token": "sm" },
|
|
79
|
+
"children-to-helper": { "relationship": "lovers", "token": "xs" },
|
|
80
|
+
"children-to-error": { "relationship": "lovers", "token": "xs" }
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"constraints": [
|
|
84
|
+
"Use semantic <fieldset>/<legend> — never a div with role='group'",
|
|
85
|
+
"label prop is required — every group must have an accessible name",
|
|
86
|
+
"Native fieldset[disabled] (not aria-disabled) — cascades automatically to children",
|
|
87
|
+
"Error message only shown when both error=true and errorMessage are set",
|
|
88
|
+
"Children must be Checkbox controls (or compose them) — group semantics assume checkbox descendants",
|
|
89
|
+
"data-slot='children' is required on the fieldset for site-builder drop targeting"
|
|
90
|
+
],
|
|
91
|
+
"a11y": [
|
|
92
|
+
"Native <fieldset><legend> — strongest semantic grouping for related form controls",
|
|
93
|
+
"label always rendered inside <legend> (visually hidden via hideLabel when needed)",
|
|
94
|
+
"aria-invalid='true' when error prop is truthy",
|
|
95
|
+
"aria-describedby points to helper id (no error) or error id (when shown)",
|
|
96
|
+
"Error message has role='alert' for live region announcement",
|
|
97
|
+
"fieldset[disabled] cascades native disabled state to all child controls",
|
|
98
|
+
"Auto-generated id enables external aria-describedby/labelling"
|
|
99
|
+
],
|
|
100
|
+
"ai": {
|
|
101
|
+
"synonyms": [
|
|
102
|
+
"checkbox set",
|
|
103
|
+
"checkbox list",
|
|
104
|
+
"multi-select group",
|
|
105
|
+
"fieldset",
|
|
106
|
+
"option group"
|
|
107
|
+
],
|
|
108
|
+
"avoid": [
|
|
109
|
+
"Wrapping a single Checkbox in CheckboxGroup — use Checkbox directly",
|
|
110
|
+
"Using FormField around individual Checkboxes — Checkbox already provides its own label; use CheckboxGroup for grouped controls",
|
|
111
|
+
"div with role='group' — use the native <fieldset> via <CheckboxGroup>",
|
|
112
|
+
"Omitting the label prop — the group must have an accessible name",
|
|
113
|
+
"Adding aria-disabled — use the native disabled prop instead"
|
|
114
|
+
],
|
|
115
|
+
"patterns": [
|
|
116
|
+
"Basic: <CheckboxGroup label=\"'Toppings'\"><Checkbox label=\"'Cheese'\" /><Checkbox label=\"'Pepperoni'\" /></CheckboxGroup>",
|
|
117
|
+
"With helper: <CheckboxGroup label=\"'Toppings'\" helperText=\"'Pick any number'\">…</CheckboxGroup>",
|
|
118
|
+
"With error: <CheckboxGroup label=\"'Toppings'\" error=\"hasError\" errorMessage=\"'Pick at least one'\">…</CheckboxGroup>",
|
|
119
|
+
"Disabled (cascades): <CheckboxGroup label=\"'Locked options'\" disabled=\"true\">…</CheckboxGroup>",
|
|
120
|
+
"Visually hidden legend: <CheckboxGroup label=\"'Filters'\" hideLabel=\"true\">…</CheckboxGroup>"
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
"examples": [
|
|
124
|
+
{
|
|
125
|
+
"id": "basic",
|
|
126
|
+
"title": "Basic checkbox group",
|
|
127
|
+
"pithy": "<CheckboxGroup label=\"'Toppings'\"><Checkbox label=\"'Cheese'\" name=\"'toppings'\" value=\"'cheese'\" /><Checkbox label=\"'Pepperoni'\" name=\"'toppings'\" value=\"'pepperoni'\" /></CheckboxGroup>",
|
|
128
|
+
"assertions": [
|
|
129
|
+
"fieldset > legend with label text",
|
|
130
|
+
"data-slot='children' on fieldset",
|
|
131
|
+
"no axe violations"
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"id": "helper",
|
|
136
|
+
"title": "Group with helper text",
|
|
137
|
+
"pithy": "<CheckboxGroup label=\"'Toppings'\" helperText=\"'Pick any number'\"><Checkbox label=\"'Cheese'\" /></CheckboxGroup>",
|
|
138
|
+
"assertions": ["helper rendered", "aria-describedby points to helper id"]
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"id": "error",
|
|
142
|
+
"title": "Group with error",
|
|
143
|
+
"pithy": "<CheckboxGroup label=\"'Toppings'\" error=\"true\" errorMessage=\"'Pick at least one'\"><Checkbox label=\"'Cheese'\" /></CheckboxGroup>",
|
|
144
|
+
"assertions": [
|
|
145
|
+
"aria-invalid='true' on fieldset",
|
|
146
|
+
"error span with role='alert' visible",
|
|
147
|
+
"aria-describedby points to error id"
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"id": "disabled-cascade",
|
|
152
|
+
"title": "Disabled group cascades to children",
|
|
153
|
+
"pithy": "<CheckboxGroup label=\"'Locked'\" disabled=\"true\"><Checkbox label=\"'Cheese'\" /></CheckboxGroup>",
|
|
154
|
+
"assertions": ["fieldset[disabled]", "child input.disabled === true"]
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"id": "hide-label",
|
|
158
|
+
"title": "Visually hidden legend",
|
|
159
|
+
"pithy": "<CheckboxGroup label=\"'Filters'\" hideLabel=\"true\"><Checkbox label=\"'On sale'\" /></CheckboxGroup>",
|
|
160
|
+
"assertions": ["legend present", "legend has u-visually-hidden class"]
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
"designGuidance": {
|
|
164
|
+
"surface": "No background by default — inherits the page surface. The group is a semantic container, not a visual card.",
|
|
165
|
+
"depth": "Flat — no shadow or elevation. Children carry their own focus state.",
|
|
166
|
+
"borders": "No border on the fieldset itself (UA defaults reset). Visual grouping comes from spacing, not chrome.",
|
|
167
|
+
"interactivity": "Stateless wrapper — interactivity lives on individual Checkbox children. fieldset[disabled] cascades native disabled to descendants without per-child wiring.",
|
|
168
|
+
"bestPractices": "Always pair related checkboxes inside a CheckboxGroup. Use a clear, concise legend label. Prefer native disabled over per-child disabling. Use hideLabel sparingly — only when surrounding context (e.g., a toolbar header) already provides the accessible name."
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "component",
|
|
3
|
+
"id": "ui.checkbox",
|
|
4
|
+
"name": "Checkbox",
|
|
5
|
+
"displayName": "Checkbox",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"level": "atom",
|
|
8
|
+
"domain": "forms",
|
|
9
|
+
"bidi": true,
|
|
10
|
+
"description": "Accessible checkbox atom with a custom-rendered box and inline SVG mark (animated checkmark + indeterminate dash via stroke-dashoffset), error/success states with shake animation, helper/error text, loading spinner, and full a11y wiring. No validation logic — states driven externally.",
|
|
11
|
+
"props": [
|
|
12
|
+
{
|
|
13
|
+
"name": "id",
|
|
14
|
+
"label": "ID",
|
|
15
|
+
"editor": { "tab": "settings" },
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Explicit input ID — overrides auto-generated ID."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "name",
|
|
21
|
+
"label": "Name",
|
|
22
|
+
"editor": { "tab": "settings" },
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Input name attribute for form submission."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "value",
|
|
28
|
+
"label": "Value",
|
|
29
|
+
"editor": { "tab": "settings" },
|
|
30
|
+
"type": "string",
|
|
31
|
+
"default": "on",
|
|
32
|
+
"description": "Submitted value when the checkbox is checked."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "checked",
|
|
36
|
+
"label": "Checked",
|
|
37
|
+
"editor": { "tab": "settings" },
|
|
38
|
+
"type": "boolean | Signal<boolean>",
|
|
39
|
+
"default": false,
|
|
40
|
+
"description": "Checked state — static or signal accessor for controlled checkbox."
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "indeterminate",
|
|
44
|
+
"label": "Indeterminate",
|
|
45
|
+
"editor": { "tab": "settings" },
|
|
46
|
+
"type": "boolean | Signal<boolean>",
|
|
47
|
+
"default": false,
|
|
48
|
+
"description": "Indeterminate state — visually shows a horizontal dash. Cleared automatically on user toggle."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "disabled",
|
|
52
|
+
"label": "Disabled",
|
|
53
|
+
"editor": { "tab": "settings" },
|
|
54
|
+
"type": "boolean | Signal<boolean>",
|
|
55
|
+
"default": false,
|
|
56
|
+
"description": "Disabled state — uses native disabled attribute."
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "readonly",
|
|
60
|
+
"label": "Read-only",
|
|
61
|
+
"editor": { "tab": "settings" },
|
|
62
|
+
"type": "boolean | Signal<boolean>",
|
|
63
|
+
"default": false,
|
|
64
|
+
"description": "Readonly state — focusable but not toggleable. Native input ignores readonly, so toggling is intercepted via click preventDefault, Space keydown preventDefault, and a change-event rollback to the controlled value."
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "required",
|
|
68
|
+
"label": "Required",
|
|
69
|
+
"type": "boolean",
|
|
70
|
+
"default": false,
|
|
71
|
+
"description": "Sets aria-required and adds a visual asterisk to the label."
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "error",
|
|
75
|
+
"label": "Error",
|
|
76
|
+
"editor": { "tab": "settings" },
|
|
77
|
+
"type": "boolean | Signal<boolean>",
|
|
78
|
+
"default": false,
|
|
79
|
+
"description": "Visual error state — sets aria-invalid, red box border, shake on transition."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "errorMessage",
|
|
83
|
+
"label": "Error message",
|
|
84
|
+
"editor": { "tab": "settings" },
|
|
85
|
+
"type": "string | Signal<string>",
|
|
86
|
+
"description": "Error message shown below the checkbox when error is truthy. Linked via aria-describedby with role='alert'."
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "helperText",
|
|
90
|
+
"label": "Helper text",
|
|
91
|
+
"type": "string",
|
|
92
|
+
"description": "Persistent hint text below the checkbox. Hidden when error is visible."
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"name": "success",
|
|
96
|
+
"label": "Success",
|
|
97
|
+
"editor": { "tab": "settings" },
|
|
98
|
+
"type": "boolean | Signal<boolean>",
|
|
99
|
+
"default": false,
|
|
100
|
+
"description": "Visual success state — green border / fill. Error takes precedence."
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "size",
|
|
104
|
+
"label": "Size",
|
|
105
|
+
"type": "enum",
|
|
106
|
+
"enum": ["sm", "md", "lg"],
|
|
107
|
+
"default": "md",
|
|
108
|
+
"description": "Control size — drives box dimensions and label font-size."
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "label",
|
|
112
|
+
"label": "Label",
|
|
113
|
+
"type": "string",
|
|
114
|
+
"required": true,
|
|
115
|
+
"placeholder": "Accept terms and conditions",
|
|
116
|
+
"description": "Visible label text rendered next to the box. Always required."
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "loading",
|
|
120
|
+
"label": "Loading",
|
|
121
|
+
"editor": { "tab": "settings" },
|
|
122
|
+
"type": "boolean | Signal<boolean>",
|
|
123
|
+
"default": false,
|
|
124
|
+
"description": "Loading state — overlays a spinner inside the box and sets aria-busy."
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "onChange",
|
|
128
|
+
"type": "(checked: boolean, e: Event) => void",
|
|
129
|
+
"description": "Fires whenever the checked state changes."
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"name": "onBlur",
|
|
133
|
+
"type": "(e: FocusEvent) => void",
|
|
134
|
+
"description": "Fires when the checkbox loses focus."
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "onFocus",
|
|
138
|
+
"type": "(e: FocusEvent) => void",
|
|
139
|
+
"description": "Fires when the checkbox gains focus."
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
"events": ["change", "blur", "focus"],
|
|
143
|
+
"slots": [],
|
|
144
|
+
"tokens": {
|
|
145
|
+
"box.size.sm": "calc(var(--font-size-xs) * 1.25)",
|
|
146
|
+
"box.size.md": "calc(var(--font-size-sm) * 1.25)",
|
|
147
|
+
"box.size.lg": "calc(var(--font-size-body) * 1.25)",
|
|
148
|
+
"box.bg.default": "--color-surface",
|
|
149
|
+
"box.bg.checked": "--color-primary",
|
|
150
|
+
"box.bg.checked.hover": "--color-primary-hover",
|
|
151
|
+
"box.bg.error": "--color-danger",
|
|
152
|
+
"box.bg.success": "--color-success",
|
|
153
|
+
"box.border.default": "border(control)",
|
|
154
|
+
"box.border.hover": "--color-primary",
|
|
155
|
+
"box.border.error": "--color-danger",
|
|
156
|
+
"box.border.success": "--color-success",
|
|
157
|
+
"box.radius": "--radius-sm",
|
|
158
|
+
"checkmark.color": "--color-on-primary",
|
|
159
|
+
"label.color": "--color-text",
|
|
160
|
+
"label.color.error": "--color-danger",
|
|
161
|
+
"focus.ring": "ring() with --ring-offset: 2px",
|
|
162
|
+
"disabled.opacity": "--state-disabled-opacity",
|
|
163
|
+
"required.indicator": "--color-danger asterisk",
|
|
164
|
+
"shake.animation": "ui-form-shake",
|
|
165
|
+
"touch-target": "a11y.touch-target-pseudo (--control-min-touch)",
|
|
166
|
+
"spinner.color": "--_cmp-spinner-color → --color-primary (default), --color-on-primary (checked/indeterminate), --color-danger (error), --color-success (success)"
|
|
167
|
+
},
|
|
168
|
+
"spacing": {
|
|
169
|
+
"internal": {
|
|
170
|
+
"box-to-label": { "relationship": "lovers", "token": "xs" }
|
|
171
|
+
},
|
|
172
|
+
"external": {
|
|
173
|
+
"wrapper-to-helper": { "relationship": "lovers", "token": "xs" },
|
|
174
|
+
"wrapper-to-error": { "relationship": "lovers", "token": "xs" },
|
|
175
|
+
"checkbox-to-checkbox": { "relationship": "close_friends", "token": "sm" }
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
"constraints": [
|
|
179
|
+
"Never use raw hex colors — use design system tokens",
|
|
180
|
+
"Use logical CSS properties only",
|
|
181
|
+
"All transitions wrapped in @include a11y.motion-safe",
|
|
182
|
+
"Must handle forced-colors mode — Highlight for checked, GrayText for disabled",
|
|
183
|
+
"No validation logic — error/success props are visual only",
|
|
184
|
+
"Label prop is required",
|
|
185
|
+
"Native disabled (not aria-disabled)",
|
|
186
|
+
"Error takes precedence over success visually",
|
|
187
|
+
"Error shake skips initial render (firstRun guard)",
|
|
188
|
+
"Indeterminate is mutually exclusive with checked (checked wins)",
|
|
189
|
+
"User toggle clears indeterminate (UA convention)",
|
|
190
|
+
"Readonly is custom — native checkboxes ignore the readonly attribute. Toggling is blocked via click preventDefault, Space keydown preventDefault, and a change-event rollback that restores the controlled value if a stray event slips through",
|
|
191
|
+
"Checkmark + indeterminate dash rendered as inline SVG paths animated via stroke-dashoffset (pathLength=1)",
|
|
192
|
+
"Native input is visually hidden but stays focusable; visual focus ring is on .ui-checkbox-box via :has(:focus-visible)",
|
|
193
|
+
"CheckboxProps extends FormControlBaseProps"
|
|
194
|
+
],
|
|
195
|
+
"a11y": [
|
|
196
|
+
"Native <input type='checkbox'> — full keyboard support (Space toggles)",
|
|
197
|
+
"Wrapper is a <label> — clicking anywhere toggles the input",
|
|
198
|
+
"aria-invalid='true' when error prop is truthy",
|
|
199
|
+
"aria-describedby dynamically updated for helper/error",
|
|
200
|
+
"Error message has role='alert'",
|
|
201
|
+
"aria-required='true' when required",
|
|
202
|
+
"aria-readonly='true' when readonly (mirrors custom intercept)",
|
|
203
|
+
"aria-busy='true' when loading",
|
|
204
|
+
"Auto-generated id enables FormField association",
|
|
205
|
+
"Focus ring on the visible box via :has(:focus-visible)",
|
|
206
|
+
"Forced-colors: Highlight for checked, GrayText for disabled, Highlight outline on focus",
|
|
207
|
+
"Custom box has aria-hidden='true' (decorative — input is the real control)",
|
|
208
|
+
"Error shake respects prefers-reduced-motion"
|
|
209
|
+
],
|
|
210
|
+
"ai": {
|
|
211
|
+
"synonyms": [
|
|
212
|
+
"check box",
|
|
213
|
+
"tick box",
|
|
214
|
+
"toggle option",
|
|
215
|
+
"boolean input",
|
|
216
|
+
"multi-select option"
|
|
217
|
+
],
|
|
218
|
+
"avoid": [
|
|
219
|
+
"div with role='checkbox' — use the native input via <Checkbox>",
|
|
220
|
+
"raw color values for box / checkmark — use design tokens",
|
|
221
|
+
"validation inside the component — belongs in Form/DynamicForm",
|
|
222
|
+
"Checkbox without a label prop"
|
|
223
|
+
],
|
|
224
|
+
"patterns": [
|
|
225
|
+
"Basic: <Checkbox label=\"'Accept terms'\" />",
|
|
226
|
+
"Required: <Checkbox label=\"'I agree'\" required=\"true\" />",
|
|
227
|
+
"Controlled: <Checkbox label=\"'Subscribe'\" checked=\"isSubscribed\" onChange=\"toggle\" />",
|
|
228
|
+
"Indeterminate (select all): <Checkbox label=\"'Select all'\" indeterminate=\"someSelected\" checked=\"allSelected\" />",
|
|
229
|
+
"Helper text: <Checkbox label=\"'Marketing emails'\" helperText=\"'You can unsubscribe at any time'\" />",
|
|
230
|
+
"Error: <Checkbox label=\"'Accept terms'\" error=\"hasError\" errorMessage=\"'You must accept the terms'\" />",
|
|
231
|
+
"Group: <CheckboxGroup label=\"'Toppings'\"><Checkbox label=\"'Cheese'\" /><Checkbox label=\"'Pepperoni'\" /></CheckboxGroup>"
|
|
232
|
+
]
|
|
233
|
+
},
|
|
234
|
+
"examples": [
|
|
235
|
+
{
|
|
236
|
+
"id": "basic",
|
|
237
|
+
"title": "Basic checkbox",
|
|
238
|
+
"pithy": "<Checkbox label=\"'Accept terms'\" />",
|
|
239
|
+
"assertions": [
|
|
240
|
+
"renders <input type='checkbox'>",
|
|
241
|
+
"label is visible",
|
|
242
|
+
"no axe violations"
|
|
243
|
+
]
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"id": "checked",
|
|
247
|
+
"title": "Pre-checked checkbox",
|
|
248
|
+
"pithy": "<Checkbox label=\"'Subscribe'\" checked=\"true\" />",
|
|
249
|
+
"assertions": ["input.checked === true", "data-checked on wrapper"]
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"id": "indeterminate",
|
|
253
|
+
"title": "Indeterminate (select-all) checkbox",
|
|
254
|
+
"pithy": "<Checkbox label=\"'Select all'\" indeterminate=\"true\" />",
|
|
255
|
+
"assertions": [
|
|
256
|
+
"input.indeterminate === true",
|
|
257
|
+
"data-indeterminate on wrapper",
|
|
258
|
+
"dash visible"
|
|
259
|
+
]
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"id": "disabled",
|
|
263
|
+
"title": "Disabled checkbox",
|
|
264
|
+
"pithy": "<Checkbox label=\"'Locked'\" disabled=\"true\" />",
|
|
265
|
+
"assertions": ["input.disabled === true", "data-disabled on wrapper"]
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"id": "readonly",
|
|
269
|
+
"title": "Readonly checkbox",
|
|
270
|
+
"pithy": "<Checkbox label=\"'Auto-set'\" readonly=\"true\" checked=\"true\" />",
|
|
271
|
+
"assertions": [
|
|
272
|
+
"click does not toggle",
|
|
273
|
+
"Space does not toggle",
|
|
274
|
+
"remains focusable"
|
|
275
|
+
]
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"id": "required",
|
|
279
|
+
"title": "Required checkbox",
|
|
280
|
+
"pithy": "<Checkbox label=\"'I agree'\" required=\"true\" />",
|
|
281
|
+
"assertions": ["aria-required on input", "asterisk visible on label"]
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"id": "error",
|
|
285
|
+
"title": "Error with shake",
|
|
286
|
+
"pithy": "<Checkbox label=\"'Accept'\" error=\"hasError\" errorMessage=\"'Required'\" />",
|
|
287
|
+
"assertions": [
|
|
288
|
+
"aria-invalid on input",
|
|
289
|
+
"shake animation on transition",
|
|
290
|
+
"error message shown"
|
|
291
|
+
]
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
"id": "success",
|
|
295
|
+
"title": "Success state",
|
|
296
|
+
"pithy": "<Checkbox label=\"'Verified'\" success=\"true\" checked=\"true\" />",
|
|
297
|
+
"assertions": ["data-success on wrapper", "green box fill"]
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"id": "loading",
|
|
301
|
+
"title": "Loading state",
|
|
302
|
+
"pithy": "<Checkbox label=\"'Saving'\" loading=\"isSaving\" />",
|
|
303
|
+
"assertions": ["spinner visible", "aria-busy='true'"]
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"id": "group",
|
|
307
|
+
"title": "Checkbox group with fieldset",
|
|
308
|
+
"pithy": "<CheckboxGroup label=\"'Toppings'\"><Checkbox label=\"'Cheese'\" name=\"'toppings'\" value=\"'cheese'\" /></CheckboxGroup>",
|
|
309
|
+
"assertions": [
|
|
310
|
+
"fieldset contains legend",
|
|
311
|
+
"data-slot='children' on fieldset",
|
|
312
|
+
"fieldset[disabled] cascades"
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
],
|
|
316
|
+
"designGuidance": {
|
|
317
|
+
"surface": "Box uses --color-surface unfilled, switching to --color-primary when checked or indeterminate. Hover deepens to --color-primary-hover.",
|
|
318
|
+
"depth": "No shadow by default. Focus ring via box-shadow ring() with 2px offset on the visible box.",
|
|
319
|
+
"borders": "Stable border width — only color changes across states.",
|
|
320
|
+
"interactivity": "Hover tints border. Active applies pressed state-background. Focus shows ring on the box. Error shakes on transition. Indeterminate replaces checkmark with a dash; user toggle clears it. Loading overlays spinner inside the box.",
|
|
321
|
+
"bestPractices": "Always provide a label. Use indeterminate for select-all summaries. Pair related checkboxes inside a <CheckboxGroup> with a legend. Use disabled (not readonly) when the value cannot change at all; use readonly when the value is meaningful but locked."
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "component",
|
|
3
|
+
"id": "ui.file-picker",
|
|
4
|
+
"name": "FilePicker",
|
|
5
|
+
"displayName": "File Picker",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"level": "atom",
|
|
8
|
+
"domain": "forms",
|
|
9
|
+
"bidi": true,
|
|
10
|
+
"description": "Accessible file upload control with a click/drag-and-drop zone. Built on a semantic <label> wrapping a real focusable native <input type='file'> (no role='button'), validates type against `accept` and size against `maxSizeBytes`, shows an optional image preview, and surfaces inline errors via aria-live. No upload logic — the consumer wires `onSelect`.",
|
|
11
|
+
"props": [
|
|
12
|
+
{
|
|
13
|
+
"name": "accept",
|
|
14
|
+
"label": "Accept",
|
|
15
|
+
"editor": { "tab": "settings" },
|
|
16
|
+
"type": "string",
|
|
17
|
+
"default": "image/*",
|
|
18
|
+
"placeholder": "image/*",
|
|
19
|
+
"description": "Accept filter — forwarded to the input and used for type validation. Supports MIME types ('image/png'), wildcards ('image/*'), and extensions ('.webp'), comma-separated."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "maxSizeBytes",
|
|
23
|
+
"label": "Max size (bytes)",
|
|
24
|
+
"editor": { "tab": "settings" },
|
|
25
|
+
"type": "number",
|
|
26
|
+
"default": 5242880,
|
|
27
|
+
"description": "Maximum accepted file size in bytes. Files over the limit are rejected with an inline error (default 5 MB)."
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "showPreview",
|
|
31
|
+
"label": "Show preview",
|
|
32
|
+
"editor": { "tab": "settings" },
|
|
33
|
+
"type": "boolean",
|
|
34
|
+
"default": true,
|
|
35
|
+
"description": "Render an image preview of the selected file. Image files only; ignored for other types."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "hint",
|
|
39
|
+
"label": "Hint",
|
|
40
|
+
"type": "string",
|
|
41
|
+
"default": "Click or drag a file here",
|
|
42
|
+
"placeholder": "Click or drag a file here",
|
|
43
|
+
"description": "Instructional text shown inside the drop zone before a file is selected."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "ariaLabel",
|
|
47
|
+
"label": "Accessible label",
|
|
48
|
+
"editor": { "tab": "settings" },
|
|
49
|
+
"type": "string",
|
|
50
|
+
"default": "Upload file — click or drag and drop",
|
|
51
|
+
"description": "Accessible name applied to the native input."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "onSelect",
|
|
55
|
+
"type": "(file: File) => void | Promise<void>",
|
|
56
|
+
"required": true,
|
|
57
|
+
"description": "Called with the selected file once it passes type/size validation. May be async (e.g. an upload); a rejection surfaces an inline 'Upload failed' error, guarded so only the latest selection can set it."
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"events": ["change", "dragover", "dragenter", "dragleave", "drop"],
|
|
61
|
+
"slots": [],
|
|
62
|
+
"tokens": {
|
|
63
|
+
"zone.bg": "--color-surface-variant",
|
|
64
|
+
"zone.bg.hover": "--color-surface-hover",
|
|
65
|
+
"zone.border": "border-width-thin + border-style-dashed + --color-border",
|
|
66
|
+
"zone.border.hover": "--color-primary",
|
|
67
|
+
"zone.radius": "--radius-md",
|
|
68
|
+
"zone.padding": "--space-md (block) / --space-sm (inline)",
|
|
69
|
+
"hint.color": "--color-text-muted",
|
|
70
|
+
"hint.font-size": "--font-size-sm",
|
|
71
|
+
"error.color": "--color-danger",
|
|
72
|
+
"error.font-size": "--font-size-sm",
|
|
73
|
+
"focus.ring": "a11y.focus-ring via :has(.ui-file-picker-input:focus-visible)",
|
|
74
|
+
"preview.max-block": "calc(--control-min-height-md * 3) — three control-rows tall; scales with the a11y control-size preset"
|
|
75
|
+
},
|
|
76
|
+
"spacing": {
|
|
77
|
+
"internal": {
|
|
78
|
+
"zone-to-error": { "relationship": "lovers", "token": "xs" }
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"constraints": [
|
|
82
|
+
"Never use raw hex colors — use design system tokens",
|
|
83
|
+
"Use logical CSS properties only",
|
|
84
|
+
"All transitions wrapped in @include a11y.motion-safe",
|
|
85
|
+
"Must handle forced-colors mode — ButtonText border on the drop zone",
|
|
86
|
+
"Drop zone is a <label> (no role='button') wrapping a real focusable <input> — no re-implemented button semantics",
|
|
87
|
+
"Native <input> is the focusable control — never aria-hidden or inert while focusable",
|
|
88
|
+
"No upload logic — onSelect is wired by the consumer",
|
|
89
|
+
"Object-URL previews are revoked on replace and teardown",
|
|
90
|
+
"Async error is guarded by a selection token so a superseded file cannot clobber the current state"
|
|
91
|
+
],
|
|
92
|
+
"a11y": [
|
|
93
|
+
"Semantic <label> wraps a native <input type='file'> — clicking the zone opens the file dialog natively",
|
|
94
|
+
"Enter/Space on the focused input opens the dialog natively (no custom key handling)",
|
|
95
|
+
"Input carries the accessible name via aria-label",
|
|
96
|
+
"Focus ring on the visible zone via :has(.ui-file-picker-input:focus-visible)",
|
|
97
|
+
"Inline error uses aria-live='assertive' for immediate announcement",
|
|
98
|
+
"Forced-colors: solid ButtonText border on the drop zone"
|
|
99
|
+
],
|
|
100
|
+
"ai": {
|
|
101
|
+
"synonyms": [
|
|
102
|
+
"file upload",
|
|
103
|
+
"image upload",
|
|
104
|
+
"drop zone",
|
|
105
|
+
"dropzone",
|
|
106
|
+
"file input",
|
|
107
|
+
"upload field",
|
|
108
|
+
"attach file",
|
|
109
|
+
"image picker"
|
|
110
|
+
],
|
|
111
|
+
"avoid": [
|
|
112
|
+
"div with role='button' for the drop zone — use a <label> wrapping the input",
|
|
113
|
+
"aria-hidden or inert on the focusable file input",
|
|
114
|
+
"raw color values — use design tokens",
|
|
115
|
+
"upload/persistence logic inside the component — wire it via onSelect"
|
|
116
|
+
],
|
|
117
|
+
"patterns": [
|
|
118
|
+
"Basic: <FilePicker onSelect=\"handleFile\" />",
|
|
119
|
+
"Restricted type + size: <FilePicker onSelect=\"handleFile\" accept=\"'image/png,.webp'\" maxSizeBytes=\"1048576\" />",
|
|
120
|
+
"No preview: <FilePicker onSelect=\"handleFile\" showPreview=\"false\" />",
|
|
121
|
+
"Custom copy: <FilePicker onSelect=\"handleFile\" hint=\"'Click or drag image here'\" ariaLabel=\"'Upload image'\" />"
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
"examples": [
|
|
125
|
+
{
|
|
126
|
+
"id": "basic",
|
|
127
|
+
"title": "Basic file picker",
|
|
128
|
+
"pithy": "<FilePicker onSelect=\"handleFile\" />",
|
|
129
|
+
"assertions": [
|
|
130
|
+
"renders a <label> drop zone wrapping <input type='file'>",
|
|
131
|
+
"input has no inert/aria-hidden",
|
|
132
|
+
"no axe violations"
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"id": "accept",
|
|
137
|
+
"title": "Restricted accept filter",
|
|
138
|
+
"pithy": "<FilePicker onSelect=\"handleFile\" accept=\"'image/png,.webp'\" />",
|
|
139
|
+
"assertions": [
|
|
140
|
+
"input accept attribute is 'image/png,.webp'",
|
|
141
|
+
"non-matching files are rejected with an inline error"
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"id": "max-size",
|
|
146
|
+
"title": "Size limit",
|
|
147
|
+
"pithy": "<FilePicker onSelect=\"handleFile\" maxSizeBytes=\"4\" />",
|
|
148
|
+
"assertions": [
|
|
149
|
+
"a file over the limit is rejected",
|
|
150
|
+
"error text mentions the maximum size"
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
"designGuidance": {
|
|
155
|
+
"surface": "Drop zone sits on --color-surface-variant with a dashed --color-border; hover deepens to --color-surface-hover and the border tints to --color-primary.",
|
|
156
|
+
"depth": "Flat by design — a dashed outline communicates the drop affordance without elevation. Focus shows the ring on the zone.",
|
|
157
|
+
"borders": "Dashed thin border signals 'drop here'; only color changes on hover/drag-over. Radius --radius-md.",
|
|
158
|
+
"interactivity": "Click or keyboard activates the native dialog; dragging a file over highlights the zone (drag-over state); a selected image renders a capped preview inside the zone. Invalid type/size or a failed async onSelect shows an inline aria-live error.",
|
|
159
|
+
"bestPractices": "Use for a single-file upload field. Constrain with `accept` and `maxSizeBytes`. Keep `showPreview` on for images so users confirm their selection. Wire `onSelect` to your upload/asset flow — the component never persists files itself."
|
|
160
|
+
}
|
|
161
|
+
}
|