@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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/dist/specs/atoms/actions/button.pidl.json +289 -0
  3. package/dist/specs/atoms/actions/icon-group.pidl.json +141 -0
  4. package/dist/specs/atoms/actions/segmented-control.pidl.json +137 -0
  5. package/dist/specs/atoms/content/action-tile.pidl.json +135 -0
  6. package/dist/specs/atoms/content/badge.pidl.json +189 -0
  7. package/dist/specs/atoms/content/callout.pidl.json +170 -0
  8. package/dist/specs/atoms/content/code-block.pidl.json +170 -0
  9. package/dist/specs/atoms/content/heading.pidl.json +175 -0
  10. package/dist/specs/atoms/content/icon.pidl.json +127 -0
  11. package/dist/specs/atoms/content/image.pidl.json +227 -0
  12. package/dist/specs/atoms/content/prose.pidl.json +183 -0
  13. package/dist/specs/atoms/content/stat.pidl.json +158 -0
  14. package/dist/specs/atoms/content/tabs.pidl.json +207 -0
  15. package/dist/specs/atoms/content/text.pidl.json +212 -0
  16. package/dist/specs/atoms/content/tile.pidl.json +156 -0
  17. package/dist/specs/atoms/data/donut.pidl.json +352 -0
  18. package/dist/specs/atoms/data/gauge.pidl.json +274 -0
  19. package/dist/specs/atoms/forms/checkbox-group.pidl.json +170 -0
  20. package/dist/specs/atoms/forms/checkbox.pidl.json +323 -0
  21. package/dist/specs/atoms/forms/file-picker.pidl.json +161 -0
  22. package/dist/specs/atoms/forms/form-field.pidl.json +106 -0
  23. package/dist/specs/atoms/forms/input.pidl.json +466 -0
  24. package/dist/specs/atoms/forms/radio-group.pidl.json +174 -0
  25. package/dist/specs/atoms/forms/radio.pidl.json +312 -0
  26. package/dist/specs/atoms/forms/search-input.pidl.json +265 -0
  27. package/dist/specs/atoms/forms/select.pidl.json +420 -0
  28. package/dist/specs/atoms/forms/textarea.pidl.json +422 -0
  29. package/dist/specs/atoms/forms/toggle.pidl.json +314 -0
  30. package/dist/specs/layout/grid-item.pidl.json +120 -0
  31. package/dist/specs/layout/grid.pidl.json +157 -0
  32. package/dist/specs/layout/page-section.pidl.json +105 -0
  33. package/dist/specs/layout/smart-container.pidl.json +148 -0
  34. package/dist/specs/layout/stack.pidl.json +144 -0
  35. package/dist/specs/organisms/content/accordion.pidl.json +196 -0
  36. package/dist/specs/organisms/content/card.pidl.json +283 -0
  37. package/dist/specs/organisms/feedback/alert-dialog.pidl.json +158 -0
  38. package/dist/specs/organisms/feedback/modal.pidl.json +133 -0
  39. package/dist/specs/organisms/feedback/toast.pidl.json +150 -0
  40. package/dist/specs/organisms/forms/dynamic-form.pidl.json +314 -0
  41. package/dist/specs/organisms/forms/form.pidl.json +135 -0
  42. package/dist/specs/organisms/forms/subscribe-form.pidl.json +188 -0
  43. package/dist/specs/organisms/navigation/command-palette.pidl.json +263 -0
  44. package/dist/specs/organisms/navigation/docs-sidebar.pidl.json +264 -0
  45. package/dist/specs/organisms/navigation/navbar.pidl.json +356 -0
  46. package/dist/specs/organisms/navigation/table-of-contents.pidl.json +197 -0
  47. package/dist/specs/sections/contact-section.pidl.json +183 -0
  48. package/dist/specs/sections/cta-section.pidl.json +168 -0
  49. package/dist/specs/sections/faq-section.pidl.json +202 -0
  50. package/dist/specs/sections/features-section.pidl.json +214 -0
  51. package/dist/specs/sections/footer-section.pidl.json +187 -0
  52. package/dist/specs/sections/hero-section.pidl.json +202 -0
  53. package/dist/src/borders-psychology.d.ts +17 -0
  54. package/dist/src/borders-psychology.d.ts.map +1 -0
  55. package/dist/src/borders-psychology.js +42 -0
  56. package/dist/src/borders-psychology.js.map +1 -0
  57. package/dist/src/color-psychology.d.ts +20 -0
  58. package/dist/src/color-psychology.d.ts.map +1 -0
  59. package/dist/src/color-psychology.js +57 -0
  60. package/dist/src/color-psychology.js.map +1 -0
  61. package/dist/src/design-intelligence.d.ts +13 -0
  62. package/dist/src/design-intelligence.d.ts.map +1 -0
  63. package/dist/src/design-intelligence.js +36 -0
  64. package/dist/src/design-intelligence.js.map +1 -0
  65. package/dist/src/index.d.ts +6238 -0
  66. package/dist/src/index.d.ts.map +1 -0
  67. package/dist/src/index.js +105 -0
  68. package/dist/src/index.js.map +1 -0
  69. package/dist/src/motion-psychology.d.ts +19 -0
  70. package/dist/src/motion-psychology.d.ts.map +1 -0
  71. package/dist/src/motion-psychology.js +47 -0
  72. package/dist/src/motion-psychology.js.map +1 -0
  73. package/dist/src/mutation-vocabulary.d.ts +13 -0
  74. package/dist/src/mutation-vocabulary.d.ts.map +1 -0
  75. package/dist/src/mutation-vocabulary.js +67 -0
  76. package/dist/src/mutation-vocabulary.js.map +1 -0
  77. package/dist/src/radius-psychology.d.ts +17 -0
  78. package/dist/src/radius-psychology.d.ts.map +1 -0
  79. package/dist/src/radius-psychology.js +42 -0
  80. package/dist/src/radius-psychology.js.map +1 -0
  81. package/dist/src/shadows-psychology.d.ts +17 -0
  82. package/dist/src/shadows-psychology.d.ts.map +1 -0
  83. package/dist/src/shadows-psychology.js +44 -0
  84. package/dist/src/shadows-psychology.js.map +1 -0
  85. package/dist/src/spacing-psychology.d.ts +22 -0
  86. package/dist/src/spacing-psychology.d.ts.map +1 -0
  87. package/dist/src/spacing-psychology.js +59 -0
  88. package/dist/src/spacing-psychology.js.map +1 -0
  89. package/dist/src/typography-psychology.d.ts +22 -0
  90. package/dist/src/typography-psychology.d.ts.map +1 -0
  91. package/dist/src/typography-psychology.js +68 -0
  92. package/dist/src/typography-psychology.js.map +1 -0
  93. package/package.json +64 -0
@@ -0,0 +1,207 @@
1
+ {
2
+ "kind": "component",
3
+ "id": "ui.tabs",
4
+ "name": "Tabs",
5
+ "displayName": "Tabs",
6
+ "version": "0.1.0",
7
+ "level": "atom",
8
+ "domain": "content",
9
+ "bidi": false,
10
+ "description": "Accessible tabbed interface implementing the WAI-ARIA tablist pattern. Renders a role=tablist of role=tab buttons over eagerly-mounted role=tabpanel panels toggled via [hidden]. Roving tabindex with arrow-key navigation (Home/End, wrap), horizontal or vertical orientation, and automatic or manual activation.",
11
+ "props": [
12
+ {
13
+ "name": "items",
14
+ "label": "Tabs",
15
+ "type": "array",
16
+ "required": true,
17
+ "placeholder": [
18
+ {
19
+ "id": "overview",
20
+ "label": "Overview",
21
+ "content": "Overview panel content."
22
+ },
23
+ {
24
+ "id": "details",
25
+ "label": "Details",
26
+ "content": "Details panel content."
27
+ }
28
+ ],
29
+ "description": "Tabs in display order. Each item is { id, label, content, icon?, disabled? }.",
30
+ "itemSchema": {
31
+ "id": {
32
+ "type": "string",
33
+ "required": true,
34
+ "description": "Stable identifier — the selected value and the onChange key. Not used for DOM/ARIA ids (those are index-based), so it may contain any characters."
35
+ },
36
+ "label": {
37
+ "type": "string",
38
+ "required": true,
39
+ "description": "Tab button label"
40
+ },
41
+ "content": {
42
+ "type": "string",
43
+ "required": true,
44
+ "description": "Panel content, authored as text in the site builder. The component's TypeScript API additionally accepts a Node or a { el, destroy } ui-kit component."
45
+ },
46
+ "icon": {
47
+ "type": "icon",
48
+ "required": false,
49
+ "description": "Optional leading icon name (resolved via the built-in icon fallback)"
50
+ },
51
+ "disabled": {
52
+ "type": "boolean",
53
+ "required": false,
54
+ "description": "Skip this tab in keyboard nav and make it non-selectable"
55
+ }
56
+ }
57
+ },
58
+ {
59
+ "name": "defaultTab",
60
+ "label": "Default tab",
61
+ "type": "string",
62
+ "required": false,
63
+ "description": "Id of the initially-selected tab. Falls back to the first enabled tab when omitted or when it names a disabled/unknown tab."
64
+ },
65
+ {
66
+ "name": "orientation",
67
+ "label": "Orientation",
68
+ "type": "enum",
69
+ "enum": ["horizontal", "vertical"],
70
+ "default": "horizontal",
71
+ "description": "Layout axis of the tablist. Sets aria-orientation and the arrow-key axis (Left/Right vs Up/Down)."
72
+ },
73
+ {
74
+ "name": "fitted",
75
+ "label": "Full width",
76
+ "type": "boolean",
77
+ "required": false,
78
+ "default": false,
79
+ "editor": {
80
+ "visibleWhen": { "prop": "orientation", "equals": "horizontal" }
81
+ },
82
+ "description": "Stretch tabs to fill the tablist width in equal shares. Horizontal only — ignored (and dev-warned) for vertical orientation."
83
+ },
84
+ {
85
+ "name": "activation",
86
+ "label": "Activation",
87
+ "type": "enum",
88
+ "enum": ["automatic", "manual"],
89
+ "default": "automatic",
90
+ "description": "Whether the selected tab follows keyboard focus. 'automatic' selects on arrow-key focus; 'manual' moves focus only and selects on Enter/Space — preferred when switching tabs is expensive."
91
+ },
92
+ {
93
+ "name": "ariaLabel",
94
+ "label": "ARIA label",
95
+ "editor": { "tab": "settings" },
96
+ "type": "string",
97
+ "required": false,
98
+ "description": "Accessible name for the tablist. Recommended so screen-reader users can distinguish multiple tablists on a page."
99
+ },
100
+ {
101
+ "name": "onChange",
102
+ "label": "On change",
103
+ "editor": { "tab": "settings" },
104
+ "type": "(id: string, index: number) => void",
105
+ "required": false,
106
+ "description": "Called when the active tab changes (not on the initial render)."
107
+ }
108
+ ],
109
+ "tokens": {
110
+ "color": "--color-text-muted (idle tab), --color-text (hover), --color-primary (active tab + indicator), --color-divider (tablist track)",
111
+ "spacing": "--space-xs (tab gap, icon↔label), --space-md (tablist↔panels), --control-padding-x-sm / --control-padding-y-sm (tab padding)",
112
+ "radius": "--radius-sm (focus-ring rounding)",
113
+ "size": "--font-size-sm (tab label)",
114
+ "border": "--border-width-medium (track shadow + horizontal indicator), --border-width-thick (vertical accent bar)",
115
+ "motion": "--motion-transition-color (tab color), --motion-duration-normal (indicator slide)"
116
+ },
117
+ "spacing": {
118
+ "tab-gap": "--space-xs (close friends — tabs in one list)",
119
+ "icon-label-gap": "--space-xs (lovers — icon beside its label)",
120
+ "list-panels-gap": "--space-md (friends — tablist above its panels)"
121
+ },
122
+ "constraints": [
123
+ "items is required — an empty list renders an empty tablist",
124
+ "Panels mount eagerly and toggle [hidden], so per-panel state survives tab switches",
125
+ "The active panel is focusable (tabindex=0) per APG guidance for panels without a focusable child",
126
+ "Disabled tabs are skipped by arrow-key navigation and cannot be selected (by keyboard or click)",
127
+ "String content is inserted as text (textContent), never HTML — avoids injection and stray {{ }} interpolation",
128
+ "Keyboard model is delegated to createRovingTabindex from @pithyjs/core — arrows honour orientation, Home/End jump, navigation wraps",
129
+ "Tab labels compose <Text as='span'> — no raw styled spans",
130
+ "A single accent indicator glides to the active tab: JS measures the tab and sets the indicator transform/size; the glide is enabled only after the first static placement (no slide-in on mount) and is repositioned by a ResizeObserver on layout/viewport changes",
131
+ "Active state also reads via a pill fill (horizontal: primary tint; vertical: selected-state overlay + accent bar) and heavier label weight",
132
+ "Horizontal tablists scroll inline on overflow; the indicator lives inside the scroller so its position stays correct. Fitted tabs fill equal shares instead",
133
+ "Tabs are 44px min touch targets"
134
+ ],
135
+ "a11y": [
136
+ "role=tablist with aria-orientation; each tab is role=tab with aria-selected and aria-controls its panel",
137
+ "Each panel is role=tabpanel, aria-labelledby its tab, and focusable (tabindex=0) only when it has no focusable descendant",
138
+ "Roving tabindex: exactly one tab is in the Tab sequence (tabindex=0); arrows move focus among the rest",
139
+ "automatic activation selects on focus; manual activation selects on Enter/Space (focus and selection may diverge)",
140
+ "Disabled tabs carry aria-disabled=true and are excluded from the roving set",
141
+ "Forced-colors: active tab uses Highlight; disabled tab uses GrayText",
142
+ "ariaLabel forwarded to the tablist to name the widget"
143
+ ],
144
+ "ai": {
145
+ "synonyms": [
146
+ "tabs",
147
+ "tab panel",
148
+ "tabbed",
149
+ "tab bar",
150
+ "tablist",
151
+ "tabbed interface",
152
+ "segmented content",
153
+ "tab navigation"
154
+ ],
155
+ "avoid": [
156
+ "hand-rolling role=tab/tabpanel with manual keydown handlers — <Tabs> delegates the WAI-ARIA keyboard model to createRovingTabindex",
157
+ "using <Tabs> for sequential steps or primary page navigation — tabs are for switching between peer views of the same context, not a wizard or a site menu"
158
+ ],
159
+ "patterns": [
160
+ "Basic: <Tabs ariaLabel=\"'Settings'\" items=\"tabItems\" /> where tabItems = [{ id, label, content }]",
161
+ "With icons: items include an icon name, e.g. { id: 'code', label: 'Code', icon: 'copy', content: … }",
162
+ "Manual activation (expensive panels): <Tabs items=\"tabItems\" activation=\"'manual'\" />",
163
+ "Vertical: <Tabs items=\"tabItems\" orientation=\"'vertical'\" />",
164
+ "Component panels: content can be a ui-kit component, e.g. content: CodeBlock({ code })"
165
+ ]
166
+ },
167
+ "designGuidance": {
168
+ "surface": "Tabs are chromeless — no card or fill. The tablist sits on a thin --color-divider track; panels inherit the surrounding surface.",
169
+ "depth": "Flat. The only visual weight is the active-tab indicator and the color shift from text-muted to primary.",
170
+ "borders": "Horizontal tablist track is a --border-width-medium inset shadow; the active indicator (--border-width-medium) slides under the selected tab. Vertical track is a --border-width-medium inline-start border with a --border-width-thick accent bar beside the selected tab.",
171
+ "interactivity": "Idle tabs are muted; hover lifts to full text color; the selected tab is primary with the indicator. focus-visible shows the design-system focus ring on both tabs and the active panel.",
172
+ "motion": "The indicator scales in via motion-safe transition; respects prefers-reduced-motion.",
173
+ "bestPractices": "Use for switching between peer views of the same content (Preview/Code, Description/Reviews). Keep labels short. Prefer automatic activation unless rendering a panel is expensive, then use manual so arrowing through tabs does not thrash. Always provide ariaLabel when more than one tablist appears on a page."
174
+ },
175
+ "examples": [
176
+ {
177
+ "id": "default",
178
+ "title": "Basic tabs",
179
+ "pithy": "<Tabs ariaLabel=\"'Account'\" items=\"accountTabs\" />",
180
+ "assertions": [
181
+ "renders <div class='ui-tabs'>",
182
+ "contains [role='tablist']",
183
+ "first tab has aria-selected='true'",
184
+ "one [role='tabpanel'] visible, others [hidden]"
185
+ ]
186
+ },
187
+ {
188
+ "id": "manual",
189
+ "title": "Manual activation",
190
+ "pithy": "<Tabs ariaLabel=\"'Docs'\" items=\"docTabs\" activation=\"'manual'\" />",
191
+ "assertions": [
192
+ "arrow keys move focus without changing aria-selected",
193
+ "Enter/Space selects the focused tab"
194
+ ]
195
+ },
196
+ {
197
+ "id": "vertical",
198
+ "title": "Vertical orientation",
199
+ "pithy": "<Tabs ariaLabel=\"'Sections'\" items=\"sectionTabs\" orientation=\"'vertical'\" />",
200
+ "assertions": [
201
+ "tablist has aria-orientation='vertical'",
202
+ "Up/Down arrows navigate",
203
+ "root has data-orientation='vertical'"
204
+ ]
205
+ }
206
+ ]
207
+ }
@@ -0,0 +1,212 @@
1
+ {
2
+ "kind": "component",
3
+ "id": "ui.text",
4
+ "name": "Text",
5
+ "displayName": "Text",
6
+ "version": "0.1.0",
7
+ "level": "atom",
8
+ "domain": "content",
9
+ "bidi": true,
10
+ "description": "Typography wrapper for body text content. Maps props to design-system typography tokens and utility classes. Uses logical properties for RTL/LTR support. Supports 18 semantic HTML tags.",
11
+ "props": [
12
+ {
13
+ "name": "text",
14
+ "reactive": true,
15
+ "label": "Text",
16
+ "editor": { "tab": "content", "multiline": true },
17
+ "type": "string | Signal<string>",
18
+ "placeholder": "Body copy. Replace this with your own content.",
19
+ "description": "Visible body text. Rendered as text inside the element; newlines (\\n) become <br> so multi-line input preserves its line breaks. When omitted, the component falls back to slot content."
20
+ },
21
+ {
22
+ "name": "size",
23
+ "label": "Size",
24
+ "editor": { "type": "token-font" },
25
+ "type": "enum",
26
+ "enum": ["xs", "sm", "base", "lg", "xl", "2xl", "3xl", "4xl"],
27
+ "default": "base",
28
+ "description": "Font size — maps to u-text-* typography utilities"
29
+ },
30
+ {
31
+ "name": "weight",
32
+ "label": "Weight",
33
+ "type": "enum",
34
+ "enum": [
35
+ "thin",
36
+ "light",
37
+ "normal",
38
+ "medium",
39
+ "semibold",
40
+ "bold",
41
+ "extrabold"
42
+ ],
43
+ "default": "normal",
44
+ "description": "Font weight — maps to u-font-* typography utilities. Semantic tags (strong, em) preserve native weight unless overridden."
45
+ },
46
+ {
47
+ "name": "color",
48
+ "label": "Color",
49
+ "editor": { "type": "token-color" },
50
+ "type": "enum",
51
+ "enum": [
52
+ "text",
53
+ "text-muted",
54
+ "text-inverse",
55
+ "primary",
56
+ "secondary",
57
+ "tertiary"
58
+ ],
59
+ "default": "text",
60
+ "description": "Text color — maps to --color-* design tokens. Status colors (success/info/warning/danger) are intentionally excluded — those belong on Badge/Button/Icon where the color carries semantic meaning, not on plain text."
61
+ },
62
+ {
63
+ "name": "align",
64
+ "label": "Align",
65
+ "type": "enum",
66
+ "enum": ["start", "center", "end", "justify"],
67
+ "description": "Text alignment — uses logical values (start/end, not left/right) for RTL support"
68
+ },
69
+ {
70
+ "name": "truncate",
71
+ "label": "Truncate",
72
+ "editor": { "tab": "layout", "type": "number" },
73
+ "type": "boolean | number",
74
+ "default": false,
75
+ "description": "Truncation — true for single-line ellipsis, number (1-4) for line-clamp"
76
+ },
77
+ {
78
+ "name": "as",
79
+ "label": "Element",
80
+ "editor": { "tab": "settings" },
81
+ "type": "enum",
82
+ "enum": [
83
+ "p",
84
+ "span",
85
+ "div",
86
+ "small",
87
+ "strong",
88
+ "em",
89
+ "s",
90
+ "abbr",
91
+ "time",
92
+ "blockquote",
93
+ "figcaption",
94
+ "cite",
95
+ "q",
96
+ "mark",
97
+ "code",
98
+ "pre",
99
+ "kbd",
100
+ "samp"
101
+ ],
102
+ "default": "p",
103
+ "description": "HTML element tag — choose the semantically correct element"
104
+ },
105
+ {
106
+ "name": "title",
107
+ "label": "Title",
108
+ "editor": { "tab": "settings" },
109
+ "type": "string",
110
+ "description": "Expansion text for <abbr> — sets the title attribute. Only used when as='abbr'. Required for accessibility."
111
+ },
112
+ {
113
+ "name": "datetime",
114
+ "label": "Datetime",
115
+ "editor": { "tab": "settings" },
116
+ "type": "string",
117
+ "description": "Machine-readable date/time for <time> — sets the datetime attribute. Only used when as='time'."
118
+ }
119
+ ],
120
+ "events": [],
121
+ "tokens": {
122
+ "color.default": "--color-text",
123
+ "color.muted": "--color-text-muted",
124
+ "color.inverse": "--color-text-inverse",
125
+ "color.brand": "--color-{primary|secondary|tertiary}",
126
+ "color.status": "--color-{success|info|warning|danger}",
127
+ "font-size": "--font-size-{size} (via u-text-* utility)",
128
+ "font-weight": "--font-weight-{weight} (via u-font-* utility)"
129
+ },
130
+ "spacing": {
131
+ "internal": "margin: 0 (reset) — external spacing controlled by parent layout",
132
+ "label-to-input": "--space-sm (close friends — label and input are tightly coupled)"
133
+ },
134
+ "constraints": [
135
+ "Never use raw hex colors — use color prop with design token values",
136
+ "Never use physical CSS properties — all alignment uses logical values (start/end, not left/right)",
137
+ "When as='abbr', the title prop is required for accessibility",
138
+ "When as='time', the datetime prop is required for machine-readability",
139
+ "Semantic tags (strong, em, small) preserve native browser styles unless weight/size explicitly overridden",
140
+ "Use logical properties only (margin-inline-start, not margin-left)",
141
+ "Must handle forced-colors mode — color: CanvasText",
142
+ "Must handle high-contrast mode — bump text-muted to full text color"
143
+ ],
144
+ "a11y": [
145
+ "Uses semantic HTML elements — <p>, <strong>, <em>, <abbr>, <time>, etc.",
146
+ "<mark> and <s> include visually-hidden pseudo-elements for screen reader announcements ('[highlight start]', '[strikethrough start]')",
147
+ "<abbr> requires title attribute for expansion (dev warning if missing)",
148
+ "Forced-colors: all text uses CanvasText for visibility",
149
+ "High-contrast: text-muted is upgraded to full text color for readability"
150
+ ],
151
+ "ai": {
152
+ "synonyms": [
153
+ "paragraph",
154
+ "body text",
155
+ "label",
156
+ "caption",
157
+ "description",
158
+ "copy"
159
+ ],
160
+ "avoid": [
161
+ "div for text content — use <Text> with correct as prop",
162
+ "hardcoded font sizes — use size prop",
163
+ "text-align: left/right — use align='start'/'end'",
164
+ "color as inline style — use color prop"
165
+ ],
166
+ "patterns": [
167
+ "Body paragraph: <Text>Lorem ipsum...</Text>",
168
+ "Muted caption: <Text size=\"'sm'\" color=\"'text-muted'\">Last updated</Text>",
169
+ "Bold inline: <Text as=\"'strong'\" color=\"'primary'\">Important</Text>",
170
+ "Centered heading subtitle: <Text size=\"'lg'\" align=\"'center'\" color=\"'text-muted'\">Subtitle</Text>",
171
+ "Truncated card description: <Text truncate=\"2\">Long text...</Text>",
172
+ "Code inline: <Text as=\"'code'\" size=\"'sm'\">const x = 1</Text>"
173
+ ]
174
+ },
175
+ "examples": [
176
+ {
177
+ "id": "basic-paragraph",
178
+ "title": "Basic paragraph",
179
+ "pithy": "<Text>Hello, world!</Text>",
180
+ "assertions": [
181
+ "renders <p> element",
182
+ "has class ui-text",
183
+ "color is --color-text"
184
+ ]
185
+ },
186
+ {
187
+ "id": "styled-text",
188
+ "title": "Styled text with size and color",
189
+ "pithy": "<Text size=\"'lg'\" weight=\"'bold'\" color=\"'primary'\">Featured</Text>",
190
+ "assertions": [
191
+ "has u-text-lg class",
192
+ "has u-font-bold class",
193
+ "data-color='primary'"
194
+ ]
195
+ },
196
+ {
197
+ "id": "semantic-abbr",
198
+ "title": "Abbreviation with expansion",
199
+ "pithy": "<Text as=\"'abbr'\" title=\"'Hypertext Markup Language'\">HTML</Text>",
200
+ "assertions": [
201
+ "renders <abbr> element",
202
+ "title='Hypertext Markup Language'"
203
+ ]
204
+ },
205
+ {
206
+ "id": "truncated",
207
+ "title": "Multi-line truncation",
208
+ "pithy": "<Text truncate=\"3\">Long text that spans multiple lines...</Text>",
209
+ "assertions": ["has u-line-clamp-3 class"]
210
+ }
211
+ ]
212
+ }
@@ -0,0 +1,156 @@
1
+ {
2
+ "kind": "component",
3
+ "id": "ui.tile",
4
+ "name": "Tile",
5
+ "displayName": "Tile",
6
+ "version": "0.1.0",
7
+ "level": "atom",
8
+ "domain": "content",
9
+ "bidi": true,
10
+ "description": "Compact clickable tile with an icon/glyph slot above a visible label. Renders a <button> with optional selected state. Used for launchers, recents rails, and template pickers.",
11
+ "props": [
12
+ {
13
+ "name": "label",
14
+ "label": "Label",
15
+ "type": "string",
16
+ "required": true,
17
+ "placeholder": "Tile",
18
+ "description": "Visible label rendered below the media slot. Doubles as the default accessible name."
19
+ },
20
+ {
21
+ "name": "selected",
22
+ "label": "Selected",
23
+ "type": "boolean | () => boolean",
24
+ "required": false,
25
+ "description": "Selected state — opt-in toggle semantics. When provided, applies primary-tinted chrome and sets aria-pressed. Accepts a static boolean or a () => boolean accessor. Omit for non-toggle launcher tiles."
26
+ },
27
+ {
28
+ "name": "disabled",
29
+ "label": "Disabled",
30
+ "type": "boolean | () => boolean",
31
+ "default": false,
32
+ "description": "Disabled state — sets the disabled attribute and dims the chrome via the design-system disabled mixin. Accepts a static boolean or a () => boolean accessor."
33
+ },
34
+ {
35
+ "name": "ariaLabel",
36
+ "label": "ARIA label",
37
+ "editor": { "tab": "settings" },
38
+ "type": "string",
39
+ "required": false,
40
+ "description": "Accessible label override. Defaults to label."
41
+ },
42
+ {
43
+ "name": "draggable",
44
+ "label": "Draggable",
45
+ "editor": { "tab": "settings" },
46
+ "type": "boolean",
47
+ "default": false,
48
+ "description": "Marks the root button as a drag source via the native draggable attribute."
49
+ },
50
+ {
51
+ "name": "data",
52
+ "label": "Data",
53
+ "editor": { "tab": "settings" },
54
+ "type": "object",
55
+ "required": false,
56
+ "description": "Map of data-* attrs. CamelCase keys emit kebab-case attrs ({ componentId: 'card' } → data-component-id='card')."
57
+ },
58
+ {
59
+ "name": "labelRef",
60
+ "type": "function",
61
+ "required": false,
62
+ "description": "Callback invoked once with the rendered label span. Lets consumers wire effects (search-match highlight, badges) without querying Tile internals."
63
+ }
64
+ ],
65
+ "events": [
66
+ {
67
+ "name": "onClick",
68
+ "description": "Fired on click. Guarded against the disabled state — the handler is not invoked while disabled is truthy."
69
+ },
70
+ {
71
+ "name": "onDragStart",
72
+ "description": "Native dragstart listener — forwarded as-is."
73
+ },
74
+ {
75
+ "name": "onDragEnd",
76
+ "description": "Native dragend listener — forwarded as-is."
77
+ },
78
+ {
79
+ "name": "onAnimationEnd",
80
+ "description": "Native animationend listener — useful for one-shot CSS flash effects."
81
+ }
82
+ ],
83
+ "slots": [{ "name": "default", "displayName": "Content" }],
84
+ "tokens": {
85
+ "color": "--color-primary, --color-on-primary (via --_tile-color, --_tile-on-color), --color-surface, --color-text, --color-border",
86
+ "spacing": "--space-md (block padding, gap), --space-sm (inline padding)",
87
+ "radius": "--radius-md (tile), --radius-sm (media)",
88
+ "size": "--font-size-sm (label), --font-size-xs (glyph)",
89
+ "border": "--border-width-hairline (idle), --border-width-thin (forced-colors)",
90
+ "motion": "--motion-transition-background, --motion-transition-color, --motion-duration-normal, --motion-easing-standard"
91
+ },
92
+ "spacing": {
93
+ "media-to-label": "--space-md (friends — distinct content blocks inside the tile)",
94
+ "tiles-in-grid": "--space-sm (close friends — adjacent picker tiles)"
95
+ },
96
+ "constraints": [
97
+ "label is required and must be non-empty — DEV warning when missing",
98
+ "Disabled tiles skip onClick invocation entirely (preventDefault + stopImmediatePropagation)",
99
+ "Default slot is treated as decorative (aria-hidden) — semantic content belongs in label / ariaLabel",
100
+ "Reactive selected / disabled accessors wire an internal effect; static booleans apply state once"
101
+ ],
102
+ "a11y": [
103
+ "Renders a real <button type='button'> — gets keyboard activation, focus ring, and form-control semantics for free",
104
+ "aria-pressed is only set when selected is provided — non-toggle launcher tiles render as plain buttons (no toggle semantics)",
105
+ "aria-label defaults to label; explicit ariaLabel takes precedence",
106
+ "Forced-colors: explicit border + Highlight color when selected, GrayText when disabled"
107
+ ],
108
+ "ai": {
109
+ "synonyms": ["tile", "card", "launcher", "picker", "thumbnail", "item"],
110
+ "avoid": [
111
+ "div with onClick — use <Tile> so keyboard activation and focus ring come for free",
112
+ "raw background colors / radii — Tile handles surface and selection chrome",
113
+ "wrapping a <Button> with custom CSS — Tile is a sibling atom for vertical icon-over-label layouts"
114
+ ],
115
+ "patterns": [
116
+ "Recent picker: <Tile label=\"'Card'\" @click=\"insert\"> <Icon name=\"'card'\" decorative=\"true\" size=\"'sm'\" /> </Tile>",
117
+ "Selected tile: <Tile label=\"'Hero'\" selected=\"isHero\" @click=\"choose\"> <Icon name=\"'hero'\" /> </Tile>",
118
+ "Disabled tile: <Tile label=\"'Coming soon'\" disabled=\"true\"> <Icon name=\"'lock'\" /> </Tile>"
119
+ ]
120
+ },
121
+ "designGuidance": {
122
+ "surface": "Surface uses --color-surface with a hairline border. Hover/selected blend the primary tint via the design-system state-background mixin.",
123
+ "depth": "Flat by default; selected state uses tint + primary border, no shadow.",
124
+ "borders": "Hairline border in idle. Selected state swaps to a 1× primary border.",
125
+ "interactivity": "Hover, focus-visible, selected, and disabled states are all derived from design-system tokens — no bespoke colors.",
126
+ "darkMode": "Adapts automatically via --color-* tokens; the --_tile-color / --_tile-on-color split keeps the media glyph contrast correct in either mode."
127
+ },
128
+ "examples": [
129
+ {
130
+ "id": "default",
131
+ "title": "Default tile",
132
+ "pithy": "<Tile label=\"'Card'\"><Icon name=\"'card'\" decorative=\"true\" size=\"'sm'\" /></Tile>",
133
+ "assertions": [
134
+ "renders a <button class='ui-tile'>",
135
+ "label text content matches",
136
+ "aria-label matches label"
137
+ ]
138
+ },
139
+ {
140
+ "id": "selected",
141
+ "title": "Selected tile",
142
+ "pithy": "<Tile label=\"'Hero'\" selected=\"true\"><Icon name=\"'hero'\" decorative=\"true\" size=\"'sm'\" /></Tile>",
143
+ "assertions": ["data-selected='true'", "aria-pressed='true'"]
144
+ },
145
+ {
146
+ "id": "disabled",
147
+ "title": "Disabled launcher tile",
148
+ "pithy": "<Tile label=\"'Locked'\" disabled=\"true\"><Icon name=\"'lock'\" /></Tile>",
149
+ "assertions": [
150
+ "disabled attribute present",
151
+ "data-disabled='true'",
152
+ "aria-pressed not set (no selected prop)"
153
+ ]
154
+ }
155
+ ]
156
+ }