stimulus_plumbers 0.4.8 → 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 +16 -0
- data/README.md +7 -9
- 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 +1 -0
- data/docs/component/calendar.md +7 -16
- data/docs/component/checklist.md +153 -0
- data/docs/component/icon.md +7 -5
- data/docs/component/indicator.md +55 -0
- data/docs/component/list.md +2 -3
- data/docs/component/ordered_list.md +118 -0
- data/docs/component/plumber.md +21 -0
- data/docs/component/popover.md +1 -2
- data/docs/component/progress.md +65 -0
- data/docs/component/timeline.md +47 -12
- data/docs/guide.md +12 -0
- data/lib/generators/stimulus_plumbers/install/install_generator.rb +16 -12
- data/lib/stimulus_plumbers/components/button.rb +1 -0
- 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/icon.rb +2 -2
- data/lib/stimulus_plumbers/components/indicator.rb +30 -0
- data/lib/stimulus_plumbers/components/link.rb +1 -0
- 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 +2 -1
- data/lib/stimulus_plumbers/components/timeline/group.rb +5 -3
- 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 +15 -9
- data/lib/stimulus_plumbers/generators/tokens_directive.rb +6 -2
- 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/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/vendor/ARIA.md +22 -2
- data/vendor/component/manifest.json +212 -0
- data/vendor/controller/docs/calendar.md +4 -7
- data/vendor/controller/docs/checklist.md +45 -0
- data/vendor/controller/docs/combobox.md +1 -31
- data/vendor/controller/docs/input-clearable.md +1 -4
- data/vendor/controller/docs/input-formatter.md +1 -2
- data/vendor/controller/docs/modal.md +1 -4
- data/vendor/controller/docs/popover.md +1 -3
- data/vendor/controller/docs/progress.md +82 -0
- data/vendor/controller/docs/reorderable.md +82 -0
- data/vendor/controller/docs/visibility.md +31 -0
- data/vendor/controller/manifest.json +331 -20
- metadata +25 -1
|
@@ -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
|
@@ -133,3 +133,4 @@ When no text is provided, no `<span>` is rendered, and the button becomes square
|
|
|
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) -->
|
|
@@ -206,25 +205,17 @@ Renders all three frames. Used inside a `combobox-date` controller for drill-dow
|
|
|
206
205
|
|
|
207
206
|
```html
|
|
208
207
|
<!-- month view frame — visible by default -->
|
|
209
|
-
<turbo-frame id="calendar-month-frame"
|
|
208
|
+
<turbo-frame id="calendar-month-frame">
|
|
210
209
|
<!-- sp_calendar_turbo_month output -->
|
|
211
210
|
</turbo-frame>
|
|
212
211
|
|
|
213
212
|
<!-- year view frame — hidden until drill-up -->
|
|
214
|
-
<turbo-frame
|
|
215
|
-
id="calendar-year-frame"
|
|
216
|
-
hidden
|
|
217
|
-
data-combobox-date-target="yearView"
|
|
218
|
-
>
|
|
213
|
+
<turbo-frame id="calendar-year-frame" hidden>
|
|
219
214
|
<!-- sp_calendar_turbo_year output -->
|
|
220
215
|
</turbo-frame>
|
|
221
216
|
|
|
222
217
|
<!-- decade view frame — hidden until drill-up twice -->
|
|
223
|
-
<turbo-frame
|
|
224
|
-
id="calendar-decade-frame"
|
|
225
|
-
hidden
|
|
226
|
-
data-combobox-date-target="decadeView"
|
|
227
|
-
>
|
|
218
|
+
<turbo-frame id="calendar-decade-frame" hidden>
|
|
228
219
|
<!-- sp_calendar_turbo_decade output -->
|
|
229
220
|
</turbo-frame>
|
|
230
221
|
```
|
|
@@ -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/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
|
|
|
@@ -154,8 +154,7 @@ When `url:` is present and `target: "_blank"` is set, `icon_trailing: "external-
|
|
|
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).
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# OrderedList
|
|
2
|
+
|
|
3
|
+
Rails helper for a flat, reorderable list — pointer-drag and keyboard reorder via the [`reorderable`](../../../stimulus-plumbers/docs/component/reorderable.md) JS controller. Unlike [`List`](list.md), item order is semantic content (`<ol>`, not `<ul>`), there are no sections, and every item has a pointer drag surface.
|
|
4
|
+
|
|
5
|
+
## Helper
|
|
6
|
+
|
|
7
|
+
### `sp_ordered_list`
|
|
8
|
+
|
|
9
|
+
```erb
|
|
10
|
+
<%# Whole-item handle (default), content-only rows %>
|
|
11
|
+
<%= sp_ordered_list do |list| %>
|
|
12
|
+
<%= list.item("First", id: "item-1") %>
|
|
13
|
+
<%= list.item("Second", id: "item-2") %>
|
|
14
|
+
<% end %>
|
|
15
|
+
|
|
16
|
+
<%# Links + a dedicated leading-icon handle, editing enabled %>
|
|
17
|
+
<%= sp_ordered_list(editing: true) do |list| %>
|
|
18
|
+
<%= list.item(id: "link-1", url: "/", handle: :leading) do |item| %>
|
|
19
|
+
<% item.with_title("Dashboard") %>
|
|
20
|
+
<% end %>
|
|
21
|
+
<% end %>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
| Option | Default | Description |
|
|
25
|
+
| ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
+
| `move_key:` | `"Alt"` | Maps to `data-reorderable-move-key-value`. One of `Alt`, `Control`, `Shift`, `Meta`. |
|
|
27
|
+
| `editing:` | `false` | Initial render-time state, maps to `data-reorderable-editing-value`. |
|
|
28
|
+
| `orientation:` | `nil` | Maps to `data-reorderable-orientation-value` when set; omitted entirely otherwise (JS controller defaults to `"vertical"`). See the [reorderable JS controller doc](../../../stimulus-plumbers/docs/component/reorderable.md) for `"horizontal"` semantics. |
|
|
29
|
+
| `role:` | `"list"` | ARIA role on the `<ol>` — preserves list semantics when consumer CSS resets `list-style`. |
|
|
30
|
+
| `**html_options` | — | Forwarded to the outer `<ol>`. |
|
|
31
|
+
|
|
32
|
+
### `list.item(content, id:, handle:, url:, target:, active:, **html_options, &block)`
|
|
33
|
+
|
|
34
|
+
| Option | Default | Description |
|
|
35
|
+
| ---------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| `content` | `nil` | Item label — positional arg or via `item.with_title`. |
|
|
37
|
+
| `id:` | — | **Required.** Raises `ArgumentError` if missing. |
|
|
38
|
+
| `handle:` | `:item` | `:item` (whole `<li>` is the drag surface) \| `:leading` \| `:trailing` (that icon position is). |
|
|
39
|
+
| `url:` | `nil` | Renders `<a href>` around the title/description. Without it, content renders with no wrapper (no click target at all — no `<button>` fallback, unlike `List::Item`). |
|
|
40
|
+
| `target:` | `nil` | Forwarded to the `<a>` (e.g. `"_blank"`), only used when `url:` is set. |
|
|
41
|
+
| `active:` | `false` | Adds `aria-current="page"`, only used when `url:` is set. |
|
|
42
|
+
| `**html_options` | — | Forwarded to the `<a>` when `url:` is set. Raises `ArgumentError` if given without `url:` — there is no element for them to land on. |
|
|
43
|
+
|
|
44
|
+
### Item slot methods (yielded as `item`)
|
|
45
|
+
|
|
46
|
+
| Slot method | Description |
|
|
47
|
+
| ------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
48
|
+
| `item.with_icon_leading(name)` | Icon at the leading position. Becomes the drag handle if `handle: :leading`; otherwise purely decorative. |
|
|
49
|
+
| `item.with_title(text)` | Title text (pre-populated when positional `content` is given). |
|
|
50
|
+
| `item.with_description(text)` | Secondary text below the title. |
|
|
51
|
+
| `item.with_icon_trailing(name)` | Icon at the trailing position. Becomes the drag handle if `handle: :trailing`; otherwise purely decorative. |
|
|
52
|
+
|
|
53
|
+
If `handle: :leading`/`:trailing` is set and the corresponding icon slot isn't, a default `grip-vertical` glyph renders there instead.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Rendered HTML Structure
|
|
58
|
+
|
|
59
|
+
### `handle: :item` (default), no link
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<ol
|
|
63
|
+
role="list"
|
|
64
|
+
data-controller="reorderable"
|
|
65
|
+
data-reorderable-move-key-value="Alt"
|
|
66
|
+
data-reorderable-editing-value="false"
|
|
67
|
+
>
|
|
68
|
+
<li
|
|
69
|
+
id="item-1"
|
|
70
|
+
data-reorderable-target="item handle"
|
|
71
|
+
data-action="pointerdown->reorderable#onPointerDown pointermove->reorderable#onPointerMove pointerup->reorderable#onPointerUp"
|
|
72
|
+
>
|
|
73
|
+
First
|
|
74
|
+
</li>
|
|
75
|
+
</ol>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `handle: :leading`, with a link
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<li id="link-1" data-reorderable-target="item">
|
|
82
|
+
<span
|
|
83
|
+
data-reorderable-target="handle"
|
|
84
|
+
data-action="pointerdown->reorderable#onPointerDown ..."
|
|
85
|
+
>
|
|
86
|
+
<svg aria-hidden="true">...</svg>
|
|
87
|
+
<!-- grip-vertical, or a custom icon if item.with_icon_leading was set -->
|
|
88
|
+
</span>
|
|
89
|
+
<a href="/" data-reorderable-target="trigger">
|
|
90
|
+
<span>
|
|
91
|
+
<span>Dashboard</span>
|
|
92
|
+
<span>Overview</span>
|
|
93
|
+
</span>
|
|
94
|
+
</a>
|
|
95
|
+
</li>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Icon positions (leading/trailing) are always siblings of the `<a>`, never nested inside it — this is what lets a real link coexist with drag reordering without one accidentally triggering the other. See [reorderable's editing mode](../../../stimulus-plumbers/docs/component/reorderable.md) for how the link itself is neutralized while editing.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Theme keys
|
|
103
|
+
|
|
104
|
+
| Key | Element | Variants |
|
|
105
|
+
| ------------------------------- | ------------------------------------------------ | -------- |
|
|
106
|
+
| `ordered_list` | Outer `<ol>` | — |
|
|
107
|
+
| `ordered_list_item` | `<li>` | — |
|
|
108
|
+
| `ordered_list_item_handle` | Leading/trailing `<span>` (decorative-or-handle) | — |
|
|
109
|
+
| `ordered_list_item_content` | Content wrapper `<span>` inside `<a>` (or bare) | — |
|
|
110
|
+
| `ordered_list_item_title` | Title `<span>` | — |
|
|
111
|
+
| `ordered_list_item_description` | Description `<span>` | — |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## ARIA
|
|
116
|
+
|
|
117
|
+
- See [ARIA.md](../../../ARIA.md) for WCAG 2.1 AA criteria.
|
|
118
|
+
- For the `reorderable` JS controller's targets, values, and actions (including `editingValue`/`trigger`/`toggleEditing`), see the [stimulus-plumbers JS controller doc](../../../stimulus-plumbers/docs/component/reorderable.md).
|
data/docs/component/plumber.md
CHANGED
|
@@ -203,3 +203,24 @@ def merge_html_options(*hashes)
|
|
|
203
203
|
end
|
|
204
204
|
end
|
|
205
205
|
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## PlumberHelper
|
|
210
|
+
|
|
211
|
+
### `sp_dom_id(record = nil, prefix: nil, suffix: nil)`
|
|
212
|
+
|
|
213
|
+
Generates an `"sp_"`-prefixed DOM id for wiring `aria-controls`/`id` pairs between a trigger and its target. Not a component renderer — an internal id-generation utility used by `Combobox`, `Popover`, and `Timeline::Event` when the caller doesn't supply an explicit `id:`.
|
|
214
|
+
|
|
215
|
+
| Param | Description |
|
|
216
|
+
| --------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
217
|
+
| `record` | ActiveRecord-like object — delegates to Rails' `dom_id(record, prefix)`. Omit for a random id (`SecureRandom.hex(8)`). |
|
|
218
|
+
| `prefix:` | Forwarded to `dom_id` when `record` is given. |
|
|
219
|
+
| `suffix:` | Appended after an underscore, regardless of whether `record` is given. |
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
sp_dom_id # => "sp_3f9a1b2c4d5e6f70"
|
|
223
|
+
sp_dom_id(suffix: "panel") # => "sp_3f9a1b2c4d5e6f70_panel"
|
|
224
|
+
sp_dom_id(post) # => "sp_post_1"
|
|
225
|
+
sp_dom_id(post, prefix: :edit, suffix: :form) # => "sp_edit_post_1_form"
|
|
226
|
+
```
|
data/docs/component/popover.md
CHANGED
|
@@ -130,9 +130,8 @@ end
|
|
|
130
130
|
|
|
131
131
|
## ARIA
|
|
132
132
|
|
|
133
|
-
- Default trigger button has `aria-haspopup="dialog"`, `aria-expanded="false"`, and `aria-controls` pointing to the panel.
|
|
134
|
-
- `aria-expanded` is toggled to `"true"` / `"false"` by the Stimulus controller.
|
|
135
133
|
- Pass `role: "dialog"` or `role: "tooltip"` on `p.panel` based on whether the content is interactive.
|
|
136
134
|
- `aria: { label: }` or `aria: { labelledby: }` on `p.panel` provides the accessible name.
|
|
135
|
+
- See [ARIA.md's Popover pattern](../../../ARIA.md) for the trigger's `aria-haspopup`/`aria-expanded`/`aria-controls` wiring.
|
|
137
136
|
|
|
138
137
|
For the JS controller API (targets, values, keyboard behaviour), see the [JS package docs](../../../stimulus-plumbers/docs/component/popover.md).
|