coles-solid-library 0.3.5 → 0.3.7

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/USAGE.html ADDED
@@ -0,0 +1,784 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Cole's Solid Library – Usage Guide</title>
6
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
7
+ <style>
8
+ :root {
9
+ font-family: system-ui,-apple-system,'Segoe UI',Roboto,Ubuntu,sans-serif;
10
+ --bg: #121212;
11
+ --fg: #f5f5f5;
12
+ --accent: #9e6fd8;
13
+ --border: #2d2d2d;
14
+ --code-bg: #1e1e1e;
15
+ --code-border: #333;
16
+ --radius: 8px;
17
+ --green: #4CAF50;
18
+ }
19
+ html { scroll-behavior:smooth; }
20
+ body { margin:0; background:var(--bg); color:var(--fg); line-height:1.55; }
21
+ a { color: var(--accent); }
22
+ h1,h2,h3 { line-height:1.2; font-weight:600; }
23
+ h1 { margin-top:0; font-size:clamp(1.9rem,4vw,2.6rem); }
24
+ h2 { margin-top:3rem; border-top:1px solid var(--border); padding-top:2rem; }
25
+ h3 { margin-top:2rem; }
26
+ p,ul,ol,pre,blockquote { max-width: 900px; }
27
+ pre { background:var(--code-bg); padding:1rem; border:1px solid var(--code-border); overflow:auto; border-radius:var(--radius); }
28
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace; font-size:.9rem; }
29
+ /* Layout */
30
+ .layout { display:flex; min-height:100vh; }
31
+ .sidebar { width:260px; flex-shrink:0; border-right:1px solid var(--border); padding:1rem 1.1rem 3rem; position:sticky; top:0; align-self:flex-start; max-height:100vh; overflow:auto; background:#141414; }
32
+ .sidebar .logo { font-size:1rem; font-weight:600; margin:0 0 .75rem; letter-spacing:.5px; }
33
+ .sidebar nav ul { list-style:none; margin:0; padding:0; }
34
+ .sidebar nav li { margin:.15rem 0; }
35
+ .sidebar nav li.h2 { margin-top:.65rem; }
36
+ .sidebar nav a { display:block; text-decoration:none; font-size:.78rem; padding:.4rem .55rem; border-radius:6px; color:var(--fg); border:1px solid transparent; }
37
+ .sidebar nav a:hover { background:var(--code-bg); }
38
+ .sidebar nav a.active { background:var(--accent); color:#fff; }
39
+ .sidebar nav li.h3 a { padding-left:1.25rem; font-size:.72rem; opacity:.85; }
40
+ .nav-toggle { display:none; background:var(--code-bg); border:1px solid var(--border); color:var(--fg); padding:.45rem .7rem; font-size:.75rem; border-radius:6px; cursor:pointer; }
41
+ .nav-group { margin-top:1rem; }
42
+ main { flex:1; padding:2rem clamp(1rem,4vw,3.5rem) 4rem; min-width:0; }
43
+ .skip-link { position:absolute; left:-999px; top:auto; width:1px; height:1px; overflow:hidden; }
44
+ .skip-link:focus { left:0; top:0; width:auto; height:auto; background:var(--accent); color:#000; padding:.5rem 1rem; z-index:1000; }
45
+ @media (max-width: 900px) {
46
+ .layout { flex-direction:column; }
47
+ .sidebar { position:relative; width:100%; max-height:none; border-right:none; border-bottom:1px solid var(--border); padding-bottom:1rem; }
48
+ .nav-toggle { display:inline-block; margin:.25rem 0 .5rem; }
49
+ .sidebar nav.collapsed { display:none; }
50
+ .sidebar nav a.active { background:var(--code-bg); color:var(--accent); }
51
+ }
52
+ .badge { display:inline-block; background:var(--green); color:#000; padding:0 .5rem; border-radius:4px; font-size:.7rem; letter-spacing:.5px; font-weight:600; vertical-align:middle; }
53
+ table.comp { border-collapse:collapse; margin:1rem 0; }
54
+ table.comp th, table.comp td { border:1px solid var(--border); padding:.5rem .75rem; font-size:.85rem; text-align:left; }
55
+ table.comp th { background:#1b1b1b; }
56
+ table.api { border-collapse:collapse; margin:1rem 0 2.25rem; width:100%; max-width:950px; }
57
+ table.api th, table.api td { border:1px solid var(--border); padding:.45rem .6rem; font-size:.75rem; text-align:left; vertical-align:top; }
58
+ table.api th { background:#202020; font-size:.65rem; letter-spacing:.5px; text-transform:uppercase; }
59
+ .req { color:#ffb347; font-weight:600; }
60
+ .deprecated { text-decoration:line-through; opacity:.5; }
61
+ details { background:#1b1b1b; border:1px solid var(--border); border-radius:var(--radius); padding:.75rem 1rem; }
62
+ details + details { margin-top:.75rem; }
63
+ summary { cursor:pointer; font-weight:600; }
64
+ .grid { display:grid; gap:1.25rem; grid-template-columns:repeat(auto-fill,minmax(260px,1fr)); margin:1rem 0 2rem; }
65
+ .card { border:1px solid var(--border); border-radius:var(--radius); padding:1rem 1rem 1.25rem; background:#181818; }
66
+ .card h3 { margin-top:0; font-size:1.05rem; }
67
+ footer { margin-top:4rem; font-size:.75rem; opacity:.65; }
68
+ </style>
69
+ </head>
70
+ <body>
71
+ <a href="#intro" class="skip-link">Skip to content</a>
72
+ <div class="layout">
73
+ <aside class="sidebar">
74
+ <div class="logo">Cole's Solid Library</div>
75
+ <button class="nav-toggle" aria-expanded="false" aria-controls="sidebar-nav">☰ Navigation</button>
76
+ <nav id="sidebar-nav" aria-label="Documentation" class="collapsed"></nav>
77
+ <noscript>
78
+ <nav aria-label="Documentation (No Script Fallback)">
79
+ <ul>
80
+ <li><a href="#intro">Intro</a></li>
81
+ <li><a href="#install">Installation</a></li>
82
+ <li><a href="#theming">Theming</a></li>
83
+ <li><a href="#components">Core Components (Glance)</a></li>
84
+ <li><a href="#layout-containers">Layout & Containers</a></li>
85
+ <li><a href="#buttons">Button</a></li>
86
+ <li><a href="#inputs-textarea">Input & TextArea</a></li>
87
+ <li><a href="#select">Select</a></li>
88
+ <li><a href="#menu">Menu</a></li>
89
+ <li><a href="#tabs">TabBar</a></li>
90
+ <li><a href="#carousel">Carousel</a></li>
91
+ <li><a href="#chips">Chip / Chipbar</a></li>
92
+ <li><a href="#modal">Modal</a></li>
93
+ <li><a href="#expansion">Expansion Panel</a></li>
94
+ <li><a href="#snackbar-ref">Snackbar</a></li>
95
+ <li><a href="#icon">Icon</a></li>
96
+ <li><a href="#radio-checkbox">Radio & Checkbox</a></li>
97
+ <li><a href="#tablev2-ref">TableV2</a></li>
98
+ <li><a href="#styling-patterns">Styling Patterns</a></li>
99
+ <li><a href="#forms">Forms</a></li>
100
+ <li><a href="#utilities">Utilities</a></li>
101
+ <li><a href="#accessibility">Accessibility</a></li>
102
+ <li><a href="#troubleshooting">Troubleshooting</a></li>
103
+ <li><a href="#example-app">Example App</a></li>
104
+ <li><a href="#tokens">Theme Tokens</a></li>
105
+ <li><a href="#roadmap">Roadmap</a></li>
106
+ </ul>
107
+ </nav>
108
+ </noscript>
109
+ </aside>
110
+ <main>
111
+ <h1 id="intro">Cole's Solid Library <span class="badge">Guide</span></h1>
112
+ <p>This HTML file ships with the npm package (see <code>USAGE.html</code>) and gives you an offline quick-start & reference for the component APIs, theming, and advanced patterns (TableV2, forms, dropdown rows, etc.).</p>
113
+
114
+ <h2 id="install">1. Installation & Peer Deps</h2>
115
+ <p>Install the library plus required peer dependencies (<code>solid-js</code> + <code>sass</code>):</p>
116
+ <pre><code>npm install coles-solid-library solid-js sass
117
+ # or
118
+ pnpm add coles-solid-library solid-js sass
119
+ </code></pre>
120
+ <p>Create a Solid project first if you don't have one:</p>
121
+ <pre><code>npx degit solidjs/templates/ts my-app
122
+ cd my-app
123
+ </code></pre>
124
+
125
+ <h2 id="theming">2. Theming</h2>
126
+ <ol>
127
+ <li>At the top of your global stylesheet (e.g. <code>src/index.scss</code>):
128
+ <pre><code>@use 'coles-solid-library/themes/themes.scss';</code></pre></li>
129
+ <li>Call <code>addTheme()</code> early in your app (defaults to <code>dark</code>):
130
+ <pre><code>import { addTheme } from 'coles-solid-library';
131
+ addTheme('dark'); // or 'light'
132
+ </code></pre></li>
133
+ </ol>
134
+ <p>You can re-run <code>addTheme('light')</code> reactively (inside an effect) to toggle themes. Components rely on the <code>data-theme</code> attribute set on <code>&lt;body&gt;</code>.</p>
135
+
136
+ <h2 id="components">3. Core Components (At a Glance)</h2>
137
+ <div class="grid">
138
+ <div class="card"><h3>Button</h3><pre><code>import { Button } from 'coles-solid-library';
139
+ &lt;Button theme="primary" onClick={...}&gt;Go&lt;/Button&gt;</code></pre></div>
140
+ <div class="card"><h3>Input / TextArea</h3><pre><code>import { Input, TextArea } from 'coles-solid-library';
141
+ &lt;Input placeholder="Email" /&gt;</code></pre></div>
142
+ <div class="card"><h3>Checkbox / Radio</h3><pre><code>import { Checkbox, RadioGroup, Radio } from '...';</code></pre></div>
143
+ <div class="card"><h3>Select</h3><pre><code>import { Select, Option } from '...';</code></pre></div>
144
+ <div class="card"><h3>Snackbar</h3><pre><code>import { Snackbar } from '...';</code></pre></div>
145
+ <div class="card"><h3>Container / Body</h3><pre><code>import { Container, Body } from '...';</code></pre></div>
146
+ <div class="card"><h3>Icons</h3><pre><code>import { Icon } from '...';
147
+ &lt;Icon name="camera" /&gt;</code></pre></div>
148
+ <div class="card"><h3>ExpansionPanel / Modal</h3><pre><code>import { ExpansionPanel, Modal } from 'coles-solid-library';</code></pre></div>
149
+ </div>
150
+
151
+ <h2 id="reference">4. Component & Feature Reference</h2>
152
+ <p>This section enumerates all exports from <code>src/index.ts</code> with usage notes, common props, and patterns. Only public-facing behavior is listed; internal context / style containers are omitted unless needed for composition.</p>
153
+
154
+ <h3 id="layout-containers">4.1 Layout & Containers</h3>
155
+ <ul>
156
+ <li><strong>Container</strong> – Provides themed surface/background. Props: <code>theme?: 'surface' | 'primary' | 'secondary' | 'container'</code>, standard HTML div props. Use to wrap content that requires consistent padding or contrasting backdrop.</li>
157
+ <li><strong>Body</strong> – Content wrapper (ComponentBody) that applies internal spacing & typography defaults on a surface. Prop: <code>style?: 'primary' | 'accent' | 'tertiary'</code> (currently decorative).</li>
158
+ </ul>
159
+
160
+ <pre><code>import { Container, Body } from 'coles-solid-library';
161
+
162
+ &lt;Container theme="surface" style={{ padding: '1rem' }}&gt;
163
+ &lt;Body&gt;Any content here&lt;/Body&gt;
164
+ &lt;/Container&gt;</code></pre>
165
+ <table class="api"><tr><th>Component</th><th>Prop</th><th>Type</th><th>Description</th><th>Default</th></tr>
166
+ <tr><td>Container</td><td>theme<span class="req">*</span></td><td><code>'primary' | 'primaryVariant' | 'secondary' | 'secondaryVariant' | 'background' | 'surface' | 'surfaceVariant' | 'container' | 'error'</code></td><td>Background / color role.</td><td>n/a</td></tr>
167
+ <tr><td>Container</td><td>class/style/...div</td><td>native</td><td>Forwarded attributes.</td><td>-</td></tr>
168
+ <tr><td>Body</td><td>class/style/...div</td><td>native</td><td>Wrapper with default spacing.</td><td>-</td></tr>
169
+ </table>
170
+
171
+ <h3 id="buttons">4.2 Buttons</h3>
172
+ <pre><code>&lt;Button
173
+ theme="primary" // color role: primary | secondary | surface | container
174
+ borderTheme="primary" // optional border color role
175
+ disabled
176
+ onClick={(e) =&gt; {}}
177
+ &gt;Click Me&lt;/Button&gt;</code></pre>
178
+ <p>Styling: theme tokens map to CSS variables like <code>--primary-color</code>. Prefer using <code>theme</code> rather than custom class names for consistent theming. Pass <code>class</code>/<code>style</code> for bespoke tweaks.</p>
179
+
180
+ <table class="api"><tr><th>Prop</th><th>Type</th><th>Description</th><th>Default</th></tr>
181
+ <tr><td>theme</td><td><code>ThemeVariables (excludes header/subheader)</code></td><td>Visual color role.</td><td><code>container</code></td></tr>
182
+ <tr><td>borderTheme</td><td><code>ThemeVariables | 'none'</code></td><td>Border color role mapping.</td><td><code>'none'</code></td></tr>
183
+ <tr><td>transparent</td><td><code>boolean</code></td><td>Removes background for subtle styling.</td><td>false</td></tr>
184
+ <tr><td>disabled</td><td><code>boolean</code></td><td>Native disabled state.</td><td>false</td></tr>
185
+ <tr><td>onClick</td><td><code>(e) =&gt; void</code></td><td>Click handler.</td><td>-</td></tr>
186
+ <tr><td>...button</td><td>native</td><td>Any other HTML button attributes.</td><td>-</td></tr>
187
+ </table>
188
+
189
+ <h3 id="inputs-textarea">4.3 Inputs & TextArea</h3>
190
+ <pre><code>const [name, setName] = createSignal("");
191
+ const [desc, setDesc] = createSignal("");
192
+ &lt;Input
193
+ value={name()}
194
+ placeholder="Name"
195
+ onChange={e =&gt; setName(e.currentTarget.value)}
196
+ /&gt;
197
+ &lt;TextArea
198
+ text={desc}
199
+ setText={setDesc}
200
+ rows={4}
201
+ placeholder="Description"
202
+ /&gt;</code></pre>
203
+ <p><strong>Input:</strong> controlled via <code>value</code> (or form context) and <code>onChange</code>. <strong>TextArea:</strong> uses a signal pair <code>text</code>/<code>setText</code> instead of a <code>value</code> prop and auto-grows with content. Wrap in <code>FormField</code> for legend float + error sync.</p>
204
+ <p><small><strong>Notes:</strong> (1) <code>onChange</code> for <code>Input</code> is invoked after internal form sync (if inside a FormField) so reading form state inside the handler reflects the new value. (2) An <code>Input</code> with <code>type="checkbox"</code> is coerced to text internally for styling; prefer the dedicated <code>Checkbox</code> component for semantics. (3) Required indicator logic appends an asterisk to placeholders when the parent FormField marks the control as required.</small></p>
205
+
206
+ <table class="api"><tr><th>Component</th><th>Prop</th><th>Type</th><th>Description</th><th>Default</th></tr>
207
+ <tr><td>Input</td><td>value</td><td><code>string</code></td><td>Controlled value (overrides form context).</td><td>-</td></tr>
208
+ <tr><td>Input</td><td>onChange</td><td><code>(e) =&gt; void</code></td><td>Change handler (after form sync).</td><td>-</td></tr>
209
+ <tr><td>Input</td><td>placeholder</td><td><code>string</code></td><td>Hint text (required indicator appended).</td><td>-</td></tr>
210
+ <tr><td>Input</td><td>tooltip</td><td><code>string</code></td><td>Title attribute tooltip.</td><td>-</td></tr>
211
+ <tr><td>Input</td><td>transparent</td><td><code>boolean</code></td><td>Alternate visual style.</td><td>false</td></tr>
212
+ <tr><td>Input</td><td>type</td><td><code>string</code></td><td>Input type (checkbox coerced to text internally).</td><td>text</td></tr>
213
+ <tr><td>TextArea</td><td>text<span class="req">*</span></td><td><code>() =&gt; string</code></td><td>Accessor signal.</td><td>n/a</td></tr>
214
+ <tr><td>TextArea</td><td>setText<span class="req">*</span></td><td><code>(v:string)=&gt;void</code></td><td>Setter signal.</td><td>n/a</td></tr>
215
+ <tr><td>TextArea</td><td>rows</td><td><code>number</code></td><td>Initial visual row hint (auto-resizes).</td><td>3</td></tr>
216
+ <tr><td>TextArea</td><td>minSize</td><td><code>{ height?: number; width?: number }</code></td><td>Minimum auto-size bounds.</td><td>-</td></tr>
217
+ <tr><td>TextArea</td><td>transparent</td><td><code>boolean</code></td><td>Alt style (no background).</td><td>false</td></tr>
218
+ <tr><td>Both</td><td>class/style/...input</td><td>native</td><td>Forwarded attributes & ARIA.</td><td>-</td></tr>
219
+ </table>
220
+
221
+ <h3 id="form-system">4.4 Form System</h3>
222
+ <p>Full documentation moved to <strong>Section 6 (Forms API)</strong> to keep everything in a single place. See that section for creation, validators, FormArray usage, async validation, binding helpers, and patterns.</p>
223
+
224
+ <h3 id="field-error">4.5 FieldError (ColeError) Registration</h3>
225
+ <p><code>&lt;FieldError errorName="required"&gt;Message&lt;/FieldError&gt;</code> (alias of <code>ColeError</code>) registers an error display element with the surrounding <code>FormField</code>. It does not render inline itself; the FormField decides which registered errors to project (e.g. hide <code>minLength</code> while <code>required</code> fails).</p>
226
+
227
+ <table class="api"><tr><th>Prop</th><th>Type</th><th>Description</th></tr>
228
+ <tr><td>errorName</td><td><code>string</code></td><td>Validator error key to register.</td></tr>
229
+ <tr><td>children</td><td><code>JSX.Element</code></td><td>Content shown when this error is active & selected by FormField.</td></tr>
230
+ </table>
231
+
232
+ <h3 id="select">4.6 Select Component (Single & Multi)</h3>
233
+ <pre><code>&lt;Select multiple placeholder="Pick" value={values()} onChange={setValues}&gt;
234
+ &lt;Option value="a"&gt;Alpha&lt;/Option&gt;
235
+ &lt;Option value="b"&gt;Beta&lt;/Option&gt;
236
+ &lt;/Select&gt;</code></pre>
237
+ <p><strong>Features:</strong> positioning logic clamps inside viewport; flips above if insufficient space. Multi-select toggles items; consumer tracks selected list via <code>onChange</code> or controlled value (depending on implementation). Provided tests assert viewport constraints.</p>
238
+
239
+ <table class="api"><tr><th>Component</th><th>Prop</th><th>Type</th><th>Description</th><th>Default</th></tr>
240
+ <tr><td>Select</td><td>multiple</td><td><code>boolean</code></td><td>Enable multi selection.</td><td>false</td></tr>
241
+ <tr><td>Select</td><td>value</td><td><code>T | T[]</code></td><td>Controlled selected value(s).</td><td>-</td></tr>
242
+ <tr><td>Select</td><td>defaultValue</td><td><code>T | T[]</code></td><td>Initial uncontrolled selection.</td><td>-</td></tr>
243
+ <tr><td>Select</td><td>onChange</td><td><code>(val) =&gt; void</code></td><td>Fires on selection state change (may fire on mount).</td><td>-</td></tr>
244
+ <tr><td>Select</td><td>onSelect</td><td><code>(val) =&gt; void</code></td><td>User interaction selection callback.</td><td>-</td></tr>
245
+ <tr><td>Select</td><td>placeholder</td><td><code>string</code></td><td>Trigger placeholder text.</td><td>"Select..."</td></tr>
246
+ <tr><td>Select</td><td>renderValue</td><td><code>(val) =&gt; JSX</code></td><td>Custom render of current value(s).</td><td>-</td></tr>
247
+ <tr><td>Select</td><td>dropdownClass</td><td><code>string</code></td><td>Class for dropdown portal element.</td><td>-</td></tr>
248
+ <tr><td>Select</td><td>transparent</td><td><code>boolean</code></td><td>Transparent trigger style.</td><td>false</td></tr>
249
+ <tr><td>Select</td><td>tooltip</td><td><code>string</code></td><td>Title attribute tooltip.</td><td>-</td></tr>
250
+ <tr><td>Select</td><td>id</td><td><code>string</code></td><td>DOM id (anchor / tests).</td><td>-</td></tr>
251
+ <tr><td>Option</td><td>value</td><td><code>T</code></td><td>Option value token.</td><td>n/a</td></tr>
252
+ <tr><td>Option</td><td>children</td><td><code>JSX</code></td><td>Displayed label (fallback <code>String(value)</code>).</td><td>-</td></tr>
253
+ <tr><td>Option</td><td>class</td><td><code>string</code></td><td>Custom class.</td><td>-</td></tr>
254
+ </table>
255
+
256
+ <h3 id="menu">4.7 Menu & Dropdown</h3>
257
+ <pre><code>const [open, setOpen] = createSignal(false);
258
+ let anchor!: HTMLButtonElement;
259
+ &lt;button ref={anchor} onClick={() =&gt; setOpen(o =&gt; !o)}&gt;Open Menu&lt;/button&gt;
260
+ &lt;Menu anchorElement={() =&gt; anchor} show={[open, setOpen]} position="left"&gt;
261
+ &lt;MenuItem onClick={() =&gt; console.log('Action')}&gt;Action&lt;/MenuItem&gt;
262
+ &lt;MenuDropdown header={() =&gt; &lt;span&gt;More&lt;/span&gt;}&gt;
263
+ &lt;MenuItem onClick={() =&gt; console.log('Nested')}&gt;Nested 1&lt;/MenuItem&gt;
264
+ &lt;/MenuDropdown&gt;
265
+ &lt;/Menu&gt;</code></pre>
266
+ <p>Anchor the menu to a DOM element via <code>anchorElement</code>. Visibility is controlled by the <code>show</code> pair. Use <code>MenuDropdown</code> for nested lists (internal toggle state). Items fire native <code>onClick</code>.</p>
267
+ <table class="api"><tr><th>Component</th><th>Prop</th><th>Type</th><th>Description</th><th>Default</th></tr>
268
+ <tr><td>Menu</td><td>anchorElement</td><td><code>()=&gt;HTMLElement|undefined</code></td><td>Anchor reference.</td><td>n/a</td></tr>
269
+ <tr><td>Menu</td><td>show</td><td><code>[()=&gt;boolean,(b:boolean)=&gt;void]</code></td><td>Visibility signal pair.</td><td>n/a</td></tr>
270
+ <tr><td>Menu</td><td>position</td><td><code>'left'|'center'|'right'</code></td><td>Horizontal alignment.</td><td>'left'</td></tr>
271
+ <tr><td>Menu</td><td>areaExclusionRefs</td><td><code>Accessor&lt;HTMLElement|undefined&gt;[]</code></td><td>Extra elements ignored for outside clicks.</td><td>-</td></tr>
272
+ <tr><td>Menu</td><td>submenu</td><td><code>boolean</code></td><td><em>Internal</em> flag for nested menus (omit in user code).</td><td>false</td></tr>
273
+ <tr><td>MenuDropdown</td><td>header</td><td><code>() =&gt; JSX</code></td><td>Header renderer (with toggle button + icon).</td><td>-</td></tr>
274
+ <tr><td>MenuItem</td><td>onClick</td><td><code>(e:MouseEvent)=&gt;void</code></td><td>Selection/action handler.</td><td>-</td></tr>
275
+ </table>
276
+ <p><small>Note: Source folder for Carousel is spelled <code>Carosel</code>; public export is <code>Carousel</code>.</small></p>
277
+
278
+ <h3 id="tabs">4.8 TabBar</h3>
279
+ <pre><code>const [tab, setTab] = createSignal(0);
280
+ &lt;TabBar
281
+ tabs={['Home','Settings']}
282
+ activeTab={tab()}
283
+ onTabChange={(label,i) =&gt; setTab(i)}
284
+ tabPosition="stretch"
285
+ /&gt;</code></pre>
286
+ <p>Indicator animates to the active tab. Use <code>tabPosition</code> for distribution and <code>noRail</code> to hide the baseline. Accessibility: ensure the root element has <code>role="tablist"</code>, each tab has <code>role="tab"</code> + <code>aria-selected</code>, and panels (if any) have <code>role="tabpanel"</code> with <code>aria-labelledby</code> pointing to the tab id.</p>
287
+ <table class="api"><tr><th>Prop</th><th>Type</th><th>Description</th><th>Default</th></tr>
288
+ <tr><td>tabs</td><td><code>string[]</code></td><td>Tab labels.</td><td>[]</td></tr>
289
+ <tr><td>activeTab</td><td><code>number</code></td><td>Active index.</td><td>0</td></tr>
290
+ <tr><td>onTabChange</td><td><code>(label:string,index:number)=&gt;void</code></td><td>Selection callback.</td><td>-</td></tr>
291
+ <tr><td>tabPosition</td><td><code>'start'|'center'|'end'|'stretch'</code></td><td>Alignment mode.</td><td>'stretch'</td></tr>
292
+ <tr><td>noRail</td><td><code>boolean</code></td><td>Hide baseline rail.</td><td>false</td></tr>
293
+ <tr><td>indicatorClass</td><td><code>string</code></td><td>Extra class for indicator.</td><td>-</td></tr>
294
+ <tr><td>colors</td><td><code>{ text?; activeText?; background?; indicator? }</code></td><td>Color overrides.</td><td>-</td></tr>
295
+ <tr><td>size</td><td><code>{ height?; fontSize?; indicatorHeight? }</code></td><td>Dimension overrides.</td><td>-</td></tr>
296
+ <tr><td>animationTiming</td><td><code>string</code></td><td>Transition timing.</td><td>CSS default</td></tr>
297
+ </table>
298
+
299
+ <h3 id="carousel">4.9 Carousel</h3>
300
+ <pre><code>const slides = [
301
+ { name: 'One', element: &lt;div&gt;First&lt;/div&gt; },
302
+ { name: 'Two', element: &lt;div&gt;Second&lt;/div&gt; }
303
+ ];
304
+ &lt;Carousel elements={slides} startingIndex={0} /&gt;</code></pre>
305
+ <p>Supports either an <code>elements</code> array or direct children. Control externally via <code>currentIndex</code> pair if you need programmatic navigation. No built-in autoplay props (prior docs were speculative). Accessibility tips: if you add autoplay externally, provide a pause button, update an <code>aria-live="polite"</code> region on slide change, and consider <code>aria-roledescription="carousel"</code>.</p>
306
+ <table class="api"><tr><th>Prop</th><th>Type</th><th>Description</th><th>Default</th></tr>
307
+ <tr><td>elements</td><td><code>{ name:string; element:JSX }[]</code></td><td>Slide list (alternative to children).</td><td>-</td></tr>
308
+ <tr><td>children</td><td><code>JSX | JSX[]</code></td><td>Slide elements.</td><td>-</td></tr>
309
+ <tr><td>startingIndex</td><td><code>number</code></td><td>Initial slide index.</td><td>0</td></tr>
310
+ <tr><td>currentIndex</td><td><code>[()=&gt;number,(n:number)=&gt;void]</code></td><td>External control pair.</td><td>-</td></tr>
311
+ <tr><td>notFoundName</td><td><code>string</code></td><td>Fallback token for empty state.</td><td>Elements</td></tr>
312
+ </table>
313
+
314
+ <h3 id="chips">4.10 Chip & Chipbar</h3>
315
+ <pre><code>&lt;Chip value="Tag" remove={() =&gt; console.log('remove')} /&gt;
316
+ &lt;Chipbar chips={() =&gt; chips()} setChips={setChips} /&gt;</code></pre>
317
+ <p><strong>Chip:</strong> Displays (optional <code>key:</code> prefix + value). Use <code>remove</code> to show a removal button. <strong>Chipbar:</strong> Horizontal scroll area with optional clear button when using reactive <code>chips</code>.</p>
318
+
319
+ <table class="api"><tr><th>Component</th><th>Prop</th><th>Type</th><th>Description</th><th>Default</th></tr>
320
+ <tr><td>Chip</td><td>value</td><td><code>string</code></td><td>Displayed value.</td><td>n/a</td></tr>
321
+ <tr><td>Chip</td><td>key</td><td><code>string</code></td><td>Optional visible prefix label (not a framework diffing key).</td><td>-</td></tr>
322
+ <tr><td>Chip</td><td>remove</td><td><code>() =&gt; any</code></td><td>Show remove button & handler.</td><td>-</td></tr>
323
+ <tr><td>Chip</td><td>onClick</td><td><code>(e) =&gt; any</code></td><td>Selection / action click.</td><td>-</td></tr>
324
+ <tr><td>Chipbar</td><td>chips</td><td><code>() =&gt; ChipType[]</code></td><td>Reactive chips (variant 1).</td><td>-</td></tr>
325
+ <tr><td>Chipbar</td><td>setChips</td><td><code>Setter&lt;ChipType[]&gt;</code></td><td>Mutator for chips.</td><td>-</td></tr>
326
+ <tr><td>Chipbar</td><td>children</td><td><code>JSX | JSX[]</code></td><td>Manual Chip nodes (variant 2).</td><td>-</td></tr>
327
+ <tr><td>Chipbar</td><td>clearAll</td><td><code>Setter&lt;any[]&gt;</code></td><td>Clear provided children variant.</td><td>-</td></tr>
328
+ </table>
329
+
330
+ <h3 id="modal">4.11 Modal</h3>
331
+ <pre><code>const modal = createSignal(false);
332
+ &lt;Button onClick={() =&gt; modal[1](true)}&gt;Open&lt;/Button&gt;
333
+ &lt;Modal title="Dialog" show={modal} /&gt;</code></pre>
334
+ <p>Export name: <code>Modal</code>. Controlled by a signal pair passed to <code>show</code>. Backdrop clicks close via global window manager logic.</p>
335
+
336
+ <table class="api"><tr><th>Prop</th><th>Type</th><th>Description</th><th>Default</th></tr>
337
+ <tr><td>title</td><td><code>string</code></td><td>Heading text.</td><td>"Modal"</td></tr>
338
+ <tr><td>show</td><td><code>[Accessor&lt;boolean&gt;, Setter&lt;boolean&gt;]</code></td><td>Signal pair controlling visibility.</td><td>n/a</td></tr>
339
+ <tr><td>width/height</td><td><code>string</code></td><td>Explicit dimensions (responsive fallback otherwise).</td><td>-</td></tr>
340
+ <tr><td>translate</td><td><code>{ x?: string; y?: string }</code></td><td>Offsets for custom positioning.</td><td>-</td></tr>
341
+ <tr><td>styleType</td><td><code>'primary' | 'accent' | 'tertiary'</code></td><td>Theme style variant.</td><td>primary</td></tr>
342
+ <tr><td>children</td><td><code>JSX</code></td><td>Dialog body.</td><td>-</td></tr>
343
+ </table>
344
+
345
+ <h3 id="expansion">4.12 Expansion Panel</h3>
346
+ <pre><code>&lt;ExpansionPanel startOpen&gt;{[ &lt;div&gt;Header&lt;/div&gt;, &lt;div&gt;Body&lt;/div&gt; ]}&lt;/ExpansionPanel&gt;</code></pre>
347
+ <p>Use for accordions. Nest multiple expansions for sections. Maintain heading hierarchy externally.</p>
348
+
349
+ <table class="api"><tr><th>Prop</th><th>Type</th><th>Description</th><th>Default</th></tr>
350
+ <tr><td>children</td><td><code>[JSX, JSX]</code></td><td>[header, body] content array.</td><td>n/a</td></tr>
351
+ <tr><td>startOpen</td><td><code>boolean</code></td><td>Initial expanded state.</td><td>false</td></tr>
352
+ <tr><td>extraLogic</td><td><code>() =&gt; void</code></td><td>Callback invoked on toggle.</td><td>-</td></tr>
353
+ <tr><td>arrowSize</td><td><code>{ width: string; height: string }</code></td><td>Chevron dimensions.</td><td>-</td></tr>
354
+ </table>
355
+
356
+ <h3 id="snackbar-ref">4.13 Snackbar</h3>
357
+ <pre><code>addSnackbar({ message: 'Saved!', severity: 'success', closeTimeout: 3000 });
358
+ &lt;SnackbarController /&gt;</code></pre>
359
+ <p>Imperative queue; render controller once. Individual Snackbar component isn't part of public API.</p>
360
+
361
+ <table class="api"><tr><th>Export</th><th>Signature / Props</th><th>Description</th></tr>
362
+ <tr><td><code>addSnackbar</code></td><td><code>(snack: { message: string; severity?: 'error' | 'warning' | 'info' | 'success'; closeTimeout?: number })</code></td><td>Enqueue snackbar.</td></tr>
363
+ <tr><td><code>SnackbarController</code></td><td>Component</td><td>Renders active snackbars (Portal).</td></tr>
364
+ </table>
365
+
366
+ <h3 id="icon">4.14 Icon</h3>
367
+ <pre><code>&lt;Icon name="camera" size="large" variant="rounded" color="#fff" /&gt;</code></pre>
368
+ <p>Dynamically imports Material Symbols SVG. Re-colors via <code>currentColor</code>; set inline <code>color</code> or inherit from parent.</p>
369
+ <table class="api"><tr><th>Prop</th><th>Type</th><th>Description</th><th>Default</th></tr>
370
+ <tr><td>name</td><td><code>string</code></td><td>Material symbol name.</td><td>n/a</td></tr>
371
+ <tr><td>size</td><td><code>number | 'small' | 'medium' | 'large'</code></td><td>Pixel size or preset.</td><td>24</td></tr>
372
+ <tr><td>variant</td><td><code>'outlined' | 'rounded' | 'sharp'</code></td><td>Icon style folder.</td><td>'outlined'</td></tr>
373
+ <tr><td>color</td><td><code>string</code></td><td>CSS color (maps to <code>fill</code> via currentColor).</td><td><code>currentColor</code></td></tr>
374
+ <tr><td>onClick/onPointerDown</td><td><code>fn</code></td><td>Interaction handlers.</td><td>-</td></tr>
375
+ </table>
376
+
377
+ <h3 id="radio-checkbox">4.15 Radio & Checkbox</h3>
378
+ <pre><code>&lt;RadioGroup value={choice()} onChange={setChoice}&gt;
379
+ &lt;Radio value="a" label="Option A" /&gt;
380
+ &lt;Radio value="b" label="Option B" /&gt;
381
+ &lt;/RadioGroup&gt;
382
+ &lt;Checkbox checked={flag()} onChange={e =&gt; setFlag(e.currentTarget.checked)} label="Enable" /&gt;</code></pre>
383
+ <p>RadioGroup enforces single selection; pass controlled <code>value</code>. For validation integrate with FormField.</p>
384
+
385
+ <table class="api"><tr><th>Component</th><th>Prop</th><th>Type</th><th>Description</th><th>Default</th></tr>
386
+ <tr><td>RadioGroup</td><td>name</td><td><code>string</code></td><td>Group name (auto-generated if omitted).</td><td>generated</td></tr>
387
+ <tr><td>RadioGroup</td><td>label</td><td><code>string</code></td><td>Visible group label.</td><td>-</td></tr>
388
+ <tr><td>RadioGroup</td><td>ariaLabel</td><td><code>string</code></td><td>Aria label if no visible label.</td><td>-</td></tr>
389
+ <tr><td>RadioGroup</td><td>value</td><td><code>string | number</code></td><td>Controlled selection.</td><td>-</td></tr>
390
+ <tr><td>RadioGroup</td><td>defaultValue</td><td><code>string | number</code></td><td>Initial uncontrolled value.</td><td>-</td></tr>
391
+ <tr><td>RadioGroup</td><td>onChange</td><td><code>(val) =&gt; void</code></td><td>Selection callback.</td><td>-</td></tr>
392
+ <tr><td>RadioGroup</td><td>disabled</td><td><code>boolean</code></td><td>Disable group.</td><td>false</td></tr>
393
+ <tr><td>Radio</td><td>value</td><td><code>string | number</code></td><td>Value token.</td><td>n/a</td></tr>
394
+ <tr><td>Radio</td><td>label</td><td><code>string | JSX</code></td><td>Visible label.</td><td>-</td></tr>
395
+ <tr><td>Radio</td><td>ariaLabel</td><td><code>string</code></td><td>Accessible label when visual label omitted.</td><td>-</td></tr>
396
+ <tr><td>Radio</td><td>checked/defaultChecked</td><td><code>boolean</code></td><td>Controlled / uncontrolled initial state (standalone).</td><td>false</td></tr>
397
+ <tr><td>Radio</td><td>onChange</td><td><code>(val) =&gt; void</code></td><td>Standalone change handler.</td><td>-</td></tr>
398
+ <tr><td>Checkbox</td><td>checked/defaultChecked</td><td><code>boolean</code></td><td>Controlled / uncontrolled state.</td><td>false</td></tr>
399
+ <tr><td>Checkbox</td><td>onChange</td><td><code>(checked:boolean) =&gt; void</code></td><td>Change handler.</td><td>-</td></tr>
400
+ <tr><td>Checkbox</td><td>label</td><td><code>string | JSX</code></td><td>Visible label.</td><td>-</td></tr>
401
+ <tr><td>Checkbox</td><td>ariaLabel</td><td><code>string</code></td><td>Aria label if no visible label.</td><td>-</td></tr>
402
+ </table>
403
+
404
+ <h3 id="tablev2-ref">4.16 TableV2 (Overview)</h3>
405
+ <p>See dedicated section for registration model, dropdown rows, error fallbacks, dynamic columns, and styling via style containers.</p>
406
+
407
+ <table class="api"><tr><th>Component</th><th>Key Props</th><th>Description</th></tr>
408
+ <tr><td>Table</td><td><code>data, columns, getRowKey?, onCellError?, otherStyles?</code></td><td>Root orchestrator.</td></tr>
409
+ <tr><td>Row</td><td><code>rowNumber?, header?, footer?, isDropHeader?, isDropRow?, onClick?</code></td><td>Defines logical row & dropdown semantics.</td></tr>
410
+ <tr><td>Column</td><td><code>name</code></td><td>Column context for Header/Cell registration.</td></tr>
411
+ <tr><td>Header</td><td><code>rowNumber?</code></td><td>Header cell content.</td></tr>
412
+ <tr><td>Cell</td><td><code>rowNumber?, footer?, header?</code></td><td>Body/footer/header cell renderer.</td></tr>
413
+ </table>
414
+
415
+ <h3 id="styling-patterns">4.17 Styling Patterns</h3>
416
+ <ul>
417
+ <li>Prefer theming props (<code>theme</code>, <code>borderTheme</code>) over manual hex values.</li>
418
+ <li>Extend appearance via <code>class</code>/<code>style</code>; SCSS modules intentionally scoped.</li>
419
+ <li>Avoid relying on underlying generated class names—they are not part of the public API.</li>
420
+ </ul>
421
+
422
+ <h2 id="tablev2">5. TableV2 (Advanced)</h2>
423
+ <p>The table system uses contextual self‑registration. You declare <code>&lt;Column/&gt;</code>, <code>&lt;Header/&gt;</code>, <code>&lt;Cell/&gt;</code>, and <code>&lt;Row/&gt;</code> children; the library assembles the final DOM table. Rows & columns register by name + index.</p>
424
+ <h3 id="tablev2-basic">Basic Example</h3>
425
+ <pre><code>import { Table, Column, Header, Cell, Row, Container, addTheme } from 'coles-solid-library';
426
+ import { createSignal } from 'solid-js';
427
+
428
+ const [people] = createSignal([
429
+ { name: 'John', age: 23 },
430
+ { name: 'Jane', age: 24 }
431
+ ]);
432
+ addTheme();
433
+
434
+ &lt;Container theme="surface"&gt;
435
+ &lt;Table data={people} columns={['name','age']}&gt;
436
+ &lt;Column name="name"&gt;
437
+ &lt;Header&gt;Name&lt;/Header&gt;
438
+ &lt;Cell&gt;{p =&gt; p.name}&lt;/Cell&gt;
439
+ &lt;/Column&gt;
440
+ &lt;Column name="age"&gt;
441
+ &lt;Header&gt;Age&lt;/Header&gt;
442
+ &lt;Cell&gt;{p =&gt; p.age}&lt;/Cell&gt;
443
+ &lt;/Column&gt;
444
+
445
+ &lt;Row header /&gt; {/* header row */}
446
+ &lt;Row /&gt; {/* body row */}
447
+ &lt;Row footer /&gt; {/* footer row (optional) */}
448
+ &lt;/Table&gt;
449
+ &lt;/Container&gt;</code></pre>
450
+ <h3 id="tablev2-dropdown">Dropdown Rows</h3>
451
+ <p>Create paired logical rows: a toggle row (<code>&lt;Row isDropHeader rowNumber={1} /&gt;</code>) and a content row (<code>&lt;Row isDropRow rowNumber={2} /&gt;</code>). Do <strong>not</strong> add the <code>header</code> prop to the toggle row; it must be a body row so the library can attach dropdown metadata. Place exactly one <code>&lt;Cell&gt;</code> for the content row (usually in the first declared column); it will automatically span all columns when rendered open.</p>
452
+ <pre><code>&lt;Row isDropHeader rowNumber={1} /&gt;
453
+ &lt;Row isDropRow rowNumber={2} /&gt;
454
+ &lt;Column name="name"&gt;
455
+ &lt;Header&gt;Name&lt;/Header&gt;
456
+ &lt;Cell rowNumber={1}&gt;{p =&gt; p.name}&lt;/Cell&gt;
457
+ &lt;Cell rowNumber={2}&gt;{p =&gt; &lt;div class="details"&gt;More about {p.name}&lt;/div&gt;}&lt;/Cell&gt;
458
+ &lt;/Column&gt;
459
+ &lt;Column name="age"&gt;
460
+ &lt;Header&gt;Age&lt;/Header&gt;
461
+ &lt;Cell rowNumber={1}&gt;{p =&gt; p.age}&lt;/Cell&gt;
462
+ &lt;/Column&gt;</code></pre>
463
+ <p>The toggle row receives <code>role="button"</code> and <code>aria-expanded</code>. Open state is tracked per data item; supply <code>getRowKey</code> if your data reorders so expansion persists.</p>
464
+ <h3 id="tablev2-dynamic-columns">Dynamic Columns</h3>
465
+ <p>When the <code>columns</code> prop changes, internal column styles reset. Ensure reactive <code>name</code> props in <code>&lt;Column name={col()} /&gt;</code> match the array entry exactly.</p>
466
+ <h3 id="tablev2-errors">Error Handling</h3>
467
+ <p>Cell renderer exceptions are caught; a fallback <code>&lt;span role="alert"&gt;Error&lt;/span&gt;</code> is rendered unless you supply <code>onCellError</code>.</p>
468
+ <pre><code>&lt;Table ... onCellError={(err,{column,row,dataIndex}) =&gt; &lt;em&gt;Bad cell&lt;/em&gt;} /&gt;</code></pre>
469
+
470
+ <h3 id="tablev2-styling">Styling Hooks</h3>
471
+ <p>Use <code>otherStyles</code> for section elements (<code>thead/tbody/tfoot/colGroup</code>) and <code>class/style</code> directly on <code>&lt;Table&gt;</code>. Per-column, row, and cell styles are registered through their respective components and can include arbitrary native attributes via the internal <code>all</code> bag (e.g., data attributes).</p>
472
+
473
+ <h3 id="tablev2-api">API Summary</h3>
474
+ <table class="api"><tr><th>Component</th><th>Prop</th><th>Type</th><th>Description</th><th>Default</th></tr>
475
+ <tr><td>Table</td><td>data</td><td><code>Accessor&lt;T[]&gt;</code></td><td>Reactive data source.</td><td>n/a</td></tr>
476
+ <tr><td>Table</td><td>setData</td><td><code>Setter&lt;T[]&gt;</code></td><td>Optional mutator used by internal operations.</td><td>-</td></tr>
477
+ <tr><td>Table</td><td>columns</td><td><code>string[]</code></td><td>Declared order & keys.</td><td>n/a</td></tr>
478
+ <tr><td>Table</td><td>getRowKey</td><td><code>(item:T,i:number)=&gt;string|number</code></td><td>Stable key for dropdown retention.</td><td>index</td></tr>
479
+ <tr><td>Table</td><td>onCellError</td><td><code>(err,ctx) =&gt; JSX | void</code></td><td>Custom cell exception fallback.</td><td>default alert span</td></tr>
480
+ <tr><td>Row</td><td>header/footer</td><td><code>boolean</code></td><td>Mark structural rows.</td><td>false</td></tr>
481
+ <tr><td>Row</td><td>isDropHeader</td><td><code>boolean</code></td><td>Toggles dropdown open state when clicked.</td><td>false</td></tr>
482
+ <tr><td>Row</td><td>isDropRow</td><td><code>boolean</code></td><td>Marks dropdown content row.</td><td>false</td></tr>
483
+ <tr><td>Row</td><td>rowNumber</td><td><code>number</code></td><td>1-based logical grouping index.</td><td>1</td></tr>
484
+ <tr><td>Cell</td><td>footer/header</td><td><code>boolean</code></td><td>Cell context (footer/header/body).</td><td>false</td></tr>
485
+ <tr><td>Cell</td><td>rowNumber</td><td><code>number</code></td><td>Assign to specific logical row.</td><td>1</td></tr>
486
+ </table>
487
+
488
+ <h2 id="forms">6. Forms API</h2>
489
+ <p>The form stack combines: <code>FormGroup</code> (state + validation), <code>FormArray</code> (dynamic collections), <code>Validators</code>, <code>&lt;Form&gt;</code> (context + submit), <code>&lt;FormField&gt;</code> (floating legend + error projection), <code>&lt;ColeError&gt;</code> (error registration), and hook <code>useFormFieldBinding</code> for manual field wiring.</p>
490
+ <h3 id="forms-full-example">Full Example</h3>
491
+ <pre><code>import { Form, FormGroup, FormArray, Validators, FormField, ColeError, Input, Select, Option, Button } from 'coles-solid-library';
492
+
493
+ interface UserForm {
494
+ name: string;
495
+ email: string;
496
+ tags: string[];
497
+ pets: { name: string }[];
498
+ agree: boolean;
499
+ }
500
+
501
+ const minOnePet = Validators.custom<any>('minOne', (a: any[]) => Array.isArray(a) && a.length >= 1);
502
+
503
+ const form = new FormGroup<UserForm>({
504
+ name: ['', [Validators.Required, Validators.minLength(3)]],
505
+ email: ['', [Validators.Email]],
506
+ tags: [[] as any, [Validators.custom<string[]>('minOneTag', v => v.length > 0) as any]],
507
+ pets: new FormArray<{ name: string }>([[], [minOnePet as any]]),
508
+ agree: [false as any, [Validators.custom<boolean>('mustAgree', v => v === true) as any]]
509
+ });
510
+
511
+ <Form data={form} onSubmit={(vals) => console.log('submit', vals)}>
512
+ <FormField name="Name" formName="name">
513
+ <ColeError errorName="required">Name required</ColeError>
514
+ <ColeError errorName="minLength">Min length 3</ColeError>
515
+ <Input />
516
+ </FormField>
517
+ <FormField name="Email" formName="email">
518
+ <ColeError errorName="email">Invalid email</ColeError>
519
+ <Input type="email" />
520
+ </FormField>
521
+ <FormField name="Tags" formName="tags">
522
+ <ColeError errorName="minOneTag">Pick at least one</ColeError>
523
+ <Select multiple placeholder="Tags">
524
+ <Option value="x">X</Option>
525
+ <Option value="y">Y</Option>
526
+ </Select>
527
+ </FormField>
528
+ <FormField name="Agree" formName="agree">
529
+ <ColeError errorName="mustAgree">You must agree</ColeError>
530
+ <Input type="checkbox" onChange={e => form.set('agree', (e.currentTarget as any).checked as any)} />
531
+ </FormField>
532
+ <Button type="submit" theme="primary">Save</Button>
533
+ </Form></code></pre>
534
+ <h3 id="forms-errors">Error Projection & Priority</h3>
535
+ <p>Register each potential error with <code>&lt;ColeError errorName="key"&gt;</code>. When <code>required</code> fails, length validators (<code>minLength</code>/<code>maxLength</code>) are hidden until required passes.</p>
536
+ <h3 id="forms-meta">Field Meta</h3>
537
+ <p><code>form.getMeta(field)</code> returns <code>{ touched, dirty, initialValue }</code>. These flags update automatically when you call <code>set()</code>. Use for UX (e.g., show helper text only after touch).</p>
538
+ <h3 id="forms-binding">Manual Binding (Without FormField)</h3>
539
+ <pre><code>import { useFormFieldBinding } from 'coles-solid-library';
540
+ const binding = useFormFieldBinding&lt;UserForm,'name'&gt;('name');
541
+ &lt;input value={binding.value()} onInput={e =&gt; binding.setValue(e.currentTarget.value)} /&gt;
542
+ {binding.hasError() &amp;&amp; binding.errors().map(e =&gt; e.key).join(',')}
543
+ </code></pre>
544
+ <h3 id="forms-formarray">FormArray Quick Start</h3>
545
+ <pre><code>interface Pet { name: string }
546
+ const pets = new FormArray<Pet>([[], [Validators.custom<any>('minOne', a => a.length >= 1) as any]]);
547
+ const fg = new FormGroup<{ pets: Pet[] }>({ pets });
548
+ fg.validate('pets'); // false initially
549
+ fg.addToArray('pets', ['name', []] as any, { name: 'Fido' });
550
+ fg.validate('pets'); // true now
551
+ fg.removeFromArray('pets', 0);</code></pre>
552
+ <p><strong>Item Shape:</strong> Adding to a FormArray requires a control definition tuple: <code>['propName', validators[]]</code>. Provide an object value with those props.</p>
553
+ <h3 id="forms-async">Async Validators</h3>
554
+ <p>Create via <code>Validators.asyncCustom('key', asyncFn, hide?)</code>. <code>validate()</code> is optimistic for async; call <code>await validateAsync()</code> to finalize.</p>
555
+ <h3 id="forms-validation-flow">Validation Flow</h3>
556
+ <pre><code>const syncOk = form.validate();
557
+ const asyncOk = syncOk && await form.validateAsync();
558
+ if (asyncOk) doSubmit();</code></pre>
559
+ <h3 id="forms-patterns">Common Patterns</h3>
560
+ <ul>
561
+ <li>Blur-driven validation via <code>FormField</code>'s outside click detection.</li>
562
+ <li>Programmatic: <code>form.validate('field')</code> returns boolean.</li>
563
+ <li>Dynamic rules: <code>form.addValidator('name', Validators.maxLength(20))</code>.</li>
564
+ <li>Reset: <code>form.reset()</code> clears errors + meta + restores initial values.</li>
565
+ <li>Array item access: <code>form.getArrayItem('pets', 0)</code>.</li>
566
+ </ul>
567
+ <h3 id="forms-api-ref">API Reference</h3>
568
+ <table class="api"><tr><th>Entity</th><th>Key Members / Props</th><th>Description</th></tr>
569
+ <tr><td>FormGroup&lt;T&gt;</td><td><code>get/getR/set/reset/validate/validateAsync/addValidator/removeValidator/addToArray/removeFromArray/getErrors/hasError/hasValidator/getMeta/getArrayItem/markTouched/markDirty/setError</code></td><td>Root form state + validation engine.</td></tr>
570
+ <tr><td>FormArray&lt;T&gt;</td><td><code>get/getAt/getProperty/set/setAt/setProperty/add/remove/replace/validate/validateCurrent/hasError/hasValidator/getErrors/getErrorsAt</code></td><td>Dynamic object array with array + control validators.</td></tr>
571
+ <tr><td>Validators</td><td><code>Required/Email/minLength/maxLength/pattern/custom/asyncCustom</code></td><td>Validator factories; <code>hide</code> predicate suppresses an error.</td></tr>
572
+ <tr><td>&lt;Form&gt;</td><td><code>data</code>, <code>onSubmit</code></td><td>Context provider + submit orchestration (sync validate).</td></tr>
573
+ <tr><td>&lt;FormField&gt;</td><td><code>name</code>, <code>formName</code>, <code>required?</code>, others</td><td>Floating legend, focus, error projection region.</td></tr>
574
+ <tr><td>&lt;ColeError&gt;</td><td><code>errorName</code></td><td>Registers error display element for key.</td></tr>
575
+ <tr><td>useFormFieldBinding</td><td><code>(key)</code> =&gt; binding object</td><td>Manual binding to a control.</td></tr>
576
+ </table>
577
+
578
+ <!-- Duplicate Snackbar section removed; see 4.13 -->
579
+
580
+
581
+
582
+ <h2 id="utilities">8. Utilities</h2>
583
+ <table class="comp">
584
+ <tr><th>Function</th><th>Description</th></tr>
585
+ <tr><td><code>addTheme(theme?)</code></td><td>Set <code>data-theme</code> on <code>body</code> ('dark' default)</td></tr>
586
+ <tr><td><code>Clone(obj, maintainFunctions?)</code></td><td>Deep clone with cycle, Date, RegExp support</td></tr>
587
+ <tr><td><code>CloneStore(val)</code></td><td>Structured clone of unwrapped store</td></tr>
588
+ <tr><td><code>isNullish(v)</code></td><td><code>null | undefined</code> guard</td></tr>
589
+ <tr><td><code>isMobile()</code></td><td>User agent based mobile heuristic</td></tr>
590
+ <tr><td><code>useEventSignal()</code></td><td>Create signal sourced from DOM events</td></tr>
591
+ <tr><td><code>useClickOutside()</code></td><td>Pointer-down outside of referenced elements</td></tr>
592
+ <tr><td><code>getMouse()</code></td><td>Returns accessor for latest mouse coords</td></tr>
593
+ </table>
594
+ <table class="api"><tr><th>Function</th><th>Signature</th><th>Description</th><th>Notes</th></tr>
595
+ <tr><td>addTheme</td><td><code>(theme?: 'dark' | 'light') =&gt; void</code></td><td>Applies theme via <code>data-theme</code>.</td><td>Call early; re-call to switch.</td></tr>
596
+ <tr><td>Clone</td><td><code>(obj:any, maintainFunctions?:boolean) =&gt; any</code></td><td>Deep clone supporting Dates, RegExp, cycles.</td><td>Functions optionally copied by ref.</td></tr>
597
+ <tr><td>CloneStore</td><td><code>(val:any) =&gt; any</code></td><td>Structured clone tailored to store values.</td><td>Preserves primitives/arrays/objects.</td></tr>
598
+ <tr><td>isNullish</td><td><code>(v:any) =&gt; v is null | undefined</code></td><td>Null / undefined guard.</td><td>Use early returns.</td></tr>
599
+ <tr><td>isMobile</td><td><code>() =&gt; boolean</code></td><td>User-agent heuristic for mobile.</td><td>Not 100% reliable.</td></tr>
600
+ <tr><td>useEventSignal</td><td><code>(type, initial, triggerFn, ref?) =&gt; Accessor&lt;T&gt;</code></td><td>Create reactive event-driven signal.</td><td>Auto cleans listeners.</td></tr>
601
+ <tr><td>useClickOutside</td><td><code>(refs[], cb) =&gt; void</code></td><td>Callback on outside pointerdown.</td><td>Use multiple refs.</td></tr>
602
+ <tr><td>getMouse</td><td><code>() =&gt; () =&gt; { x:number; y:number }</code></td><td>Mouse position accessor factory.</td><td>Call returned fn inside effect.</td></tr>
603
+ </table>
604
+
605
+ <h2 id="accessibility">9. Accessibility Notes</h2>
606
+ <ul>
607
+ <li>Table dropdown headers use <code>role="button"</code> + <code>aria-expanded</code>.</li>
608
+ <li>Dropdown content cell has <code>role="region"</code> + <code>aria-hidden</code> reflecting open state.</li>
609
+ <li>Error cells render with <code>role="alert"</code>.</li>
610
+ </ul>
611
+
612
+ <h2 id="troubleshooting">10. Troubleshooting</h2>
613
+ <details open>
614
+ <summary>Table cells not rendering?</summary>
615
+ <p>Ensure the <code>Column name</code> matches the string in the <code>columns</code> array and that a corresponding <code>&lt;Row /&gt;</code> is declared.</p>
616
+ </details>
617
+ <details>
618
+ <summary>Theme not applying?</summary>
619
+ <p>Confirm you imported <code>@use 'coles-solid-library/themes/themes.scss'</code> before calling <code>addTheme()</code>.</p>
620
+ </details>
621
+ <details>
622
+ <summary>Dropdown rows all expand together</summary>
623
+ <p>Provide a stable <code>getRowKey</code> when data order can change; default key is index.</p>
624
+ </details>
625
+
626
+ <h2 id="example-app">11. Example App Snippet</h2>
627
+ <pre><code>import { createSignal } from 'solid-js';
628
+ import { addTheme, Container, Table, Column, Header, Cell, Row, Button } from 'coles-solid-library';
629
+
630
+ addTheme();
631
+ const [data, setData] = createSignal([{ name:'John', age:23 }, { name:'Jane', age:24 }]);
632
+
633
+ export const App = () =&gt; (
634
+ &lt;Container theme="surface" style={{ padding: '1rem' }}&gt;
635
+ &lt;Button theme="primary" onClick={() =&gt; setData(d =&gt; [...d, { name:'New', age:30 }])}&gt;Add&lt;/Button&gt;
636
+ &lt;Table data={data} columns={['name','age']}&gt;
637
+ &lt;Column name="name"&gt;
638
+ &lt;Header&gt;Name&lt;/Header&gt;
639
+ &lt;Cell&gt;{p =&gt; p.name}&lt;/Cell&gt;
640
+ &lt;/Column&gt;
641
+ &lt;Column name="age"&gt;
642
+ &lt;Header&gt;Age&lt;/Header&gt;
643
+ &lt;Cell&gt;{p =&gt; p.age}&lt;/Cell&gt;
644
+ &lt;/Column&gt;
645
+ &lt;Row header /&gt;
646
+ &lt;Row /&gt;
647
+ &lt;/Table&gt;
648
+ &lt;/Container&gt;
649
+ );</code></pre>
650
+
651
+ <h2 id="tokens">12. Theme Tokens & CSS Variables</h2>
652
+ <p>The SCSS theme file emits CSS variables on <code>:root</code> and <code>[data-theme]</code> contexts. Common tokens:</p>
653
+ <ul>
654
+ <li><code>--primary-color</code>, <code>--secondary-color</code>, their <code>-variant</code> counterparts.</li>
655
+ <li>Surface roles: <code>--surface-color</code>, <code>--surface-color-variant</code>, <code>--container-color</code>.</li>
656
+ <li>On colors: <code>--on-primary-color</code>, <code>--on-surface-color</code>, etc.</li>
657
+ <li>Typography: <code>--font-size-h1</code> .. <code>--font-size-small</code>, <code>--font-family</code>.</li>
658
+ <li>Spacing: <code>--spacing-1..4</code>, Radii: <code>--border-radius-* </code>.</li>
659
+ <li>Shadows: <code>--shadow-elevation-1..3</code>.</li>
660
+ </ul>
661
+ <pre><code>.customCard { background: var(--surface-color); color: var(--on-surface-color); }
662
+ .primaryText { color: var(--primary-color); }
663
+ .lowEmphasis { opacity: var(--text-emphasis-medium); }</code></pre>
664
+
665
+ <h2 id="roadmap">13. Future Enhancements (Roadmap Ideas)</h2>
666
+ <ul>
667
+ <li>ARIA audits & automated a11y tests.</li>
668
+ <li>Composable theme augmentation (user-provided palettes).</li>
669
+ <li>TableV2: column resizing & virtualization.</li>
670
+ <li>Form async validation state indicators (pending spinners).</li>
671
+ </ul>
672
+
673
+ <footer>
674
+ <p>MIT License. This guide is bundled for convenience. For latest updates see the project README / repository. Contributions & PRs welcome.</p>
675
+ </footer>
676
+ </main>
677
+ </div>
678
+ <script>
679
+ (function(){
680
+ const sidebarNav = document.getElementById('sidebar-nav');
681
+ if(!sidebarNav) return;
682
+
683
+ const structure = [
684
+ { id:'intro', label:'Intro' },
685
+ { id:'install', label:'Installation' },
686
+ { id:'theming', label:'Theming' },
687
+ { id:'components', label:'Core Components (Glance)' },
688
+ { id:'layout-containers', label:'Layout & Containers' },
689
+ { id:'buttons', label:'Button' },
690
+ { id:'inputs-textarea', label:'Input & TextArea' },
691
+ { id:'select', label:'Select' },
692
+ { id:'menu', label:'Menu' },
693
+ { id:'tabs', label:'TabBar' },
694
+ { id:'carousel', label:'Carousel' },
695
+ { id:'chips', label:'Chip / Chipbar' },
696
+ { id:'modal', label:'Modal' },
697
+ { id:'expansion', label:'Expansion Panel' },
698
+ { id:'snackbar-ref', label:'Snackbar' },
699
+ { id:'icon', label:'Icon' },
700
+ { id:'radio-checkbox', label:'Radio & Checkbox' },
701
+ { id:'tablev2-ref', label:'TableV2 (Overview)', children:[
702
+ { id:'tablev2-basic', label:'Basic' },
703
+ { id:'tablev2-dropdown', label:'Dropdown Rows' },
704
+ { id:'tablev2-dynamic-columns', label:'Dynamic Columns' },
705
+ { id:'tablev2-errors', label:'Error Handling' },
706
+ { id:'tablev2-styling', label:'Styling' },
707
+ { id:'tablev2-api', label:'API Summary' },
708
+ ]},
709
+ { id:'tablev2', label:'TableV2 (Advanced)' },
710
+ { id:'styling-patterns', label:'Styling Patterns' },
711
+ { id:'forms', label:'Forms', children:[
712
+ { id:'forms-full-example', label:'Full Example' },
713
+ { id:'forms-errors', label:'Errors' },
714
+ { id:'forms-meta', label:'Meta' },
715
+ { id:'forms-binding', label:'Binding' },
716
+ { id:'forms-formarray', label:'FormArray' },
717
+ { id:'forms-async', label:'Async' },
718
+ { id:'forms-validation-flow', label:'Validation Flow' },
719
+ { id:'forms-patterns', label:'Patterns' },
720
+ { id:'forms-api-ref', label:'API Ref' },
721
+ ]},
722
+ { id:'utilities', label:'Utilities' },
723
+ { id:'accessibility', label:'Accessibility' },
724
+ { id:'troubleshooting', label:'Troubleshooting' },
725
+ { id:'example-app', label:'Example App' },
726
+ { id:'tokens', label:'Theme Tokens' },
727
+ { id:'roadmap', label:'Roadmap' }
728
+ ];
729
+
730
+ const buildList = (items, level=0) => {
731
+ const ul = document.createElement('ul');
732
+ items.forEach(item => {
733
+ const li = document.createElement('li');
734
+ li.className = level === 0 ? 'h2' : 'h3';
735
+ const a = document.createElement('a');
736
+ a.href = '#' + item.id;
737
+ a.textContent = item.label;
738
+ li.appendChild(a);
739
+ if(item.children) li.appendChild(buildList(item.children, level+1));
740
+ ul.appendChild(li);
741
+ });
742
+ return ul;
743
+ };
744
+ sidebarNav.innerHTML = '';
745
+ sidebarNav.appendChild(buildList(structure));
746
+
747
+ const allIds = []; const collect = arr => arr.forEach(i => { allIds.push(i.id); if(i.children) collect(i.children); }); collect(structure);
748
+ const linkMap = new Map();
749
+ sidebarNav.querySelectorAll('a').forEach(a => linkMap.set(a.getAttribute('href').slice(1), a));
750
+
751
+ const activate = (id) => {
752
+ sidebarNav.querySelectorAll('a.active').forEach(a => { a.classList.remove('active'); a.removeAttribute('aria-current'); });
753
+ const link = linkMap.get(id);
754
+ if(link){
755
+ link.classList.add('active');
756
+ link.setAttribute('aria-current','true');
757
+ let parent = link.parentElement?.parentElement?.closest('li');
758
+ while(parent){
759
+ const anchor = parent.querySelector(':scope > a');
760
+ if(anchor) anchor.classList.add('active');
761
+ parent = parent.parentElement?.closest('li');
762
+ }
763
+ }
764
+ };
765
+
766
+ const observer = new IntersectionObserver(entries => {
767
+ const visible = entries.filter(e => e.isIntersecting).sort((a,b)=> a.boundingClientRect.top - b.boundingClientRect.top);
768
+ if(visible.length){ activate(visible[0].target.id); }
769
+ }, { rootMargin:'0px 0px -65% 0px', threshold:0.1 });
770
+ allIds.forEach(id => { const el = document.getElementById(id); if(el) observer.observe(el); });
771
+
772
+ const toggleBtn = document.querySelector('.nav-toggle');
773
+ if(toggleBtn){
774
+ toggleBtn.addEventListener('click', () => {
775
+ const collapsed = sidebarNav.classList.toggle('collapsed');
776
+ toggleBtn.setAttribute('aria-expanded', String(!collapsed));
777
+ });
778
+ }
779
+ const mq = window.matchMedia('(min-width: 901px)');
780
+ if(mq.matches) sidebarNav.classList.remove('collapsed');
781
+ })();
782
+ </script>
783
+ </body>
784
+ </html>