stimulus_plumbers_tailwind 0.3.1
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 +7 -0
- data/CHANGELOG.md +12 -0
- data/README.md +88 -0
- data/lib/stimulus_plumbers/themes/tailwind/action_list.rb +33 -0
- data/lib/stimulus_plumbers/themes/tailwind/avatar.rb +52 -0
- data/lib/stimulus_plumbers/themes/tailwind/button.rb +89 -0
- data/lib/stimulus_plumbers/themes/tailwind/calendar.rb +84 -0
- data/lib/stimulus_plumbers/themes/tailwind/card.rb +24 -0
- data/lib/stimulus_plumbers/themes/tailwind/combobox.rb +86 -0
- data/lib/stimulus_plumbers/themes/tailwind/form.rb +116 -0
- data/lib/stimulus_plumbers/themes/tailwind/icon.rb +24 -0
- data/lib/stimulus_plumbers/themes/tailwind/layout.rb +25 -0
- data/lib/stimulus_plumbers/themes/tailwind_theme.rb +33 -0
- data/lib/stimulus_plumbers_tailwind/engine.rb +11 -0
- data/lib/stimulus_plumbers_tailwind/version.rb +5 -0
- data/lib/stimulus_plumbers_tailwind.rb +14 -0
- metadata +74 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9276ad8bfe52d19448c0e3571e8ab449d32a92157c88ee4711397919d1df25e8
|
|
4
|
+
data.tar.gz: 013d01a397e3d8efaba9fe5addf41d10d1bf5dfe98f4c89e551c0de69aa61f13
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d0c7e9fb5e48a6610dff539bc4a1e153faef1b6237bfe19593eb746f4b5c66520bd00931f2e6e3ea23c3214f13b5cd32055352a8d0ee982052a9eae570fc7da5
|
|
7
|
+
data.tar.gz: c71d3ccc142cbd8566fe77bf4b0e2bd7d002854525e59351739be5a6a122ea492619007f3c7a7afaba448d55de491c117a1f20f7227d7f90266d8b249b3f5d3f
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
## [0.3.1] - 2026-05-19
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- tailwind gem ([#74](https://github.com/ryancyq/stimulus-plumbers/issues/74)) - ([d9cbbdc](https://github.com/ryancyq/stimulus-plumbers/commit/d9cbbdce65d1d6f93773613a477ea082912af967)) - Ryan Chang
|
|
11
|
+
|
|
12
|
+
<!-- generated by git-cliff -->
|
data/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# stimulus-plumbers-tailwind
|
|
2
|
+
|
|
3
|
+
[![Version][rubygems_badge]][rubygems]
|
|
4
|
+
[![CI][ci_badge]][ci]
|
|
5
|
+
[![Coverage][coverage_badge]][coverage]
|
|
6
|
+
|
|
7
|
+
Tailwind CSS v4 theme for [`stimulus_plumbers`](../stimulus-plumbers-rails). Extends the base theme with utility classes for all components.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Ruby >= 3.0
|
|
12
|
+
- `stimulus_plumbers` >= 0.2.9
|
|
13
|
+
- Tailwind CSS v4 in your build toolchain
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
# Gemfile
|
|
19
|
+
gem "stimulus_plumbers_tailwind"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bundle install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Activate the theme in an initializer or `config/application.rb`:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
StimulusPlumbers.configure do |config|
|
|
30
|
+
config.theme = :tailwind
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Tell Tailwind to scan the gem's lib files so component class names are included in the generated CSS. Add an `@source` directive pointing at the gem's installed path:
|
|
35
|
+
|
|
36
|
+
```css
|
|
37
|
+
@import "tailwindcss";
|
|
38
|
+
@source "/path/to/gems/stimulus_plumbers_tailwind-VERSION/lib/**/*.rb";
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Use `bundle show stimulus_plumbers_tailwind` to get the exact installed path.
|
|
42
|
+
|
|
43
|
+
## Theming
|
|
44
|
+
|
|
45
|
+
`StimulusPlumbers::Themes::TailwindTheme` subclasses `StimulusPlumbers::Themes::Base` and provides CSS class resolution for all component families:
|
|
46
|
+
|
|
47
|
+
| Module | Components |
|
|
48
|
+
|--------|------------|
|
|
49
|
+
| `Tailwind::ActionList` | Action list, menu items |
|
|
50
|
+
| `Tailwind::Avatar` | Avatar |
|
|
51
|
+
| `Tailwind::Button` | Button |
|
|
52
|
+
| `Tailwind::Calendar` | Calendar grid, date picker |
|
|
53
|
+
| `Tailwind::Card` | Card |
|
|
54
|
+
| `Tailwind::Combobox` | Combobox (date, time, dropdown, autocomplete) |
|
|
55
|
+
| `Tailwind::Form` | Form fields, labels, errors |
|
|
56
|
+
| `Tailwind::Layout` | Layout primitives |
|
|
57
|
+
|
|
58
|
+
Custom themes can subclass `TailwindTheme` to override individual methods, or subclass `StimulusPlumbers::Themes::Base` directly.
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bundle install
|
|
64
|
+
npm install
|
|
65
|
+
|
|
66
|
+
bundle exec rake test:unit # unit tests
|
|
67
|
+
npm run test:snapshots # visual snapshot tests (Playwright)
|
|
68
|
+
npm run test:snapshots:update # regenerate baseline screenshots
|
|
69
|
+
bundle exec rake rubocop # lint
|
|
70
|
+
bundle exec rake coverage # run tests with coverage + collate report
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The sandbox Rails app lives in `test/sandbox/`. Run it with:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
bundle exec puma test/sandbox/config.ru
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
[MIT](https://opensource.org/licenses/MIT)
|
|
82
|
+
|
|
83
|
+
[rubygems_badge]: https://img.shields.io/gem/v/stimulus_plumbers_tailwind.svg
|
|
84
|
+
[rubygems]: https://rubygems.org/gems/stimulus_plumbers_tailwind
|
|
85
|
+
[ci_badge]: https://github.com/ryancyq/stimulus-plumbers/actions/workflows/ci-tailwind.yml/badge.svg
|
|
86
|
+
[ci]: https://github.com/ryancyq/stimulus-plumbers/actions/workflows/ci-tailwind.yml
|
|
87
|
+
[coverage_badge]: https://codecov.io/gh/ryancyq/stimulus-plumbers/graph/badge.svg?token=Z77H6M5GER&flag=tailwind
|
|
88
|
+
[coverage]: https://codecov.io/gh/ryancyq/stimulus-plumbers
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Tailwind
|
|
6
|
+
module ActionList
|
|
7
|
+
LIST_ITEM_ACTIVE = %w[bg-(--sp-color-primary)/10 text-(--sp-color-primary)].freeze
|
|
8
|
+
LIST_ITEM_BASE = %w[
|
|
9
|
+
flex items-center gap-(--sp-space-2) w-full
|
|
10
|
+
px-(--sp-space-2) py-(--sp-space-1)
|
|
11
|
+
rounded-(--sp-radius-sm) text-(--sp-text-sm)
|
|
12
|
+
cursor-pointer select-none outline-none
|
|
13
|
+
hover:bg-(--sp-color-muted) focus:bg-(--sp-color-muted) focus:text-(--sp-color-fg)
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def action_list_classes
|
|
19
|
+
{ classes: klasses("py-(--sp-space-1)") }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def action_list_item_classes(active: false)
|
|
23
|
+
{
|
|
24
|
+
classes: klasses(
|
|
25
|
+
*LIST_ITEM_BASE,
|
|
26
|
+
*(active ? LIST_ITEM_ACTIVE : [])
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Tailwind
|
|
6
|
+
module Avatar
|
|
7
|
+
COLORS = {
|
|
8
|
+
amber: "text-white bg-amber-600",
|
|
9
|
+
lime: "text-white bg-lime-600",
|
|
10
|
+
sky: "text-white bg-sky-600",
|
|
11
|
+
cyan: "text-white bg-cyan-600",
|
|
12
|
+
teal: "text-white bg-teal-600",
|
|
13
|
+
emerald: "text-white bg-emerald-600",
|
|
14
|
+
indigo: "text-white bg-indigo-600",
|
|
15
|
+
fuchsia: "text-white bg-fuchsia-600",
|
|
16
|
+
rose: "text-white bg-rose-600",
|
|
17
|
+
pink: "text-white bg-pink-600",
|
|
18
|
+
violet: "text-white bg-violet-600",
|
|
19
|
+
blue: "text-white bg-blue-600"
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
SIZES = {
|
|
23
|
+
sm: "size-(--sp-icon-size)",
|
|
24
|
+
md: "size-(--sp-avatar-size)",
|
|
25
|
+
lg: "size-12"
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
BASE = %w[rounded-(--sp-radius-full) overflow-hidden inline-flex items-center justify-center].freeze
|
|
29
|
+
|
|
30
|
+
def avatar_colors
|
|
31
|
+
COLORS
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def avatar_color_range
|
|
35
|
+
COLORS.values
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def avatar_classes(size: :md, color: nil)
|
|
41
|
+
{
|
|
42
|
+
classes: klasses(
|
|
43
|
+
SIZES.fetch(size, SIZES[:md]),
|
|
44
|
+
COLORS.fetch(color, nil),
|
|
45
|
+
*BASE
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Tailwind
|
|
6
|
+
module Button
|
|
7
|
+
VARIANTS = {
|
|
8
|
+
primary: %w[
|
|
9
|
+
bg-(--sp-color-primary)
|
|
10
|
+
text-(--sp-color-primary-fg)
|
|
11
|
+
hover:bg-(--sp-color-primary)/90
|
|
12
|
+
focus-visible:ring-(--sp-focus-ring-color)
|
|
13
|
+
].freeze,
|
|
14
|
+
secondary: %w[
|
|
15
|
+
bg-(--sp-color-muted)
|
|
16
|
+
text-(--sp-color-fg)
|
|
17
|
+
hover:bg-(--sp-color-border)
|
|
18
|
+
].freeze,
|
|
19
|
+
outline: %w[
|
|
20
|
+
border border-(--sp-color-border)
|
|
21
|
+
bg-transparent
|
|
22
|
+
text-(--sp-color-fg)
|
|
23
|
+
hover:bg-(--sp-color-muted)
|
|
24
|
+
].freeze,
|
|
25
|
+
destructive: %w[
|
|
26
|
+
bg-(--sp-color-destructive)
|
|
27
|
+
text-(--sp-color-destructive-fg)
|
|
28
|
+
hover:bg-(--sp-color-destructive)/90
|
|
29
|
+
].freeze,
|
|
30
|
+
ghost: %w[hover:bg-(--sp-color-muted) text-(--sp-color-fg)].freeze,
|
|
31
|
+
link: %w[text-(--sp-color-primary) underline-offset-4 hover:underline].freeze
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
SIZES = {
|
|
35
|
+
sm: %w[h-8 px-(--sp-space-3) text-(--sp-text-sm)].freeze,
|
|
36
|
+
md: %w[h-9 px-(--sp-space-4) text-(--sp-text-base)].freeze,
|
|
37
|
+
lg: %w[h-11 px-(--sp-space-6) text-(--sp-text-lg)].freeze
|
|
38
|
+
}.freeze
|
|
39
|
+
|
|
40
|
+
FLEX_ALIGN = {
|
|
41
|
+
row: {
|
|
42
|
+
left: "justify-start",
|
|
43
|
+
center: %w[justify-center items-center].freeze,
|
|
44
|
+
right: "justify-end",
|
|
45
|
+
top: "items-start",
|
|
46
|
+
bottom: "items-end"
|
|
47
|
+
}.freeze,
|
|
48
|
+
col: {
|
|
49
|
+
top: "justify-start",
|
|
50
|
+
center: %w[justify-center items-center].freeze,
|
|
51
|
+
bottom: "justify-end",
|
|
52
|
+
left: "items-start",
|
|
53
|
+
right: "items-end"
|
|
54
|
+
}.freeze
|
|
55
|
+
}.freeze
|
|
56
|
+
|
|
57
|
+
BASE = %w[
|
|
58
|
+
inline-flex items-center justify-center gap-2 font-medium
|
|
59
|
+
rounded-(--sp-radius-md) transition-colors
|
|
60
|
+
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2
|
|
61
|
+
disabled:pointer-events-none disabled:opacity-50
|
|
62
|
+
].freeze
|
|
63
|
+
|
|
64
|
+
GROUP_BASE = %w[flex gap-(--sp-space-2)].freeze
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def button_classes(variant: :primary, size: :md)
|
|
69
|
+
{
|
|
70
|
+
classes: klasses(
|
|
71
|
+
*BASE,
|
|
72
|
+
*VARIANTS.fetch(variant, []),
|
|
73
|
+
*SIZES.fetch(size, [])
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def button_group_classes(alignment: :left, direction: :row)
|
|
79
|
+
{
|
|
80
|
+
classes: klasses(
|
|
81
|
+
*GROUP_BASE,
|
|
82
|
+
*Array(FLEX_ALIGN.dig(direction, alignment))
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Tailwind
|
|
6
|
+
module Calendar
|
|
7
|
+
GRID = %w[w-full].freeze
|
|
8
|
+
|
|
9
|
+
DAYS_OF_WEEK = %w[
|
|
10
|
+
grid grid-cols-7 text-center text-(--sp-text-xs)
|
|
11
|
+
font-medium text-(--sp-color-muted-fg) mb-1
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
DAYS_OF_MONTH = %w[grid grid-cols-7 justify-items-center].freeze
|
|
15
|
+
|
|
16
|
+
DAY = %w[
|
|
17
|
+
size-(--sp-calendar-day-size) rounded-(--sp-radius-md)
|
|
18
|
+
flex items-center justify-center text-(--sp-text-sm)
|
|
19
|
+
hover:bg-(--sp-color-muted) cursor-pointer
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
DAY_SELECTED = %w[
|
|
23
|
+
bg-(--sp-color-primary)
|
|
24
|
+
text-(--sp-color-primary-fg)
|
|
25
|
+
hover:bg-(--sp-color-primary)/90
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
NAV = %w[flex items-center justify-between gap-1 mb-2].freeze
|
|
29
|
+
|
|
30
|
+
NAV_BTN = %w[
|
|
31
|
+
inline-flex items-center justify-center
|
|
32
|
+
size-(--sp-calendar-day-size) rounded-(--sp-radius-md)
|
|
33
|
+
text-(--sp-color-fg) hover:bg-(--sp-color-muted)
|
|
34
|
+
focus-visible:outline-none focus-visible:ring-2
|
|
35
|
+
focus-visible:ring-(--sp-focus-ring-color)
|
|
36
|
+
disabled:pointer-events-none disabled:opacity-50
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
39
|
+
NAV_ICON = %w[size-4 stroke-current].freeze
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def calendar_classes
|
|
44
|
+
{ classes: klasses(*GRID) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def calendar_days_of_week_classes
|
|
48
|
+
{ classes: klasses(*DAYS_OF_WEEK) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def calendar_week_classes
|
|
52
|
+
{ classes: "contents" }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def calendar_days_of_month_classes
|
|
56
|
+
{ classes: klasses(*DAYS_OF_MONTH) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def calendar_day_classes(today: false, selected: false, outside: false)
|
|
60
|
+
{
|
|
61
|
+
classes: klasses(
|
|
62
|
+
*DAY,
|
|
63
|
+
*(today ? ["font-bold"] : []),
|
|
64
|
+
*(selected ? DAY_SELECTED : []),
|
|
65
|
+
*(outside ? %w[text-(--sp-color-muted-fg) opacity-50] : [])
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def calendar_navigation_classes
|
|
71
|
+
{ classes: klasses(*NAV) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def calendar_navigation_navigator_classes
|
|
75
|
+
{ classes: klasses(*NAV_BTN) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def calendar_navigation_navigator_icon_classes
|
|
79
|
+
{ classes: klasses(*NAV_ICON) }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Tailwind
|
|
6
|
+
module Card
|
|
7
|
+
BASE = %w[
|
|
8
|
+
rounded-(--sp-radius-lg) border border-(--sp-color-border)
|
|
9
|
+
bg-(--sp-color-bg) shadow-(--sp-shadow-sm)
|
|
10
|
+
].freeze
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def card_classes
|
|
15
|
+
{ classes: klasses(*BASE) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def card_section_classes
|
|
19
|
+
{ classes: klasses("p-(--sp-space-6)") }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Tailwind
|
|
6
|
+
module Combobox
|
|
7
|
+
TRIGGER = %w[
|
|
8
|
+
w-full rounded-(--sp-radius-md) border border-(--sp-color-muted-fg)
|
|
9
|
+
px-(--sp-space-3) py-(--sp-space-2)
|
|
10
|
+
text-(--sp-text-sm) text-(--sp-color-fg) bg-(--sp-color-bg)
|
|
11
|
+
focus:outline-none focus:ring-2 focus:ring-(--sp-focus-ring-color)
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
LISTBOX = %w[
|
|
15
|
+
py-(--sp-space-1) overflow-y-auto max-h-60
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
OPTION_BASE = %w[
|
|
19
|
+
flex items-center gap-(--sp-space-2) w-full
|
|
20
|
+
px-(--sp-space-2) py-(--sp-space-1)
|
|
21
|
+
rounded-(--sp-radius-sm) text-(--sp-text-sm)
|
|
22
|
+
cursor-pointer select-none outline-none
|
|
23
|
+
hover:bg-(--sp-color-muted) focus:bg-(--sp-color-muted)
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
OPTION_SELECTED = %w[
|
|
27
|
+
bg-(--sp-color-primary)/10 text-(--sp-color-primary)
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
OPTION_DISABLED = %w[
|
|
31
|
+
opacity-50 cursor-not-allowed pointer-events-none
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
OPTION_GROUP = %w[py-(--sp-space-1)].freeze
|
|
35
|
+
|
|
36
|
+
AUTOCOMPLETE_LOADING = %w[
|
|
37
|
+
flex items-center justify-center
|
|
38
|
+
py-(--sp-space-2) text-(--sp-text-sm) text-(--sp-color-muted-fg)
|
|
39
|
+
].freeze
|
|
40
|
+
|
|
41
|
+
AUTOCOMPLETE_EMPTY = %w[
|
|
42
|
+
flex items-center justify-center
|
|
43
|
+
py-(--sp-space-2) text-(--sp-text-sm) text-(--sp-color-muted-fg)
|
|
44
|
+
].freeze
|
|
45
|
+
|
|
46
|
+
TIME = %w[flex gap-(--sp-space-2) overflow-hidden].freeze
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def combobox_trigger_classes
|
|
51
|
+
{ classes: klasses(*TRIGGER) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def combobox_listbox_classes
|
|
55
|
+
{ classes: klasses(*LISTBOX) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def combobox_option_classes(selected: false, disabled: false)
|
|
59
|
+
{
|
|
60
|
+
classes: klasses(
|
|
61
|
+
*OPTION_BASE,
|
|
62
|
+
*(selected ? OPTION_SELECTED : []),
|
|
63
|
+
*(disabled ? OPTION_DISABLED : [])
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def combobox_option_group_classes
|
|
69
|
+
{ classes: klasses(*OPTION_GROUP) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def combobox_autocomplete_loading_classes
|
|
73
|
+
{ classes: klasses(*AUTOCOMPLETE_LOADING) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def combobox_autocomplete_empty_classes
|
|
77
|
+
{ classes: klasses(*AUTOCOMPLETE_EMPTY) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def combobox_time_classes
|
|
81
|
+
{ classes: klasses(*TIME) }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Tailwind
|
|
6
|
+
module Form
|
|
7
|
+
INPUT_BASE = %w[
|
|
8
|
+
w-full rounded-md border px-3 py-2 text-sm text-gray-900
|
|
9
|
+
bg-white focus:outline-none focus:ring-2 focus:ring-offset-0
|
|
10
|
+
].freeze
|
|
11
|
+
INPUT_ERROR = %w[border-red-700 focus:ring-red-700].freeze
|
|
12
|
+
INPUT_DEFAULT = %w[border-gray-500 focus:ring-blue-700].freeze
|
|
13
|
+
|
|
14
|
+
GROUP_BASE = %w[flex gap-1 mb-3].freeze
|
|
15
|
+
GROUP_INLINE = %w[flex-row items-center].freeze
|
|
16
|
+
|
|
17
|
+
LABEL = %w[text-sm font-medium text-gray-900].freeze
|
|
18
|
+
REQUIRED_MARK = %w[text-red-700 ml-0.5].freeze
|
|
19
|
+
DETAILS = %w[text-xs text-gray-600].freeze
|
|
20
|
+
ERROR_TEXT = %w[text-xs text-red-700].freeze
|
|
21
|
+
CHECKBOX = %w[size-4 rounded border-gray-500 text-blue-700].freeze
|
|
22
|
+
RADIO = %w[size-4 border-gray-500 text-blue-700].freeze
|
|
23
|
+
|
|
24
|
+
INPUT_GROUP_BASE = %w[flex items-center overflow-hidden rounded-md border].freeze
|
|
25
|
+
INPUT_GROUP_BORDER = { error: "border-red-700", default: "border-gray-500" }.freeze
|
|
26
|
+
|
|
27
|
+
BUTTON_REVEAL = %w[
|
|
28
|
+
self-stretch border-0 bg-transparent px-3 cursor-pointer text-gray-600 hover:text-gray-900 text-sm
|
|
29
|
+
].freeze
|
|
30
|
+
BUTTON_CLEAR = %w[
|
|
31
|
+
self-stretch border-0 bg-transparent px-2 cursor-pointer text-gray-500 hover:text-gray-900 text-sm
|
|
32
|
+
].freeze
|
|
33
|
+
SUBMIT_LINK = %w[cursor-pointer text-sm font-medium text-gray-900 hover:underline].freeze
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def form_group_classes(layout: :stacked, **_rest)
|
|
38
|
+
{ classes: klasses(*GROUP_BASE, layout == :inline ? GROUP_INLINE : "flex-col") }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def form_label_classes(hidden: false, **)
|
|
42
|
+
{ classes: klasses(*LABEL, hidden ? "sr-only" : nil) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def form_required_mark_classes
|
|
46
|
+
{ classes: klasses(*REQUIRED_MARK) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def form_details_classes
|
|
50
|
+
{ classes: klasses(*DETAILS) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def form_error_classes
|
|
54
|
+
{ classes: klasses(*ERROR_TEXT) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def form_input_classes(error: false)
|
|
58
|
+
{ classes: klasses(*INPUT_BASE, *(error ? INPUT_ERROR : INPUT_DEFAULT)) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def form_textarea_classes(error: false)
|
|
62
|
+
form_input_classes(error: error)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def form_file_classes(error: false)
|
|
66
|
+
form_input_classes(error: error)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def form_select_classes(error: false)
|
|
70
|
+
form_input_classes(error: error)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def form_checkbox_classes(**)
|
|
74
|
+
{ classes: klasses(*CHECKBOX) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def form_radio_classes(**)
|
|
78
|
+
{ classes: klasses(*RADIO) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def form_input_group_classes(error: false)
|
|
82
|
+
{ classes: klasses(*INPUT_GROUP_BASE, INPUT_GROUP_BORDER[error ? :error : :default]) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def form_combobox_classes(error: false)
|
|
86
|
+
form_input_classes(error: error)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def form_input_reveal_classes(**)
|
|
90
|
+
{ classes: "" }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def form_input_search_classes
|
|
94
|
+
{ classes: "" }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def form_button_reveal_classes
|
|
98
|
+
{ classes: klasses(*BUTTON_REVEAL) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def form_button_clear_classes
|
|
102
|
+
{ classes: klasses(*BUTTON_CLEAR) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def form_submit_classes(variant: :default)
|
|
106
|
+
case variant
|
|
107
|
+
when :button
|
|
108
|
+
{ classes: klasses(*Button::BASE, *Button::VARIANTS[:primary], *Button::SIZES[:md]) }
|
|
109
|
+
else
|
|
110
|
+
{ classes: klasses(*SUBMIT_LINK) }
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Tailwind
|
|
6
|
+
module Icon
|
|
7
|
+
ICONS = {
|
|
8
|
+
"arrow-left" => { d: "M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" },
|
|
9
|
+
"arrow-right" => { d: "M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" }
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
def icons
|
|
13
|
+
ICONS
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def icon_classes
|
|
19
|
+
{ classes: "" }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Tailwind
|
|
6
|
+
module Layout
|
|
7
|
+
DIVIDER = %w[border-t border-(--sp-color-border) my-(--sp-space-1)].freeze
|
|
8
|
+
POPOVER = %w[
|
|
9
|
+
rounded-(--sp-radius-lg) border border-(--sp-color-border)
|
|
10
|
+
bg-(--sp-color-bg) shadow-(--sp-shadow-md) z-(--sp-z-popover)
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def divider_classes
|
|
16
|
+
{ classes: klasses(*DIVIDER) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def popover_classes
|
|
20
|
+
{ classes: klasses(*POPOVER) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tailwind/action_list"
|
|
4
|
+
require_relative "tailwind/avatar"
|
|
5
|
+
require_relative "tailwind/button"
|
|
6
|
+
require_relative "tailwind/calendar"
|
|
7
|
+
require_relative "tailwind/card"
|
|
8
|
+
require_relative "tailwind/combobox"
|
|
9
|
+
require_relative "tailwind/form"
|
|
10
|
+
require_relative "tailwind/icon"
|
|
11
|
+
require_relative "tailwind/layout"
|
|
12
|
+
|
|
13
|
+
module StimulusPlumbers
|
|
14
|
+
module Themes
|
|
15
|
+
class TailwindTheme < Base
|
|
16
|
+
include Tailwind::ActionList
|
|
17
|
+
include Tailwind::Combobox
|
|
18
|
+
include Tailwind::Avatar
|
|
19
|
+
include Tailwind::Button
|
|
20
|
+
include Tailwind::Calendar
|
|
21
|
+
include Tailwind::Card
|
|
22
|
+
include Tailwind::Form
|
|
23
|
+
include Tailwind::Icon
|
|
24
|
+
include Tailwind::Layout
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def klasses(*classes)
|
|
29
|
+
classes.flatten.reject(&:blank?).join(" ")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbersTailwind
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
initializer "stimulus_plumbers_tailwind.register_theme" do
|
|
6
|
+
StimulusPlumbers.configure do |c|
|
|
7
|
+
c.theme.register(:tailwind, StimulusPlumbers::Themes::TailwindTheme)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stimulus_plumbers"
|
|
4
|
+
|
|
5
|
+
require_relative "stimulus_plumbers_tailwind/version"
|
|
6
|
+
require_relative "stimulus_plumbers/themes/tailwind_theme"
|
|
7
|
+
|
|
8
|
+
if defined?(Rails::Engine)
|
|
9
|
+
require_relative "stimulus_plumbers_tailwind/engine"
|
|
10
|
+
else
|
|
11
|
+
StimulusPlumbers.configure do |c|
|
|
12
|
+
c.theme.register(:tailwind, StimulusPlumbers::Themes::TailwindTheme)
|
|
13
|
+
end
|
|
14
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: stimulus_plumbers_tailwind
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.3.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ryan Chang
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: stimulus_plumbers
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.2.9
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.2.9
|
|
26
|
+
description: Extends stimulus_plumbers with a Tailwind CSS v4 theme
|
|
27
|
+
email:
|
|
28
|
+
- ryancyq@gmail.com
|
|
29
|
+
executables: []
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- CHANGELOG.md
|
|
34
|
+
- README.md
|
|
35
|
+
- lib/stimulus_plumbers/themes/tailwind/action_list.rb
|
|
36
|
+
- lib/stimulus_plumbers/themes/tailwind/avatar.rb
|
|
37
|
+
- lib/stimulus_plumbers/themes/tailwind/button.rb
|
|
38
|
+
- lib/stimulus_plumbers/themes/tailwind/calendar.rb
|
|
39
|
+
- lib/stimulus_plumbers/themes/tailwind/card.rb
|
|
40
|
+
- lib/stimulus_plumbers/themes/tailwind/combobox.rb
|
|
41
|
+
- lib/stimulus_plumbers/themes/tailwind/form.rb
|
|
42
|
+
- lib/stimulus_plumbers/themes/tailwind/icon.rb
|
|
43
|
+
- lib/stimulus_plumbers/themes/tailwind/layout.rb
|
|
44
|
+
- lib/stimulus_plumbers/themes/tailwind_theme.rb
|
|
45
|
+
- lib/stimulus_plumbers_tailwind.rb
|
|
46
|
+
- lib/stimulus_plumbers_tailwind/engine.rb
|
|
47
|
+
- lib/stimulus_plumbers_tailwind/version.rb
|
|
48
|
+
homepage: https://github.com/ryancyq/stimulus-plumbers
|
|
49
|
+
licenses:
|
|
50
|
+
- MIT
|
|
51
|
+
metadata:
|
|
52
|
+
rubygems_mfa_required: 'true'
|
|
53
|
+
allowed_push_host: https://rubygems.org
|
|
54
|
+
changelog_uri: https://github.com/ryancyq/stimulus-plumbers/blob/main/CHANGELOG.md
|
|
55
|
+
homepage_uri: https://github.com/ryancyq/stimulus-plumbers
|
|
56
|
+
source_code_uri: https://github.com/ryancyq/stimulus-plumbers/tree/main/stimulus-plumbers-tailwind
|
|
57
|
+
rdoc_options: []
|
|
58
|
+
require_paths:
|
|
59
|
+
- lib
|
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: '3.0'
|
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: 3.2.0
|
|
70
|
+
requirements: []
|
|
71
|
+
rubygems_version: 3.6.9
|
|
72
|
+
specification_version: 4
|
|
73
|
+
summary: Tailwind CSS theme for stimulus_plumbers
|
|
74
|
+
test_files: []
|