stimulus_plumbers 0.4.5 → 0.4.9
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +15 -1
- data/app/assets/javascripts/stimulus-plumbers/controllers.manifest.json +331 -20
- data/app/assets/javascripts/stimulus-plumbers/index.es.js +493 -226
- data/app/assets/javascripts/stimulus-plumbers/index.es.js.map +1 -1
- data/app/assets/javascripts/stimulus-plumbers/index.umd.js +1 -1
- data/app/assets/javascripts/stimulus-plumbers/index.umd.js.map +1 -1
- data/app/assets/stylesheets/stimulus_plumbers/tokens.css +58 -53
- data/docs/architecture.md +0 -2
- data/docs/component/avatar.md +1 -0
- data/docs/component/button.md +2 -1
- data/docs/component/calendar.md +18 -20
- data/docs/component/card.md +14 -12
- data/docs/component/checklist.md +153 -0
- data/docs/component/combobox.md +2 -2
- data/docs/component/form.md +32 -6
- data/docs/component/icon.md +7 -5
- data/docs/component/indicator.md +55 -0
- data/docs/component/list.md +4 -5
- data/docs/component/ordered_list.md +118 -0
- data/docs/component/plumber.md +43 -0
- data/docs/component/popover.md +1 -2
- data/docs/component/progress.md +65 -0
- data/docs/component/theme.md +15 -2
- data/docs/component/timeline.md +49 -14
- data/docs/guide.md +39 -0
- data/lib/generators/stimulus_plumbers/install/install_generator.rb +33 -0
- data/lib/stimulus_plumbers/components/button.rb +2 -1
- data/lib/stimulus_plumbers/components/card.rb +1 -1
- data/lib/stimulus_plumbers/components/checklist/item/slots.rb +13 -0
- data/lib/stimulus_plumbers/components/checklist/item.rb +64 -0
- data/lib/stimulus_plumbers/components/checklist.rb +50 -0
- data/lib/stimulus_plumbers/components/combobox/typeahead.rb +1 -0
- data/lib/stimulus_plumbers/components/combobox.rb +1 -1
- data/lib/stimulus_plumbers/components/icon.rb +2 -2
- data/lib/stimulus_plumbers/components/indicator.rb +30 -0
- data/lib/stimulus_plumbers/components/link.rb +2 -1
- data/lib/stimulus_plumbers/components/list/item.rb +1 -1
- data/lib/stimulus_plumbers/components/ordered_list/item/slots.rb +13 -0
- data/lib/stimulus_plumbers/components/ordered_list/item.rb +129 -0
- data/lib/stimulus_plumbers/components/ordered_list.rb +24 -0
- data/lib/stimulus_plumbers/components/progress_bar.rb +48 -0
- data/lib/stimulus_plumbers/components/progress_meter.rb +37 -0
- data/lib/stimulus_plumbers/components/progress_ring.rb +40 -0
- data/lib/stimulus_plumbers/components/timeline/event.rb +3 -2
- data/lib/stimulus_plumbers/components/timeline/group.rb +5 -3
- data/lib/stimulus_plumbers/engine.rb +4 -0
- data/lib/stimulus_plumbers/form/fields/inputs/password.rb +1 -0
- data/lib/stimulus_plumbers/form/fields/inputs/search.rb +1 -0
- data/lib/stimulus_plumbers/generators/css_entrypoint.rb +80 -0
- data/lib/stimulus_plumbers/generators/tokens_directive.rb +24 -0
- data/lib/stimulus_plumbers/helpers/avatar_helper.rb +1 -7
- data/lib/stimulus_plumbers/helpers/button_helper.rb +1 -7
- data/lib/stimulus_plumbers/helpers/calendar_helper.rb +2 -9
- data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +3 -6
- data/lib/stimulus_plumbers/helpers/checklist_helper.rb +11 -0
- data/lib/stimulus_plumbers/helpers/indicator_helper.rb +11 -0
- data/lib/stimulus_plumbers/helpers/ordered_list_helper.rb +11 -0
- data/lib/stimulus_plumbers/helpers/popover_helper.rb +1 -7
- data/lib/stimulus_plumbers/helpers/progress_helper.rb +21 -0
- data/lib/stimulus_plumbers/helpers.rb +8 -0
- data/lib/stimulus_plumbers/plumber/slots.rb +10 -3
- data/lib/stimulus_plumbers/themes/base.rb +4 -0
- data/lib/stimulus_plumbers/themes/schema/icon.rb +21 -4
- data/lib/stimulus_plumbers/themes/schema/indicator/ranges.rb +14 -0
- data/lib/stimulus_plumbers/themes/schema.rb +39 -3
- data/lib/stimulus_plumbers/version.rb +1 -1
- data/lib/stimulus_plumbers.rb +10 -0
- data/lib/tasks/stimulus_plumbers.rake +18 -0
- data/vendor/ARIA.md +108 -0
- data/vendor/component/manifest.json +212 -0
- data/vendor/controller/docs/calendar.md +212 -0
- data/vendor/controller/docs/checklist.md +45 -0
- data/vendor/controller/docs/clipboard.md +61 -0
- data/vendor/controller/docs/combobox.md +212 -0
- data/vendor/controller/docs/dismisser.md +39 -0
- data/vendor/controller/docs/flipper.md +46 -0
- data/vendor/controller/docs/input-clearable.md +43 -0
- data/vendor/controller/docs/input-formatter.md +89 -0
- data/vendor/controller/docs/modal.md +57 -0
- data/vendor/controller/docs/panner.md +24 -0
- data/vendor/controller/docs/popover.md +111 -0
- data/vendor/controller/docs/progress.md +82 -0
- data/vendor/controller/docs/reorderable.md +82 -0
- data/vendor/controller/docs/timeline.md +84 -0
- data/vendor/controller/docs/visibility.md +31 -0
- data/vendor/controller/guide.md +16 -0
- data/vendor/{controllers.manifest.json → controller/manifest.json} +331 -20
- metadata +44 -2
|
@@ -8,52 +8,52 @@
|
|
|
8
8
|
|
|
9
9
|
:root {
|
|
10
10
|
/* ── Semantic colors ─────────────────────────────────────────────────── */
|
|
11
|
-
--sp-color-primary: oklch(
|
|
12
|
-
--sp-color-primary-fg: oklch(98% 0.01
|
|
13
|
-
--sp-color-primary-border: oklch(
|
|
14
|
-
--sp-color-primary-ring: oklch(
|
|
15
|
-
|
|
16
|
-
--sp-color-secondary: oklch(
|
|
17
|
-
--sp-color-secondary-fg: oklch(98% 0.01
|
|
18
|
-
--sp-color-secondary-border: oklch(
|
|
19
|
-
--sp-color-secondary-ring: oklch(
|
|
20
|
-
|
|
21
|
-
--sp-color-destructive: oklch(55% 0.
|
|
22
|
-
--sp-color-destructive-fg: oklch(98% 0.01
|
|
23
|
-
--sp-color-destructive-border: oklch(65% 0.
|
|
24
|
-
--sp-color-destructive-ring: oklch(
|
|
25
|
-
|
|
26
|
-
--sp-color-success: oklch(
|
|
27
|
-
--sp-color-success-fg: oklch(98% 0.01
|
|
28
|
-
--sp-color-success-border: oklch(
|
|
29
|
-
--sp-color-success-ring: oklch(
|
|
30
|
-
|
|
31
|
-
--sp-color-warning: oklch(68% 0.
|
|
32
|
-
--sp-color-warning-fg: oklch(
|
|
33
|
-
--sp-color-warning-border: oklch(78% 0.
|
|
34
|
-
--sp-color-warning-ring: oklch(88% 0.
|
|
35
|
-
|
|
36
|
-
--sp-color-info: oklch(55% 0.
|
|
37
|
-
--sp-color-info-fg: oklch(98% 0.01
|
|
38
|
-
--sp-color-info-border: oklch(65% 0.
|
|
39
|
-
--sp-color-info-ring: oklch(75% 0.
|
|
40
|
-
|
|
41
|
-
--sp-color-error: oklch(
|
|
42
|
-
|
|
43
|
-
--sp-color-muted: oklch(
|
|
44
|
-
--sp-color-muted-fg: oklch(45% 0.05
|
|
45
|
-
--sp-color-muted-border: oklch(88% 0.
|
|
46
|
-
--sp-color-muted-ring: oklch(82% 0.
|
|
47
|
-
|
|
48
|
-
--sp-color-border: oklch(
|
|
49
|
-
--sp-color-border-strong: oklch(75% 0.
|
|
50
|
-
--sp-color-bg: oklch(
|
|
51
|
-
--sp-color-bg-muted: oklch(
|
|
52
|
-
--sp-color-fg: oklch(
|
|
11
|
+
--sp-color-primary: oklch(50% 0.11 140);
|
|
12
|
+
--sp-color-primary-fg: oklch(98% 0.01 140);
|
|
13
|
+
--sp-color-primary-border: oklch(60% 0.11 140);
|
|
14
|
+
--sp-color-primary-ring: oklch(58% 0.15 140);
|
|
15
|
+
|
|
16
|
+
--sp-color-secondary: oklch(50% 0.03 140);
|
|
17
|
+
--sp-color-secondary-fg: oklch(98% 0.01 140);
|
|
18
|
+
--sp-color-secondary-border: oklch(60% 0.03 140);
|
|
19
|
+
--sp-color-secondary-ring: oklch(70% 0.02 140);
|
|
20
|
+
|
|
21
|
+
--sp-color-destructive: oklch(55% 0.13 30);
|
|
22
|
+
--sp-color-destructive-fg: oklch(98% 0.01 30);
|
|
23
|
+
--sp-color-destructive-border: oklch(65% 0.13 30);
|
|
24
|
+
--sp-color-destructive-ring: oklch(76% 0.09 30);
|
|
25
|
+
|
|
26
|
+
--sp-color-success: oklch(46% 0.13 165);
|
|
27
|
+
--sp-color-success-fg: oklch(98% 0.01 165);
|
|
28
|
+
--sp-color-success-border: oklch(56% 0.13 165);
|
|
29
|
+
--sp-color-success-ring: oklch(66% 0.09 165);
|
|
30
|
+
|
|
31
|
+
--sp-color-warning: oklch(68% 0.11 85);
|
|
32
|
+
--sp-color-warning-fg: oklch(20% 0.02 85);
|
|
33
|
+
--sp-color-warning-border: oklch(78% 0.11 85);
|
|
34
|
+
--sp-color-warning-ring: oklch(88% 0.07 85);
|
|
35
|
+
|
|
36
|
+
--sp-color-info: oklch(55% 0.10 210);
|
|
37
|
+
--sp-color-info-fg: oklch(98% 0.01 210);
|
|
38
|
+
--sp-color-info-border: oklch(65% 0.10 210);
|
|
39
|
+
--sp-color-info-ring: oklch(75% 0.07 210);
|
|
40
|
+
|
|
41
|
+
--sp-color-error: oklch(38% 0.16 25);
|
|
42
|
+
|
|
43
|
+
--sp-color-muted: oklch(95% 0.018 68);
|
|
44
|
+
--sp-color-muted-fg: oklch(45% 0.05 68);
|
|
45
|
+
--sp-color-muted-border: oklch(88% 0.015 68);
|
|
46
|
+
--sp-color-muted-ring: oklch(82% 0.015 68);
|
|
47
|
+
|
|
48
|
+
--sp-color-border: oklch(88% 0.012 68);
|
|
49
|
+
--sp-color-border-strong: oklch(75% 0.02 68);
|
|
50
|
+
--sp-color-bg: oklch(99% 0.005 68);
|
|
51
|
+
--sp-color-bg-muted: oklch(95% 0.015 68);
|
|
52
|
+
--sp-color-fg: oklch(22% 0.02 68);
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
/* ── Component tokens ───────────────────────────────────────────────── */
|
|
56
|
-
--sp-color-indicator: oklch(
|
|
56
|
+
--sp-color-indicator: oklch(78% 0.015 68); /* timeline dot fill */
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
/* ── Focus ring ──────────────────────────────────────────────────────── */
|
|
@@ -63,16 +63,16 @@
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
/* ── Avatar palette (10 slots, index chosen by name hash) ────────────── */
|
|
66
|
-
--sp-avatar-color-1: oklch(55% 0.
|
|
67
|
-
--sp-avatar-color-2: oklch(
|
|
68
|
-
--sp-avatar-color-3: oklch(
|
|
69
|
-
--sp-avatar-color-4: oklch(
|
|
70
|
-
--sp-avatar-color-5: oklch(
|
|
71
|
-
--sp-avatar-color-6: oklch(
|
|
72
|
-
--sp-avatar-color-7: oklch(
|
|
73
|
-
--sp-avatar-color-8: oklch(
|
|
74
|
-
--sp-avatar-color-9: oklch(
|
|
75
|
-
--sp-avatar-color-10: oklch(
|
|
66
|
+
--sp-avatar-color-1: oklch(55% 0.13 40);
|
|
67
|
+
--sp-avatar-color-2: oklch(58% 0.12 85);
|
|
68
|
+
--sp-avatar-color-3: oklch(52% 0.10 125);
|
|
69
|
+
--sp-avatar-color-4: oklch(55% 0.08 150);
|
|
70
|
+
--sp-avatar-color-5: oklch(52% 0.09 195);
|
|
71
|
+
--sp-avatar-color-6: oklch(50% 0.08 230);
|
|
72
|
+
--sp-avatar-color-7: oklch(48% 0.09 320);
|
|
73
|
+
--sp-avatar-color-8: oklch(38% 0.15 8);
|
|
74
|
+
--sp-avatar-color-9: oklch(45% 0.09 55);
|
|
75
|
+
--sp-avatar-color-10: oklch(58% 0.10 10);
|
|
76
76
|
--sp-avatar-fg: oklch(100% 0 0);
|
|
77
77
|
|
|
78
78
|
|
|
@@ -128,3 +128,8 @@
|
|
|
128
128
|
/* ── Z-index ─────────────────────────────────────────────────────────── */
|
|
129
129
|
--sp-z-popover: 50;
|
|
130
130
|
}
|
|
131
|
+
|
|
132
|
+
/* Smooth width changes on the progress bar fill — renders with or without a theme. */
|
|
133
|
+
[data-progress-target="fill"] {
|
|
134
|
+
transition: width 0.3s ease;
|
|
135
|
+
}
|
data/docs/architecture.md
CHANGED
|
@@ -9,8 +9,6 @@ Validation ranges for theme schema params live under `lib/stimulus_plumbers/them
|
|
|
9
9
|
- If a component branches on the values internally (e.g. `when *FLOATING_TYPES`), the component owns the constant (`Form::Field::FLOATING_TYPES`). The schema references it directly — no alias.
|
|
10
10
|
- If the component only passes the value through to the theme, the schema owns the range (e.g. `Schema::Button::Ranges::TYPE`).
|
|
11
11
|
|
|
12
|
-
**Namespace rules:**
|
|
13
|
-
|
|
14
12
|
**Namespaces:**
|
|
15
13
|
|
|
16
14
|
- `Schema::Ranges` — cross-cutting constants (e.g. `BOOL`)
|
data/docs/component/avatar.md
CHANGED
|
@@ -82,3 +82,4 @@ Rails helper for rendering a user avatar — image, initials, or fallback silhou
|
|
|
82
82
|
- Always renders `role="img"` with `aria-label` set to `name` (when provided).
|
|
83
83
|
- Image `alt` is set to `"[name]'s avatar"`; for anonymous avatars `alt=""` is used.
|
|
84
84
|
- `onerror` on `<img>` clears the `src` to prevent broken-image icons.
|
|
85
|
+
- See [ARIA.md's Avatar / Card / Icon pattern](../../../ARIA.md) for the decorative-vs-meaningful image rule this follows.
|
data/docs/component/button.md
CHANGED
|
@@ -107,7 +107,7 @@ Icons are rendered **inside** the `<button>`, before or after the label `<span>`
|
|
|
107
107
|
|
|
108
108
|
### Icon only
|
|
109
109
|
|
|
110
|
-
When no text is provided, no `<span>` is rendered
|
|
110
|
+
When no text is provided, no `<span>` is rendered, and the button becomes square (or a circle for `type: :fab` / `:fab_outline`):
|
|
111
111
|
|
|
112
112
|
```html
|
|
113
113
|
<button type="button" aria-label="Add" class="[theme classes]">
|
|
@@ -133,3 +133,4 @@ When no text is provided, no `<span>` is rendered. The theme uses `:has(> span)`
|
|
|
133
133
|
- `<button>` always has `type="button"` to prevent accidental form submission.
|
|
134
134
|
- Icon-only buttons must supply an accessible label via `aria: { label: "..." }` in `html_options`.
|
|
135
135
|
- For navigation actions (links), use `sp_link` instead — it renders an `<a>` element.
|
|
136
|
+
- See [ARIA.md's Button pattern](../../../ARIA.md) for the `disabled` attribute requirement.
|
data/docs/component/calendar.md
CHANGED
|
@@ -33,10 +33,10 @@ end
|
|
|
33
33
|
</div>
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
| Option | Description
|
|
37
|
-
| ---------------- |
|
|
38
|
-
| `date` | `Date` — sets `year-value`, `month-value` (0-indexed)
|
|
39
|
-
| `**html_options` | Forwarded to the `calendar-month` controller root element
|
|
36
|
+
| Option | Description |
|
|
37
|
+
| ---------------- | ----------------------------------------------------------------------- |
|
|
38
|
+
| `date` | `Date` — sets `year-value`, `month-value` (0-indexed) on the controller |
|
|
39
|
+
| `**html_options` | Forwarded to the `calendar-month` controller root element |
|
|
40
40
|
|
|
41
41
|
For the JS controller API (targets, values, keyboard behaviour), see the [JS package docs](../../../stimulus-plumbers/docs/component/calendar.md).
|
|
42
42
|
|
|
@@ -50,7 +50,6 @@ For the JS controller API (targets, values, keyboard behaviour), see the [JS pac
|
|
|
50
50
|
data-controller="calendar-month"
|
|
51
51
|
data-calendar-month-year-value="2024"
|
|
52
52
|
data-calendar-month-month-value="1"
|
|
53
|
-
data-calendar-month-day-value="15"
|
|
54
53
|
role="grid"
|
|
55
54
|
>
|
|
56
55
|
<!-- days-of-week header row (JS-populated) -->
|
|
@@ -94,10 +93,17 @@ Individual view helpers render a single view (used for Turbo Frame responses). T
|
|
|
94
93
|
<%= sp_calendar_turbo_decade(date: @date, today: @today) %>
|
|
95
94
|
```
|
|
96
95
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
|
100
|
-
|
|
|
96
|
+
**`sp_calendar_turbo_month`** — extra option:
|
|
97
|
+
|
|
98
|
+
| Option | Description |
|
|
99
|
+
| ---------------- | ------------------------------------------------------------------ |
|
|
100
|
+
| `weekday_format` | `:short` (default) \| `:long` \| `:narrow` — weekday header format |
|
|
101
|
+
|
|
102
|
+
**`sp_calendar_turbo_year`** — extra option:
|
|
103
|
+
|
|
104
|
+
| Option | Description |
|
|
105
|
+
| -------------- | ---------------------------------------------------------------- |
|
|
106
|
+
| `month_format` | `:short` (default) \| `:long` \| `:narrow` — month button format |
|
|
101
107
|
|
|
102
108
|
**Common options** (all helpers and `sp_calendar_turbo`):
|
|
103
109
|
|
|
@@ -199,25 +205,17 @@ Renders all three frames. Used inside a `combobox-date` controller for drill-dow
|
|
|
199
205
|
|
|
200
206
|
```html
|
|
201
207
|
<!-- month view frame — visible by default -->
|
|
202
|
-
<turbo-frame id="calendar-month-frame"
|
|
208
|
+
<turbo-frame id="calendar-month-frame">
|
|
203
209
|
<!-- sp_calendar_turbo_month output -->
|
|
204
210
|
</turbo-frame>
|
|
205
211
|
|
|
206
212
|
<!-- year view frame — hidden until drill-up -->
|
|
207
|
-
<turbo-frame
|
|
208
|
-
id="calendar-year-frame"
|
|
209
|
-
hidden
|
|
210
|
-
data-combobox-date-target="yearView"
|
|
211
|
-
>
|
|
213
|
+
<turbo-frame id="calendar-year-frame" hidden>
|
|
212
214
|
<!-- sp_calendar_turbo_year output -->
|
|
213
215
|
</turbo-frame>
|
|
214
216
|
|
|
215
217
|
<!-- decade view frame — hidden until drill-up twice -->
|
|
216
|
-
<turbo-frame
|
|
217
|
-
id="calendar-decade-frame"
|
|
218
|
-
hidden
|
|
219
|
-
data-combobox-date-target="decadeView"
|
|
220
|
-
>
|
|
218
|
+
<turbo-frame id="calendar-decade-frame" hidden>
|
|
221
219
|
<!-- sp_calendar_turbo_decade output -->
|
|
222
220
|
</turbo-frame>
|
|
223
221
|
```
|
data/docs/component/card.md
CHANGED
|
@@ -63,10 +63,12 @@ Rails helper for rendering a themed card with optional icon, title, body, and ac
|
|
|
63
63
|
<!-- body -->
|
|
64
64
|
<div class="[card_body theme classes]">Your account is active.</div>
|
|
65
65
|
|
|
66
|
-
<!-- action -->
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
<!-- action: a ghost/tertiary sp_button, with card_action classes merged directly onto it (no wrapping div) -->
|
|
67
|
+
<a
|
|
68
|
+
href="/settings"
|
|
69
|
+
class="[button ghost/tertiary + card_action theme classes]"
|
|
70
|
+
><span>Manage</span></a
|
|
71
|
+
>
|
|
70
72
|
</div>
|
|
71
73
|
```
|
|
72
74
|
|
|
@@ -74,14 +76,14 @@ Rails helper for rendering a themed card with optional icon, title, body, and ac
|
|
|
74
76
|
|
|
75
77
|
## Theme keys
|
|
76
78
|
|
|
77
|
-
| Key | Element
|
|
78
|
-
| ------------- |
|
|
79
|
-
| `card` | Outer `<div>`
|
|
80
|
-
| `card_header` | Header wrapper `<div>` (icon+title)
|
|
81
|
-
| `card_icon` | Icon inside the header
|
|
82
|
-
| `card_title` | Title element (`h2` etc.)
|
|
83
|
-
| `card_body` | Body `<div>`
|
|
84
|
-
| `card_action` |
|
|
79
|
+
| Key | Element | Variants |
|
|
80
|
+
| ------------- | ------------------------------------------------------------- | --------------------------------------------- |
|
|
81
|
+
| `card` | Outer `<div>` | `variant: :primary\|:secondary\|:tertiary\|…` |
|
|
82
|
+
| `card_header` | Header wrapper `<div>` (icon+title) | — |
|
|
83
|
+
| `card_icon` | Icon inside the header | — |
|
|
84
|
+
| `card_title` | Title element (`h2` etc.) | — |
|
|
85
|
+
| `card_body` | Body `<div>` | — |
|
|
86
|
+
| `card_action` | Merged onto the action `<a>`/`<button>` (no wrapping element) | — |
|
|
85
87
|
|
|
86
88
|
---
|
|
87
89
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Checklist
|
|
2
|
+
|
|
3
|
+
Rails helper for rendering an accessible group of checkbox-style items. Items render as native `<label><input type="checkbox"></label>` pairs, no `<li>` wrapper — see [ARIA.md's Checklist pattern](../../../ARIA.md) for why.
|
|
4
|
+
|
|
5
|
+
## Helper
|
|
6
|
+
|
|
7
|
+
### `sp_checklist`
|
|
8
|
+
|
|
9
|
+
```erb
|
|
10
|
+
<%= sp_checklist(label: "Groceries") do |checklist| %>
|
|
11
|
+
<%= checklist.item("Buy milk", checked: true) %>
|
|
12
|
+
<%= checklist.item("Walk the dog", checked: false) %>
|
|
13
|
+
<% end %>
|
|
14
|
+
|
|
15
|
+
<%# With a description %>
|
|
16
|
+
<%= sp_checklist(label: "Onboarding") do |checklist| %>
|
|
17
|
+
<%= checklist.item("Verify email", checked: true) do |item| %>
|
|
18
|
+
<% item.with_description("Sent to you at signup") %>
|
|
19
|
+
<% end %>
|
|
20
|
+
<% end %>
|
|
21
|
+
|
|
22
|
+
<%# Read-only summary (e.g. an activity feed) %>
|
|
23
|
+
<%= sp_checklist(label: "Completed steps") do |checklist| %>
|
|
24
|
+
<%= checklist.item("Account created", checked: true, readonly: true) %>
|
|
25
|
+
<% end %>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Option | Default | Description |
|
|
29
|
+
| ------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `label:` | `nil` | Sets `aria-label` on the group wrapper |
|
|
31
|
+
| `labelledby:` | `nil` | Sets `aria-labelledby`; takes precedence over `label:` when both given |
|
|
32
|
+
| `select_all:` | `false` | `true` renders a master "select all" toggle wired to the group's items |
|
|
33
|
+
| `select_all_label:` | `"Select all"` | Label text for the master toggle (used as both `aria-label` and visible text); ignored unless `select_all: true` |
|
|
34
|
+
| `**html_options` | — | Forwarded to the `<div role="group">` |
|
|
35
|
+
|
|
36
|
+
### `select_all:`
|
|
37
|
+
|
|
38
|
+
```erb
|
|
39
|
+
<%= sp_checklist(label: "Groceries", select_all: true, select_all_label: "Select all items") do |checklist| %>
|
|
40
|
+
<%= checklist.item("Buy milk", checked: true) %>
|
|
41
|
+
<%= checklist.item("Walk the dog", checked: false) %>
|
|
42
|
+
<% end %>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Renders a master `<label><input type="checkbox"></label>` before the items, wired to the `checklist` Stimulus controller. See [stimulus-plumbers's docs/component/checklist.md](../../../stimulus-plumbers/docs/component/checklist.md) for the controller's targets and actions.
|
|
46
|
+
|
|
47
|
+
Read-only items (`readonly: true`) are excluded from the master's aggregate state — see [ARIA.md's Checklist pattern](../../../ARIA.md) for why.
|
|
48
|
+
|
|
49
|
+
### `checklist.item(content, checked:, readonly:, **html_options, &block)`
|
|
50
|
+
|
|
51
|
+
Renders a `<label>` wrapping a native `<input type="checkbox">`.
|
|
52
|
+
|
|
53
|
+
| Option | Default | Description |
|
|
54
|
+
| ---------------- | ------- | ------------------------------------------------------------------------------------ |
|
|
55
|
+
| `content` | `nil` | Item label — positional arg or via `item.with_title` |
|
|
56
|
+
| `checked:` | — | **Required.** Sets `checked` |
|
|
57
|
+
| `readonly:` | `false` | `true` renders **disabled** on the input — removed from the tab order, no controller |
|
|
58
|
+
| `**html_options` | — | Forwarded to the `<label>` |
|
|
59
|
+
|
|
60
|
+
### Item slot methods (yielded as `item`)
|
|
61
|
+
|
|
62
|
+
| Slot method | Description |
|
|
63
|
+
| ----------------------------- | ------------------------------------------------------------- |
|
|
64
|
+
| `item.with_title(text)` | Title text (pre-populated when positional `content` is given) |
|
|
65
|
+
| `item.with_description(text)` | Secondary text below the title |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Rendered HTML Structure
|
|
70
|
+
|
|
71
|
+
```html
|
|
72
|
+
<div role="group" aria-label="Groceries" class="[checklist theme classes]">
|
|
73
|
+
<label class="[checklist_item theme classes]">
|
|
74
|
+
<input
|
|
75
|
+
type="checkbox"
|
|
76
|
+
checked
|
|
77
|
+
data-checklist-target="item"
|
|
78
|
+
class="[checklist_item_input theme classes]"
|
|
79
|
+
/>
|
|
80
|
+
<span class="[checklist_item_content theme classes]">
|
|
81
|
+
<span class="[checklist_item_title theme classes]">Buy milk</span>
|
|
82
|
+
</span>
|
|
83
|
+
</label>
|
|
84
|
+
</div>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `select_all: true`
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<div
|
|
91
|
+
role="group"
|
|
92
|
+
aria-label="Groceries"
|
|
93
|
+
data-controller="checklist"
|
|
94
|
+
data-action="change->checklist#onChange"
|
|
95
|
+
class="[checklist theme classes]"
|
|
96
|
+
>
|
|
97
|
+
<label class="[checklist_item theme classes]">
|
|
98
|
+
<input
|
|
99
|
+
type="checkbox"
|
|
100
|
+
data-checklist-target="master"
|
|
101
|
+
class="[checklist_item_input theme classes]"
|
|
102
|
+
/>
|
|
103
|
+
Select all
|
|
104
|
+
</label>
|
|
105
|
+
|
|
106
|
+
<label class="[checklist_item theme classes]">
|
|
107
|
+
<input
|
|
108
|
+
type="checkbox"
|
|
109
|
+
checked
|
|
110
|
+
data-checklist-target="item"
|
|
111
|
+
class="[checklist_item_input theme classes]"
|
|
112
|
+
/>
|
|
113
|
+
...
|
|
114
|
+
</label>
|
|
115
|
+
</div>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The master's `checked` state is computed from the items' `checked:` values at render time (all-true renders `checked`; every other case, including mixed, renders unchecked) — the `checklist` controller corrects it to `indeterminate` client-side once connected. See [ARIA.md's Checklist pattern](../../../ARIA.md) for the accepted flash this tradeoff causes in the mixed case.
|
|
119
|
+
|
|
120
|
+
### Read-only item (`readonly: true`)
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<label class="[checklist_item theme classes]">
|
|
124
|
+
<input
|
|
125
|
+
type="checkbox"
|
|
126
|
+
checked
|
|
127
|
+
disabled
|
|
128
|
+
class="[checklist_item_input theme classes]"
|
|
129
|
+
/>
|
|
130
|
+
...
|
|
131
|
+
</label>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Theme keys
|
|
137
|
+
|
|
138
|
+
| Key | Element | Variants |
|
|
139
|
+
| ---------------------------- | ---------------------------------------------- | -------- |
|
|
140
|
+
| `checklist` | Outer `<div role="group">` | — |
|
|
141
|
+
| `checklist_item` | Item `<label>` | — |
|
|
142
|
+
| `checklist_item_input` | Item/master checkbox `<input>` | — |
|
|
143
|
+
| `checklist_item_content` | Content wrapper `<span>` (title + description) | — |
|
|
144
|
+
| `checklist_item_title` | Title `<span>` | — |
|
|
145
|
+
| `checklist_item_description` | Description `<span>` | — |
|
|
146
|
+
|
|
147
|
+
Checked-state styling (strikethrough title) reads the sibling `<input>`'s `:checked` state via Tailwind's `:has()`-based `group-has-checked/checklist-item:` variant — there is no `aria-checked` or `checked:` theme-resolver kwarg to pass.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## ARIA
|
|
152
|
+
|
|
153
|
+
See [ARIA.md's Checklist pattern](../../../ARIA.md) for why items use native `<input type="checkbox">` with no ARIA attributes, the `disabled` read-only behavior, and the `indeterminate` tradeoff for the master toggle.
|
data/docs/component/combobox.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Rails helpers that render fully-wired combobox components. Each variant shares the same wrapper structure (`input-combobox` + `input-formatter`) and differs only in its popover body.
|
|
4
4
|
|
|
5
|
-
See [docs/component/combobox.md](
|
|
5
|
+
See [docs/component/combobox.md](../../../stimulus-plumbers/docs/component/combobox.md) in the JS package for the underlying controller API.
|
|
6
6
|
|
|
7
7
|
## Helpers
|
|
8
8
|
|
|
@@ -127,7 +127,7 @@ iOS-style drum/scroll-wheel time picker.
|
|
|
127
127
|
|
|
128
128
|
## Form Builder
|
|
129
129
|
|
|
130
|
-
`StimulusPlumbers::Form::Builder` exposes combobox-backed fields through purpose-specific methods rather than a single generic helper. See [
|
|
130
|
+
`StimulusPlumbers::Form::Builder` exposes combobox-backed fields through purpose-specific methods rather than a single generic helper. See [form.md](form.md) for the full API.
|
|
131
131
|
|
|
132
132
|
```erb
|
|
133
133
|
<%= form_with model: @user, builder: StimulusPlumbers::Form::Builder do |f| %>
|
data/docs/component/form.md
CHANGED
|
@@ -118,9 +118,34 @@ Three methods render a complete, accessible field:
|
|
|
118
118
|
|
|
119
119
|
Use `f.time_field` for a plain `<input type="time">`.
|
|
120
120
|
|
|
121
|
-
**Select** (`as: :select`) — read-only listbox backed by `combobox-dropdown`.
|
|
121
|
+
**Select** (`as: :select`) — read-only listbox backed by `combobox-dropdown`.
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
| Option | Values | Default | Description |
|
|
124
|
+
| --------------- | -------------- | ------- | ------------------------------------------------------------- |
|
|
125
|
+
| `choices` | Array | `[]` | `[label, value]` pairs, or a flat array of strings |
|
|
126
|
+
| `include_blank` | Boolean/String | `nil` | Prepends a blank option; String overrides its label |
|
|
127
|
+
| `prompt` | Boolean/String | `nil` | Prepends a disabled placeholder option |
|
|
128
|
+
| `selected` | Value | `nil` | Pre-selected value; defaults to the attribute's current value |
|
|
129
|
+
|
|
130
|
+
Use `f.select` for a native `<select>`.
|
|
131
|
+
|
|
132
|
+
**Search** (`as: :search`) — editable typeahead backed by `combobox-dropdown`.
|
|
133
|
+
|
|
134
|
+
| Option | Values | Default | Description |
|
|
135
|
+
| ----------- | ------- | ------- | ----------------------------------------------------------------------------------- |
|
|
136
|
+
| `choices` | Array | `[]` | Client-side options to filter; omit when using `url:` |
|
|
137
|
+
| `url` | String | `nil` | Server-side endpoint, receives `?q=<query>`, returns `<li role="option">` fragments |
|
|
138
|
+
| `clearable` | Boolean | `false` | Adds a clear button that resets the input |
|
|
139
|
+
|
|
140
|
+
Use `f.search_field` for a native `<input type="search">`.
|
|
141
|
+
|
|
142
|
+
**Password** (`as: :password`) — reveal-toggle wrapper backed by `input-formatter`.
|
|
143
|
+
|
|
144
|
+
| Option | Values | Default | Description |
|
|
145
|
+
| ------------ | ------- | ------- | ------------------------------------------- |
|
|
146
|
+
| `revealable` | Boolean | `false` | Adds a show/hide toggle button on the input |
|
|
147
|
+
|
|
148
|
+
Use `f.password_field` for a plain `<input type="password">` (also accepts `revealable:`).
|
|
124
149
|
|
|
125
150
|
---
|
|
126
151
|
|
|
@@ -169,9 +194,9 @@ Renders a `<fieldset>` / `<legend>` for accessible grouping, or a single checkbo
|
|
|
169
194
|
|
|
170
195
|
**Card / button behaviour:**
|
|
171
196
|
|
|
172
|
-
- **Checkbox card** — input visible on right; card border
|
|
173
|
-
- **Radio card** — input
|
|
174
|
-
- **Radio button** — input
|
|
197
|
+
- **Checkbox card** — input visible on the right; the card border highlights when checked
|
|
198
|
+
- **Radio card** — input visually hidden; the whole card is the clickable target and highlights when selected
|
|
199
|
+
- **Radio button** — input visually hidden; renders as an inline pill that highlights when selected
|
|
175
200
|
|
|
176
201
|
---
|
|
177
202
|
|
|
@@ -185,7 +210,8 @@ Renders a `<fieldset>` / `<legend>` for accessible grouping, or a single checkbo
|
|
|
185
210
|
<input
|
|
186
211
|
id="user_email"
|
|
187
212
|
type="email"
|
|
188
|
-
aria-describedby="user_email_hint"
|
|
213
|
+
aria-describedby="user_email_hint user_email_error"
|
|
214
|
+
aria-invalid="true"
|
|
189
215
|
required
|
|
190
216
|
aria-required="true"
|
|
191
217
|
/>
|
data/docs/component/icon.md
CHANGED
|
@@ -10,12 +10,14 @@ Rails helper for rendering a named icon from the active theme's icon registry.
|
|
|
10
10
|
<%= sp_icon name: "check" %>
|
|
11
11
|
<%= sp_icon name: "spinner", aria: { label: "Loading" }, role: "img" %>
|
|
12
12
|
<%= sp_icon name: "unknown-icon" %>
|
|
13
|
+
<%= sp_icon name: "check", size: :sm %>
|
|
13
14
|
```
|
|
14
15
|
|
|
15
|
-
| Option | Default | Description
|
|
16
|
-
| ---------------- | ---------- |
|
|
17
|
-
| `name` | (required) | Icon name — looked up in the theme registry
|
|
18
|
-
|
|
|
16
|
+
| Option | Default | Description |
|
|
17
|
+
| ---------------- | ---------- | --------------------------------------------------- |
|
|
18
|
+
| `name` | (required) | Icon name — looked up in the theme registry |
|
|
19
|
+
| `size` | `:lg` | `:sm` \| `:md` \| `:lg` — theme-resolved size class |
|
|
20
|
+
| `**html_options` | — | Forwarded to the root element |
|
|
19
21
|
|
|
20
22
|
---
|
|
21
23
|
|
|
@@ -39,6 +41,6 @@ Rails helper for rendering a named icon from the active theme's icon registry.
|
|
|
39
41
|
|
|
40
42
|
## ARIA
|
|
41
43
|
|
|
42
|
-
-
|
|
44
|
+
- The theme applies `aria-hidden="true"` by default (see [ARIA.md's Avatar / Card / Icon pattern](../../../ARIA.md)).
|
|
43
45
|
- For meaningful icons (standalone, no adjacent label), pass `aria: { label: "..." }` and `role: "img"` to override.
|
|
44
46
|
- Button and link icons are rendered internally with `aria-hidden="true"`; callers do not need to set this manually.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Indicator
|
|
2
|
+
|
|
3
|
+
Presentational status marker — a colored dot, an animated "pulse" ring, or a numeric badge. No Stimulus controller.
|
|
4
|
+
|
|
5
|
+
## Helper
|
|
6
|
+
|
|
7
|
+
### `sp_indicator`
|
|
8
|
+
|
|
9
|
+
```erb
|
|
10
|
+
<%# Dot — must be paired with an accessible name %>
|
|
11
|
+
<span>
|
|
12
|
+
<%= sp_indicator(variant: :success) %>
|
|
13
|
+
<span class="sr-only">Online</span>
|
|
14
|
+
</span>
|
|
15
|
+
|
|
16
|
+
<%# Pulse %>
|
|
17
|
+
<span>
|
|
18
|
+
<%= sp_indicator(type: :pulse, variant: :destructive, pulse: true) %>
|
|
19
|
+
<span class="sr-only">Live</span>
|
|
20
|
+
</span>
|
|
21
|
+
|
|
22
|
+
<%# Badge %>
|
|
23
|
+
<span>
|
|
24
|
+
<%= sp_indicator(type: :badge, variant: :primary) { "5" } %>
|
|
25
|
+
<span class="sr-only">5 unread notifications</span>
|
|
26
|
+
</span>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Option | Default | Description |
|
|
30
|
+
| ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
31
|
+
| `type:` | `:dot` | `:dot` \| `:pulse` \| `:badge` — `:badge` renders the given block as content |
|
|
32
|
+
| `variant:` | `:primary` | Semantic color token — `:primary` \| `:secondary` \| `:tertiary` \| `:success` \| `:destructive` \| `:warning` \| `:info` |
|
|
33
|
+
| `pulse:` | `false` | Adds an animated ring element (`prefers-reduced-motion` suppresses the animation) |
|
|
34
|
+
| `**html_options` | — | Forwarded to the dot `<span>` |
|
|
35
|
+
|
|
36
|
+
When `pulse: true`, the dot is wrapped in an extra `<span>` (`indicator_wrapper` theme key) that positions the ring behind the dot — no margin offsets involved.
|
|
37
|
+
|
|
38
|
+
**Every indicator must be paired with an accessible name** — a visible label or `aria-label`/adjacent `sr-only` text. The component itself renders no text and cannot know the right label; this is enforced by an accessibility test (see `test/accessibility/components/indicator_accessibility_test.rb`), not by the component.
|
|
39
|
+
|
|
40
|
+
## Legend pattern
|
|
41
|
+
|
|
42
|
+
To explain what each indicator color means (e.g. a status legend), pair indicators with visible text inside a list — do not rely on color alone:
|
|
43
|
+
|
|
44
|
+
```erb
|
|
45
|
+
<%= sp_list do |list| %>
|
|
46
|
+
<%= list.item do |item| %>
|
|
47
|
+
<% item.with_icon_leading { sp_indicator(variant: :success) } %>
|
|
48
|
+
<% item.with_title("Online") %>
|
|
49
|
+
<% end %>
|
|
50
|
+
<%= list.item do |item| %>
|
|
51
|
+
<% item.with_icon_leading { sp_indicator(variant: :warning) } %>
|
|
52
|
+
<% item.with_title("Away") %>
|
|
53
|
+
<% end %>
|
|
54
|
+
<% end %>
|
|
55
|
+
```
|
data/docs/component/list.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# List
|
|
2
2
|
|
|
3
|
-
Rails helper for rendering an accessible list of links, buttons, or grouped sections.
|
|
3
|
+
Rails helper for rendering an accessible list of links, buttons, or grouped sections. For a flat, drag/keyboard-reorderable list, see [`OrderedList`](ordered_list.md) instead.
|
|
4
4
|
|
|
5
5
|
## Helper
|
|
6
6
|
|
|
@@ -39,7 +39,7 @@ Rails helper for rendering an accessible list of links, buttons, or grouped sect
|
|
|
39
39
|
| `role:` | `"list"` | ARIA role on the `<ul>` — use `"menu"` for interactive menus |
|
|
40
40
|
| `**html_options` | — | Forwarded to the `<ul>` |
|
|
41
41
|
|
|
42
|
-
### `list.section(title:, description:, **html_options)`
|
|
42
|
+
### `list.section(title:, description:, **html_options, &block)`
|
|
43
43
|
|
|
44
44
|
Renders a `<li>` with an optional heading/description and a nested `<ul>`.
|
|
45
45
|
|
|
@@ -60,7 +60,7 @@ Renders a `<li>` containing an `<a>` (when `url:` present) or `<button>`.
|
|
|
60
60
|
| `target:` | `nil` | Forwarded to the `<a>` (e.g. `"_blank"`) |
|
|
61
61
|
| `**html_options` | — | Forwarded to the inner `<a>` or `<button>` |
|
|
62
62
|
|
|
63
|
-
When `target: "_blank"` is set, `icon_trailing: "external-link"` is added automatically.
|
|
63
|
+
When `url:` is present and `target: "_blank"` is set, `icon_trailing: "external-link"` is added automatically.
|
|
64
64
|
|
|
65
65
|
### Item slot methods (yielded as `item`)
|
|
66
66
|
|
|
@@ -154,8 +154,7 @@ When `target: "_blank"` is set, `icon_trailing: "external-link"` is added automa
|
|
|
154
154
|
|
|
155
155
|
## ARIA
|
|
156
156
|
|
|
157
|
-
- Default `role="list"` is appropriate for navigation or command lists.
|
|
158
157
|
- Use `role="menu"` + `role="menuitem"` (via `html_options`) only when items form a widget menu — arrow key navigation must then be wired via a Stimulus controller.
|
|
159
|
-
- `active: true` adds `aria-current="page"` on links and `aria-current="true"` on buttons; pair this with a visual style change so the state is not communicated by color alone.
|
|
160
158
|
- Section titles without `heading_level` use `aria-hidden="true"` on the `<span>` and set `aria-label` on the nested `<ul>` to avoid double-announcing.
|
|
161
159
|
- Nested sections increment the heading level automatically (clamped at `h6`).
|
|
160
|
+
- See [ARIA.md's List pattern](../../../ARIA.md) for the default `role="list"`/`role="listitem"` and `active:` → `aria-current` mapping (pair it with a visual style change so the state isn't communicated by color alone).
|