stimulus_plumbers 0.4.4 → 0.4.5
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 +7 -0
- data/docs/architecture.md +33 -0
- data/docs/component/avatar.md +84 -0
- data/docs/component/button.md +135 -0
- data/docs/component/calendar.md +223 -0
- data/docs/component/card.md +92 -0
- data/docs/component/combobox.md +254 -0
- data/docs/component/dispatcher.md +76 -0
- data/docs/component/divider.md +47 -0
- data/docs/component/form.md +238 -0
- data/docs/component/icon.md +44 -0
- data/docs/component/link.md +90 -0
- data/docs/component/list.md +161 -0
- data/docs/component/modal.md +19 -0
- data/docs/component/plumber.md +183 -0
- data/docs/component/popover.md +138 -0
- data/docs/component/theme.md +188 -0
- data/docs/component/timeline.md +138 -0
- data/lib/stimulus_plumbers/version.rb +1 -1
- data/vendor/controllers.manifest.json +386 -0
- metadata +19 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 76a3529b969c106cec1fef750f3a76a5c05782f31b56e29c8c4640cd105d7dd7
|
|
4
|
+
data.tar.gz: 118813bf270d4eed2901da381d4effa1b663a5558e9c97b6041850ddeb63e572
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fccd6c0b45374752edd4583acb07d099668dcbdf3e745165775e86bd2b19603680f55b6641c37dbebe529b317772eceba6e1dc47bcc8b2637ae574009f6bc2b6
|
|
7
|
+
data.tar.gz: e7fe95f7e5f38a551a95cd27ac69466a73602603b6eac243e8469d59ea20e0f96cd9fcad8189742555ac6ba72f071ab92eadc2a9243e686258f6473d12416559
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
---
|
|
6
|
+
## [0.4.4](https://github.com/ryancyq/stimulus-plumbers/compare/stimulus-plumbers-rails/v0.4.3..stimulus-plumbers-rails/v0.4.4) - 2026-06-30
|
|
7
|
+
|
|
8
|
+
### Dependencies
|
|
9
|
+
|
|
10
|
+
- update npm package dist to have both es + umd + source map - ([64ec309](https://github.com/ryancyq/stimulus-plumbers/commit/64ec309ca171584d1604e4e7d3d3533878ebed7a)) - Ryan Chang
|
|
11
|
+
|
|
5
12
|
---
|
|
6
13
|
## [0.4.3](https://github.com/ryancyq/stimulus-plumbers/compare/stimulus-plumbers-rails/v0.4.2..stimulus-plumbers-rails/v0.4.3) - 2026-06-30
|
|
7
14
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Rails Package Architecture
|
|
2
|
+
|
|
3
|
+
## Schema Ranges Convention
|
|
4
|
+
|
|
5
|
+
Validation ranges for theme schema params live under `lib/stimulus_plumbers/themes/schema/`.
|
|
6
|
+
|
|
7
|
+
**Where ranges live — the branching rule:**
|
|
8
|
+
|
|
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
|
+
- If the component only passes the value through to the theme, the schema owns the range (e.g. `Schema::Button::Ranges::TYPE`).
|
|
11
|
+
|
|
12
|
+
**Namespace rules:**
|
|
13
|
+
|
|
14
|
+
**Namespaces:**
|
|
15
|
+
|
|
16
|
+
- `Schema::Ranges` — cross-cutting constants (e.g. `BOOL`)
|
|
17
|
+
- `Schema::<Component>::Ranges` — per-component constants
|
|
18
|
+
- `Schema::Link::Ranges` — link-specific; uses `:default` as base instead of `:primary`
|
|
19
|
+
- `Schema::Form::<Input>::Ranges` — per-input-type constants
|
|
20
|
+
- `Schema::Form::Ranges` — form-level ranges (`LAYOUT`, `VARIANT`)
|
|
21
|
+
|
|
22
|
+
**Guidelines:**
|
|
23
|
+
|
|
24
|
+
- **No local aliases** — never re-export another module's constant inside a `Ranges` module.
|
|
25
|
+
- **Remove unused constants** — don't keep range constants with no call sites in `schema.rb`.
|
|
26
|
+
|
|
27
|
+
## Icon-Only Detection Contract (Button + Link)
|
|
28
|
+
|
|
29
|
+
`Button#build_button` and `Link#build_content` always wrap non-nil text/block content in `<span>`. When content is nil and no block is given (icon-only), no `<span>` is rendered.
|
|
30
|
+
|
|
31
|
+
The active theme uses `:has(> span)` / `:not(:has(> span))` CSS to distinguish icon-only from text buttons without any Ruby flag.
|
|
32
|
+
|
|
33
|
+
**Do not change this contract** without updating `stimulus-plumbers-tailwind`'s `Button::LAYOUT` and `Link::BUTTON` constants accordingly. See [tailwind architecture doc](../../stimulus-plumbers-tailwind/docs/architecture.md#icon-only-square-pattern).
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Avatar
|
|
2
|
+
|
|
3
|
+
Rails helper for rendering a user avatar — image, initials, or fallback silhouette.
|
|
4
|
+
|
|
5
|
+
## Helper
|
|
6
|
+
|
|
7
|
+
### `sp_avatar`
|
|
8
|
+
|
|
9
|
+
```erb
|
|
10
|
+
<%# Image from URL %>
|
|
11
|
+
<%= sp_avatar url: user.avatar_url, name: user.name %>
|
|
12
|
+
|
|
13
|
+
<%# Initials (SVG text) %>
|
|
14
|
+
<%= sp_avatar initials: "RC", name: "Ryan Chang" %>
|
|
15
|
+
|
|
16
|
+
<%# Named variant + size %>
|
|
17
|
+
<%= sp_avatar initials: "AB", name: "Alice B", variant: :purple, size: :lg %>
|
|
18
|
+
|
|
19
|
+
<%# Auto-selected variant (hashed from name) %>
|
|
20
|
+
<%= sp_avatar name: "Ryan Chang" %>
|
|
21
|
+
|
|
22
|
+
<%# Custom content block %>
|
|
23
|
+
<%= sp_avatar(name: "Ryan") { image_tag "custom.png" } %>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Option | Default | Description |
|
|
27
|
+
| ---------------- | ------- | ------------------------------------------------------------------------------------------ |
|
|
28
|
+
| `name` | `nil` | Used for `aria-label` and auto-variant selection |
|
|
29
|
+
| `initials` | `nil` | Renders an SVG with the initials text; used when no `url` or block is given |
|
|
30
|
+
| `url` | `nil` | Image source; renders `<img>` with `alt="[name]'s avatar"` and `onerror` fallback |
|
|
31
|
+
| `variant` | `nil` | Color variant for the avatar background — auto-selected from theme range if omitted |
|
|
32
|
+
| `size` | `:md` | Size — values defined by the active theme (e.g. `:xs` \| `:sm` \| `:md` \| `:lg` \| `:xl`) |
|
|
33
|
+
| `**html_options` | — | Forwarded to the `<span>` wrapper |
|
|
34
|
+
|
|
35
|
+
**Render modes (in priority order):**
|
|
36
|
+
|
|
37
|
+
| Condition | Output |
|
|
38
|
+
| -------------------- | ------------------------------ |
|
|
39
|
+
| Block given | Custom content inside `<span>` |
|
|
40
|
+
| `url:` provided | `<img>` inside `<span>` |
|
|
41
|
+
| `initials:` provided | SVG with initials text |
|
|
42
|
+
| None of the above | SVG fallback silhouette |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Rendered HTML Structure
|
|
47
|
+
|
|
48
|
+
```html
|
|
49
|
+
<span role="img" aria-label="Ryan Chang" class="[avatar theme classes]">
|
|
50
|
+
<!-- image mode -->
|
|
51
|
+
<img
|
|
52
|
+
src="[url]"
|
|
53
|
+
alt="Ryan Chang's avatar"
|
|
54
|
+
class="[avatar_image theme classes]"
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
<!-- initials mode -->
|
|
58
|
+
<svg viewBox="0 0 40 40">
|
|
59
|
+
<text
|
|
60
|
+
x="50%"
|
|
61
|
+
y="50%"
|
|
62
|
+
dy="0.35em"
|
|
63
|
+
text-anchor="middle"
|
|
64
|
+
fill="currentColor"
|
|
65
|
+
font-size="20"
|
|
66
|
+
>
|
|
67
|
+
RC
|
|
68
|
+
</text>
|
|
69
|
+
</svg>
|
|
70
|
+
|
|
71
|
+
<!-- fallback mode -->
|
|
72
|
+
<svg viewBox="0 0 40 40">
|
|
73
|
+
<path fill="currentColor" d="..." />
|
|
74
|
+
</svg>
|
|
75
|
+
</span>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## ARIA
|
|
81
|
+
|
|
82
|
+
- Always renders `role="img"` with `aria-label` set to `name` (when provided).
|
|
83
|
+
- Image `alt` is set to `"[name]'s avatar"`; for anonymous avatars `alt=""` is used.
|
|
84
|
+
- `onerror` on `<img>` clears the `src` to prevent broken-image icons.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Button
|
|
2
|
+
|
|
3
|
+
Rails helpers for rendering themed, accessible buttons and links.
|
|
4
|
+
|
|
5
|
+
## Helpers
|
|
6
|
+
|
|
7
|
+
### `sp_button`
|
|
8
|
+
|
|
9
|
+
```erb
|
|
10
|
+
<%= sp_button "Save" %>
|
|
11
|
+
<%= sp_button(icon_leading: "check") { "Confirm" } %>
|
|
12
|
+
<%= sp_button "Delete", type: :default, variant: :destructive, size: :sm %>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| Option | Default | Description |
|
|
16
|
+
| ---------------- | ---------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `content` | `nil` | Button label — positional arg or block |
|
|
18
|
+
| `type` | `:default` | Visual style — `:default` \| `:outline` \| `:ghost` \| `:fab` \| `:fab_outline` \| `:dashed` \| `:card` |
|
|
19
|
+
| `variant` | `:primary` | Color source — `:primary` \| `:secondary` \| `:tertiary` \| `:success` \| `:destructive` \| `:warning` \| `:info` |
|
|
20
|
+
| `size` | `:md` | Size — `:xs` \| `:sm` \| `:md` \| `:lg` \| `:xl` (ignored when `type: :card`) |
|
|
21
|
+
| `icon_leading` | `nil` | Icon rendered **before** the label — icon name (string or symbol) or callable |
|
|
22
|
+
| `icon_trailing` | `nil` | Icon rendered **after** the label — icon name (string or symbol) or callable |
|
|
23
|
+
| `**html_options` | — | Forwarded to the `<button>` element |
|
|
24
|
+
|
|
25
|
+
**`type:` — visual style**
|
|
26
|
+
|
|
27
|
+
| Value | Appearance |
|
|
28
|
+
| -------------- | ------------------------------------------------------------------------------ |
|
|
29
|
+
| `:default` | Filled — solid background from `variant` color |
|
|
30
|
+
| `:outline` | Surface background, colored border and text; subtle tint on hover |
|
|
31
|
+
| `:ghost` | Transparent, no border; subtle tint on hover |
|
|
32
|
+
| `:fab` | Floating action button — `rounded-full`, elevated shadow, filled |
|
|
33
|
+
| `:fab_outline` | Floating action button — `rounded-full`, elevated shadow, fills solid on hover |
|
|
34
|
+
| `:dashed` | Dashed border, surface background |
|
|
35
|
+
| `:card` | Full-padding card — `flex-1`, `justify-start`; `size:` ignored |
|
|
36
|
+
|
|
37
|
+
**`variant:` — color source**
|
|
38
|
+
|
|
39
|
+
| Value | Color tokens used |
|
|
40
|
+
| -------------- | ------------------------------ |
|
|
41
|
+
| `:primary` | `--sp-color-primary-*` |
|
|
42
|
+
| `:secondary` | `--sp-color-secondary-*` |
|
|
43
|
+
| `:tertiary` | `--sp-color-muted-*` (neutral) |
|
|
44
|
+
| `:success` | `--sp-color-success-*` |
|
|
45
|
+
| `:destructive` | `--sp-color-destructive-*` |
|
|
46
|
+
| `:warning` | `--sp-color-warning-*` |
|
|
47
|
+
| `:info` | `--sp-color-info-*` |
|
|
48
|
+
|
|
49
|
+
**Icon values:**
|
|
50
|
+
|
|
51
|
+
- String or symbol — resolved by name through the active theme's icon registry, rendered via `sp_icon` with `button_icon` theme classes applied
|
|
52
|
+
- Callable (e.g. `-> { tag.span "★" }`) — rendered as-is, no theme applied
|
|
53
|
+
|
|
54
|
+
### `sp_button_group`
|
|
55
|
+
|
|
56
|
+
Wraps buttons in a themed container `<div>`.
|
|
57
|
+
|
|
58
|
+
```erb
|
|
59
|
+
<%= sp_button_group do %>
|
|
60
|
+
<%= sp_button "Cancel", type: :outline %>
|
|
61
|
+
<%= sp_button "Save" %>
|
|
62
|
+
<% end %>
|
|
63
|
+
|
|
64
|
+
<%= sp_button_group(layout: :stacked) do %>
|
|
65
|
+
...
|
|
66
|
+
<% end %>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| Option | Default | Description |
|
|
70
|
+
| ---------------- | --------- | ------------------------------------------ |
|
|
71
|
+
| `layout` | `:inline` | Layout direction — `:inline` \| `:stacked` |
|
|
72
|
+
| `**html_options` | — | Forwarded to the wrapper `<div>` |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Rendered HTML Structure
|
|
77
|
+
|
|
78
|
+
### Button (action)
|
|
79
|
+
|
|
80
|
+
Text content is always wrapped in a `<span>`:
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<button type="button" class="[theme classes]"><span>Save</span></button>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### With icons
|
|
87
|
+
|
|
88
|
+
Icons are rendered **inside** the `<button>`, before or after the label `<span>`:
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<button type="button" class="[theme classes]">
|
|
92
|
+
<svg aria-hidden="true" class="[button_icon theme classes]">
|
|
93
|
+
<!-- icon svg -->
|
|
94
|
+
</svg>
|
|
95
|
+
<span>Confirm</span>
|
|
96
|
+
</button>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<button type="button" class="[theme classes]">
|
|
101
|
+
<span>Next</span>
|
|
102
|
+
<svg aria-hidden="true" class="[button_icon theme classes]">
|
|
103
|
+
<!-- icon svg -->
|
|
104
|
+
</svg>
|
|
105
|
+
</button>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Icon only
|
|
109
|
+
|
|
110
|
+
When no text is provided, no `<span>` is rendered. The theme uses `:has(> span)` to detect this and applies `aspect-square` + `px-0`, making the button square (or a circle for `type: :fab` / `:fab_outline`):
|
|
111
|
+
|
|
112
|
+
```html
|
|
113
|
+
<button type="button" aria-label="Add" class="[theme classes]">
|
|
114
|
+
<svg aria-hidden="true" class="[button_icon theme classes]">
|
|
115
|
+
<!-- icon svg -->
|
|
116
|
+
</svg>
|
|
117
|
+
</button>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Button group
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<div role="group" class="[button_group theme classes]">
|
|
124
|
+
<button type="button">Cancel</button>
|
|
125
|
+
<button type="button">Save</button>
|
|
126
|
+
</div>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## ARIA
|
|
132
|
+
|
|
133
|
+
- `<button>` always has `type="button"` to prevent accidental form submission.
|
|
134
|
+
- Icon-only buttons must supply an accessible label via `aria: { label: "..." }` in `html_options`.
|
|
135
|
+
- For navigation actions (links), use `sp_link` instead — it renders an `<a>` element.
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Calendar
|
|
2
|
+
|
|
3
|
+
Rails helpers for rendering calendar grids. Two rendering modes are supported:
|
|
4
|
+
|
|
5
|
+
- **CSR (Stimulus)** — JS controller renders the month grid client-side; drill-down (year/decade views) driven by `combobox-date`.
|
|
6
|
+
- **SSR (Turbo)** — Server renders each view as a Turbo Frame; controller handles navigation via Turbo Drive.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## CSR — Stimulus calendar
|
|
11
|
+
|
|
12
|
+
### Setup
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
module ApplicationHelper
|
|
16
|
+
include StimulusPlumbers::Helpers::CalendarHelper
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Usage
|
|
21
|
+
|
|
22
|
+
```erb
|
|
23
|
+
<%# Current month (defaults to today) %>
|
|
24
|
+
<%= sp_calendar_month %>
|
|
25
|
+
|
|
26
|
+
<%# Navigate to a specific month %>
|
|
27
|
+
<%= sp_calendar_month(date: Date.new(2024, 2, 1)) %>
|
|
28
|
+
|
|
29
|
+
<%# Inside a combobox-date wrapper for drill-down navigation %>
|
|
30
|
+
<div data-controller="combobox-date"
|
|
31
|
+
data-combobox-date-calendar-month-outlet="[data-controller~='calendar-month']">
|
|
32
|
+
<%= sp_calendar_month(date: @date) %>
|
|
33
|
+
</div>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Option | Description |
|
|
37
|
+
| ---------------- | ------------------------------------------------------------------------------------ |
|
|
38
|
+
| `date` | `Date` — sets `year-value`, `month-value` (0-indexed), `day-value` on the controller |
|
|
39
|
+
| `**html_options` | Forwarded to the `calendar-month` controller root element |
|
|
40
|
+
|
|
41
|
+
For the JS controller API (targets, values, keyboard behaviour), see the [JS package docs](../../../stimulus-plumbers/docs/component/calendar.md).
|
|
42
|
+
|
|
43
|
+
### Rendered HTML structure
|
|
44
|
+
|
|
45
|
+
`sp_calendar_month` renders a `calendar-month` controller shell. The JS controller populates the day/week grids on connect and on each navigation. Year/decade drill-down views are separate controllers wired by `combobox-date` — see the [JS package docs](../../../stimulus-plumbers/docs/component/calendar.md).
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<!-- sp_calendar_month(date: Date.new(2024, 2, 15)) -->
|
|
49
|
+
<div
|
|
50
|
+
data-controller="calendar-month"
|
|
51
|
+
data-calendar-month-year-value="2024"
|
|
52
|
+
data-calendar-month-month-value="1"
|
|
53
|
+
data-calendar-month-day-value="15"
|
|
54
|
+
role="grid"
|
|
55
|
+
>
|
|
56
|
+
<!-- days-of-week header row (JS-populated) -->
|
|
57
|
+
<div data-calendar-month-target="daysOfWeek"></div>
|
|
58
|
+
|
|
59
|
+
<!-- days-of-month body (JS-populated) -->
|
|
60
|
+
<div role="rowgroup" data-calendar-month-target="daysOfMonth"></div>
|
|
61
|
+
</div>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## SSR — Turbo calendar
|
|
67
|
+
|
|
68
|
+
### Setup
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
module ApplicationHelper
|
|
72
|
+
include StimulusPlumbers::Helpers::CalendarTurboHelper
|
|
73
|
+
end
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Usage
|
|
77
|
+
|
|
78
|
+
`sp_calendar_turbo` renders all three view frames at once (month visible, year/decade hidden). Each frame is a `<turbo-frame>` targeted by `combobox-date`.
|
|
79
|
+
|
|
80
|
+
```erb
|
|
81
|
+
<%= sp_calendar_turbo(date: @date, today: @today, selectable: true) %>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Individual view helpers render a single view (used for Turbo Frame responses). They accept the same common options plus extras:
|
|
85
|
+
|
|
86
|
+
```erb
|
|
87
|
+
<%# Month view (days grid) %>
|
|
88
|
+
<%= sp_calendar_turbo_month(date: @date, today: @today, weekday_format: :narrow) %>
|
|
89
|
+
|
|
90
|
+
<%# Year view (months grid) %>
|
|
91
|
+
<%= sp_calendar_turbo_year(date: @date, today: @today, month_format: :long) %>
|
|
92
|
+
|
|
93
|
+
<%# Decade view (years grid) %>
|
|
94
|
+
<%= sp_calendar_turbo_decade(date: @date, today: @today) %>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
| Helper | Extra option | Description |
|
|
98
|
+
| ------------------------- | ---------------- | ------------------------------------------------------------------ |
|
|
99
|
+
| `sp_calendar_turbo_month` | `weekday_format` | `:short` (default) \| `:long` \| `:narrow` — weekday header format |
|
|
100
|
+
| `sp_calendar_turbo_year` | `month_format` | `:short` (default) \| `:long` \| `:narrow` — month button format |
|
|
101
|
+
|
|
102
|
+
**Common options** (all helpers and `sp_calendar_turbo`):
|
|
103
|
+
|
|
104
|
+
| Option | Description |
|
|
105
|
+
| ------------------- | -------------------------------------------------------- |
|
|
106
|
+
| `date` | `Date` — the month/year being displayed |
|
|
107
|
+
| `today` | `Date` — used to mark today and aria-current |
|
|
108
|
+
| `selectable` | `true` renders day cells as `<button>` (month only) |
|
|
109
|
+
| `selected_date` | `Date` — marks the selected cell with aria-selected |
|
|
110
|
+
| `show_other_months` | `true` shows padding days from adjacent months |
|
|
111
|
+
| `since` | `Date` — minimum selectable date (disables earlier days) |
|
|
112
|
+
| `till` | `Date` — maximum selectable date (disables later days) |
|
|
113
|
+
| `disabled_months` | Array — month numbers/names to disable in the year view |
|
|
114
|
+
| `disabled_years` | Array — years to disable in the decade view |
|
|
115
|
+
|
|
116
|
+
### Rendered HTML structure
|
|
117
|
+
|
|
118
|
+
#### Month view (`sp_calendar_turbo_month`)
|
|
119
|
+
|
|
120
|
+
```html
|
|
121
|
+
<!-- sp_calendar_turbo_month(date: @date, today: @today, selectable: true) -->
|
|
122
|
+
<div role="grid" data-controller="calendar-month-selector">
|
|
123
|
+
<!-- weekday column headers -->
|
|
124
|
+
<div role="row">
|
|
125
|
+
<span role="columnheader">Su</span>
|
|
126
|
+
<!-- … -->
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<!-- day cells -->
|
|
130
|
+
<div role="rowgroup">
|
|
131
|
+
<div role="row">
|
|
132
|
+
<!-- selectable: true → <button>; selectable: false → <span> -->
|
|
133
|
+
<button role="gridcell" tabindex="0" aria-selected="false">
|
|
134
|
+
<time datetime="2024-02-01">1</time>
|
|
135
|
+
</button>
|
|
136
|
+
<!-- … -->
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Year view (`sp_calendar_turbo_year`)
|
|
143
|
+
|
|
144
|
+
Displays 12 month buttons in a 4-column grid. Used when drilling up from the month view.
|
|
145
|
+
|
|
146
|
+
```html
|
|
147
|
+
<!-- sp_calendar_turbo_year(date: @date, today: @today) -->
|
|
148
|
+
<div
|
|
149
|
+
role="grid"
|
|
150
|
+
aria-label="Year view"
|
|
151
|
+
data-controller="calendar-year-selector"
|
|
152
|
+
>
|
|
153
|
+
<div role="rowgroup">
|
|
154
|
+
<div role="row">
|
|
155
|
+
<button
|
|
156
|
+
role="gridcell"
|
|
157
|
+
data-month="1"
|
|
158
|
+
aria-current="month"
|
|
159
|
+
aria-selected="false"
|
|
160
|
+
>
|
|
161
|
+
Jan
|
|
162
|
+
</button>
|
|
163
|
+
<!-- … 11 more months … -->
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### Decade view (`sp_calendar_turbo_decade`)
|
|
170
|
+
|
|
171
|
+
Displays 10 year buttons in a 4-column grid (plus 2 buffer cells, disabled). Used when drilling up from the year view.
|
|
172
|
+
|
|
173
|
+
```html
|
|
174
|
+
<!-- sp_calendar_turbo_decade(date: @date, today: @today) -->
|
|
175
|
+
<div
|
|
176
|
+
role="grid"
|
|
177
|
+
aria-label="Decade view"
|
|
178
|
+
data-controller="calendar-decade-selector"
|
|
179
|
+
>
|
|
180
|
+
<div role="rowgroup">
|
|
181
|
+
<div role="row">
|
|
182
|
+
<button
|
|
183
|
+
role="gridcell"
|
|
184
|
+
data-year="2020"
|
|
185
|
+
aria-current="year"
|
|
186
|
+
aria-selected="false"
|
|
187
|
+
>
|
|
188
|
+
2020
|
|
189
|
+
</button>
|
|
190
|
+
<!-- … 9 more years … -->
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Full Turbo setup (`sp_calendar_turbo`)
|
|
197
|
+
|
|
198
|
+
Renders all three frames. Used inside a `combobox-date` controller for drill-down navigation.
|
|
199
|
+
|
|
200
|
+
```html
|
|
201
|
+
<!-- month view frame — visible by default -->
|
|
202
|
+
<turbo-frame id="calendar-month-frame" data-combobox-date-target="monthView">
|
|
203
|
+
<!-- sp_calendar_turbo_month output -->
|
|
204
|
+
</turbo-frame>
|
|
205
|
+
|
|
206
|
+
<!-- year view frame — hidden until drill-up -->
|
|
207
|
+
<turbo-frame
|
|
208
|
+
id="calendar-year-frame"
|
|
209
|
+
hidden
|
|
210
|
+
data-combobox-date-target="yearView"
|
|
211
|
+
>
|
|
212
|
+
<!-- sp_calendar_turbo_year output -->
|
|
213
|
+
</turbo-frame>
|
|
214
|
+
|
|
215
|
+
<!-- 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
|
+
>
|
|
221
|
+
<!-- sp_calendar_turbo_decade output -->
|
|
222
|
+
</turbo-frame>
|
|
223
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Card
|
|
2
|
+
|
|
3
|
+
Rails helper for rendering a themed card with optional icon, title, body, and action slots.
|
|
4
|
+
|
|
5
|
+
## Helper
|
|
6
|
+
|
|
7
|
+
### `sp_card`
|
|
8
|
+
|
|
9
|
+
```erb
|
|
10
|
+
<%# Minimal %>
|
|
11
|
+
<%= sp_card do |card| %>
|
|
12
|
+
<% card.with_body { "Simple content." } %>
|
|
13
|
+
<% end %>
|
|
14
|
+
|
|
15
|
+
<%# With title and icon %>
|
|
16
|
+
<%= sp_card(title_tag: :h3) do |card| %>
|
|
17
|
+
<% card.with_icon("user") %>
|
|
18
|
+
<% card.with_title("Account") %>
|
|
19
|
+
<% card.with_body { "Your account is active." } %>
|
|
20
|
+
<% end %>
|
|
21
|
+
|
|
22
|
+
<%# With action link %>
|
|
23
|
+
<%= sp_card do |card| %>
|
|
24
|
+
<% card.with_title("Settings") %>
|
|
25
|
+
<% card.with_action("Manage", url: settings_path) %>
|
|
26
|
+
<% end %>
|
|
27
|
+
|
|
28
|
+
<%# With action button (no url) %>
|
|
29
|
+
<%= sp_card do |card| %>
|
|
30
|
+
<% card.with_action("Open") %>
|
|
31
|
+
<% end %>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
| Option | Default | Description |
|
|
35
|
+
| ---------------- | ----------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
36
|
+
| `variant:` | `:tertiary` | Theme variant — `:primary` \| `:secondary` \| `:tertiary` \| `:success` \| `:destructive` \| `:warning` \| `:info` |
|
|
37
|
+
| `title_tag:` | `:h2` | HTML tag used by `with_title` |
|
|
38
|
+
| `**html_options` | — | Forwarded to the outer `<div>` |
|
|
39
|
+
|
|
40
|
+
### Slot methods (yielded as `card`)
|
|
41
|
+
|
|
42
|
+
| Slot method | Description |
|
|
43
|
+
| ---------------------------------- | ---------------------------------------------------------------------------- |
|
|
44
|
+
| `card.with_icon(name_or_html)` | Icon before the title — string/symbol resolves via `Icon`; HTML passed as-is |
|
|
45
|
+
| `card.with_title(text)` | Title text rendered as `title_tag` |
|
|
46
|
+
| `card.with_body { content }` | Body content — block required |
|
|
47
|
+
| `card.with_action(text, url: nil)` | Action link (`<a>`) when `url:` present; `<button>` otherwise |
|
|
48
|
+
|
|
49
|
+
`with_action` raises `ArgumentError` when `url:` is given but no content (text or block).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Rendered HTML Structure
|
|
54
|
+
|
|
55
|
+
```html
|
|
56
|
+
<div class="[card theme classes]">
|
|
57
|
+
<!-- header: rendered when icon or title is present -->
|
|
58
|
+
<div class="[card_header theme classes]">
|
|
59
|
+
<svg aria-hidden="true" class="[card_icon theme classes]">...</svg>
|
|
60
|
+
<h2 class="[card_title theme classes]">Account</h2>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<!-- body -->
|
|
64
|
+
<div class="[card_body theme classes]">Your account is active.</div>
|
|
65
|
+
|
|
66
|
+
<!-- action -->
|
|
67
|
+
<div class="[card_action theme classes]">
|
|
68
|
+
<a href="/settings">Manage</a>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Theme keys
|
|
76
|
+
|
|
77
|
+
| Key | Element | Variants |
|
|
78
|
+
| ------------- | ----------------------------------- | --------------------------------------------- |
|
|
79
|
+
| `card` | Outer `<div>` | `variant: :primary\|:secondary\|:tertiary\|…` |
|
|
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` | Action `<div>` | — |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## ARIA
|
|
89
|
+
|
|
90
|
+
- `sp_card` is a layout container — no ARIA roles are added automatically.
|
|
91
|
+
- Supply `role:` or `aria:` via `html_options` when the card plays a semantic role (e.g. `role: "region"`, `aria: { label: "..." }`).
|
|
92
|
+
- For interactive card-style inputs (radio/checkbox), see `f.choice` with `type: :card` in the [form doc](form.md).
|