stimulus_plumbers_tailwind 0.3.3 → 0.4.2
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 +68 -0
- data/README.md +6 -4
- data/lib/stimulus_plumbers/themes/tailwind/avatar.rb +23 -15
- data/lib/stimulus_plumbers/themes/tailwind/button/group.rb +36 -0
- data/lib/stimulus_plumbers/themes/tailwind/button.rb +112 -62
- data/lib/stimulus_plumbers/themes/tailwind/calendar.rb +59 -25
- data/lib/stimulus_plumbers/themes/tailwind/card.rb +39 -7
- data/lib/stimulus_plumbers/themes/tailwind/combobox.rb +51 -15
- data/lib/stimulus_plumbers/themes/tailwind/control.rb +17 -0
- data/lib/stimulus_plumbers/themes/tailwind/form/field.rb +159 -0
- data/lib/stimulus_plumbers/themes/tailwind/form/input.rb +240 -0
- data/lib/stimulus_plumbers/themes/tailwind/form.rb +3 -104
- data/lib/stimulus_plumbers/themes/tailwind/icon.rb +9 -3
- data/lib/stimulus_plumbers/themes/tailwind/layout.rb +25 -4
- data/lib/stimulus_plumbers/themes/tailwind/link.rb +87 -0
- data/lib/stimulus_plumbers/themes/tailwind/list.rb +84 -0
- data/lib/stimulus_plumbers/themes/tailwind/timeline/group.rb +28 -0
- data/lib/stimulus_plumbers/themes/tailwind/timeline.rb +104 -0
- data/lib/stimulus_plumbers/themes/tailwind_theme.rb +14 -2
- data/lib/stimulus_plumbers_tailwind/version.rb +1 -1
- metadata +9 -2
- data/lib/stimulus_plumbers/themes/tailwind/action_list.rb +0 -33
|
@@ -4,33 +4,32 @@ module StimulusPlumbers
|
|
|
4
4
|
module Themes
|
|
5
5
|
module Tailwind
|
|
6
6
|
module Combobox
|
|
7
|
+
# ── Container / popover ───────────────────────────────────────────────
|
|
8
|
+
CONTAINER = %w[relative].freeze
|
|
9
|
+
POPOVER = %w[absolute top-full left-0 min-w-full].freeze
|
|
10
|
+
|
|
11
|
+
# ── Trigger ───────────────────────────────────────────────────────────
|
|
7
12
|
TRIGGER = %w[
|
|
8
|
-
w-full rounded-(--sp-radius-md) border border-(--sp-color-muted-fg)
|
|
13
|
+
w-full rounded-(--sp-radius-md) border border-(--sp-color-muted-fg) hover:border-(--sp-color-fg)
|
|
9
14
|
px-(--sp-space-3) py-(--sp-space-2)
|
|
10
15
|
text-(length:--sp-text-sm) text-(--sp-color-fg) bg-(--sp-color-bg)
|
|
11
16
|
focus:outline-none focus:ring-2 focus:ring-(--sp-focus-ring-color)
|
|
12
|
-
[.sp-form-combobox_&]:border-0 [.sp-form-combobox_&]:rounded-none
|
|
13
|
-
[.sp-form-combobox_&]:px-0 [.sp-form-combobox_&]:py-0
|
|
14
|
-
[.sp-form-combobox_&]:bg-transparent [.sp-form-combobox_&]:shadow-none
|
|
15
|
-
[.sp-form-combobox_&]:focus:ring-0
|
|
16
|
-
[.sp-combobox-group_&]:border-0 [.sp-combobox-group_&]:rounded-none
|
|
17
|
-
[.sp-combobox-group_&]:px-0 [.sp-combobox-group_&]:py-0
|
|
18
|
-
[.sp-combobox-group_&]:bg-transparent [.sp-combobox-group_&]:shadow-none
|
|
19
|
-
[.sp-combobox-group_&]:focus:ring-0
|
|
20
17
|
].freeze
|
|
21
18
|
|
|
22
19
|
TRIGGER_GROUP = %w[
|
|
23
20
|
flex items-center gap-(--sp-space-2) overflow-hidden
|
|
24
|
-
rounded-(--sp-radius-md) border border-(--sp-color-muted-fg) bg-(--sp-color-bg)
|
|
21
|
+
rounded-(--sp-radius-md) border border-(--sp-color-muted-fg) hover:border-(--sp-color-fg) bg-(--sp-color-bg)
|
|
25
22
|
px-(--sp-space-3) py-(--sp-space-2)
|
|
26
23
|
focus-within:outline-none focus-within:ring-2 focus-within:ring-(--sp-focus-ring-color)
|
|
27
|
-
|
|
28
|
-
[
|
|
29
|
-
[
|
|
24
|
+
[&>input]:border-0 [&>input]:rounded-none
|
|
25
|
+
[&>input]:px-0 [&>input]:py-0
|
|
26
|
+
[&>input]:bg-transparent [&>input]:shadow-none
|
|
27
|
+
[&>input]:focus:ring-0
|
|
30
28
|
].freeze
|
|
31
29
|
|
|
30
|
+
# ── Listbox / options ─────────────────────────────────────────────────
|
|
32
31
|
LISTBOX = %w[
|
|
33
|
-
py-(--sp-space-1) overflow-y-auto max-h-60
|
|
32
|
+
py-(--sp-space-1) overflow-y-auto max-h-60 min-h-(--sp-space-6)
|
|
34
33
|
].freeze
|
|
35
34
|
|
|
36
35
|
OPTION_BASE = %w[
|
|
@@ -51,6 +50,7 @@ module StimulusPlumbers
|
|
|
51
50
|
|
|
52
51
|
OPTION_GROUP = %w[py-(--sp-space-1)].freeze
|
|
53
52
|
|
|
53
|
+
# ── Typeahead states ──────────────────────────────────────────────────
|
|
54
54
|
TYPEAHEAD_LOADING = %w[
|
|
55
55
|
flex items-center justify-center
|
|
56
56
|
py-(--sp-space-2) text-(length:--sp-text-sm) text-(--sp-color-muted-fg)
|
|
@@ -61,10 +61,26 @@ module StimulusPlumbers
|
|
|
61
61
|
py-(--sp-space-2) text-(length:--sp-text-sm) text-(--sp-color-muted-fg)
|
|
62
62
|
].freeze
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
# ── Time picker ───────────────────────────────────────────────────────
|
|
65
|
+
TIME = %w[flex gap-(--sp-space-2) overflow-hidden].freeze
|
|
66
|
+
TIME_DRUM_UNIT = %w[flex-1 min-w-0].freeze
|
|
67
|
+
TIME_DRUM_PERIOD = %w[shrink-0].freeze
|
|
68
|
+
|
|
69
|
+
# ── Date navigation ───────────────────────────────────────────────────
|
|
70
|
+
DATE_NAV = %w[flex items-center justify-between gap-(--sp-space-1) mb-(--sp-space-2)].freeze
|
|
71
|
+
DATE_NAV_BTN = %w[].freeze
|
|
72
|
+
DATE_NAV_TITLE = %w[flex-1 text-center].freeze
|
|
65
73
|
|
|
66
74
|
private
|
|
67
75
|
|
|
76
|
+
def combobox_classes
|
|
77
|
+
{ classes: klasses(*CONTAINER) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def combobox_popover_classes
|
|
81
|
+
{ classes: klasses(*POPOVER) }
|
|
82
|
+
end
|
|
83
|
+
|
|
68
84
|
def combobox_trigger_classes
|
|
69
85
|
{ classes: klasses(*TRIGGER) }
|
|
70
86
|
end
|
|
@@ -95,6 +111,10 @@ module StimulusPlumbers
|
|
|
95
111
|
{ classes: klasses(*TYPEAHEAD_LOADING) }
|
|
96
112
|
end
|
|
97
113
|
|
|
114
|
+
def combobox_typeahead_loading_icon_classes
|
|
115
|
+
{ classes: klasses("size-(--sp-icon-size-md)", "animate-spin") }
|
|
116
|
+
end
|
|
117
|
+
|
|
98
118
|
def combobox_typeahead_empty_classes
|
|
99
119
|
{ classes: klasses(*TYPEAHEAD_EMPTY) }
|
|
100
120
|
end
|
|
@@ -102,6 +122,22 @@ module StimulusPlumbers
|
|
|
102
122
|
def combobox_time_classes
|
|
103
123
|
{ classes: klasses(*TIME) }
|
|
104
124
|
end
|
|
125
|
+
|
|
126
|
+
def combobox_time_drum_classes(type: :unit)
|
|
127
|
+
{ classes: klasses(*(type == :period ? TIME_DRUM_PERIOD : TIME_DRUM_UNIT)) }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def combobox_date_navigation_classes
|
|
131
|
+
{ classes: klasses(*DATE_NAV) }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def combobox_date_navigation_navigator_classes
|
|
135
|
+
{ classes: klasses(*DATE_NAV_BTN) }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def combobox_date_navigation_title_classes
|
|
139
|
+
{ classes: klasses(*DATE_NAV_TITLE) }
|
|
140
|
+
end
|
|
105
141
|
end
|
|
106
142
|
end
|
|
107
143
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Tailwind
|
|
6
|
+
module Control
|
|
7
|
+
BASE = %w[
|
|
8
|
+
font-medium transition-colors
|
|
9
|
+
focus-visible:outline-none
|
|
10
|
+
focus-visible:ring-(length:--sp-focus-ring-width)
|
|
11
|
+
focus-visible:ring-offset-(length:--sp-focus-ring-offset)
|
|
12
|
+
disabled:pointer-events-none disabled:opacity-50
|
|
13
|
+
].freeze
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Tailwind
|
|
6
|
+
module Form
|
|
7
|
+
module Field
|
|
8
|
+
LABEL = %w[text-(length:--sp-text-sm) font-medium text-(--sp-color-fg)].freeze
|
|
9
|
+
REQUIRED_MARK = %w[text-(--sp-color-error) ml-(--sp-space-0-5)].freeze
|
|
10
|
+
HINT = %w[text-(length:--sp-text-xs) text-(--sp-color-muted-fg)].freeze
|
|
11
|
+
ERROR_TEXT = %w[text-(length:--sp-text-xs) text-(--sp-color-error)].freeze
|
|
12
|
+
|
|
13
|
+
FLOATING_GROUP_TYPES = {
|
|
14
|
+
filled: %w[relative].freeze,
|
|
15
|
+
outlined: %w[relative].freeze,
|
|
16
|
+
standard: %w[relative z-0].freeze
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
FLOATING_LABEL_BASE = %w[
|
|
20
|
+
absolute text-(length:--sp-text-sm) text-(--sp-color-muted-fg)
|
|
21
|
+
duration-300 transform origin-[0]
|
|
22
|
+
].freeze
|
|
23
|
+
FLOATING_LABEL_FOCUS = %w[peer-focus-within:text-(--sp-color-primary)].freeze
|
|
24
|
+
FLOATING_LABEL_ERROR = %w[text-(--sp-color-error)].freeze
|
|
25
|
+
FLOATING_LABEL_TYPES = {
|
|
26
|
+
filled: %w[
|
|
27
|
+
-translate-y-(--sp-space-4) scale-75 top-(--sp-space-4) z-10 start-(--sp-space-2-5)
|
|
28
|
+
peer-placeholder-shown:scale-100
|
|
29
|
+
peer-placeholder-shown:translate-y-0
|
|
30
|
+
peer-has-[input:not(:focus):placeholder-shown]:scale-100
|
|
31
|
+
peer-has-[input:not(:focus):placeholder-shown]:translate-y-0
|
|
32
|
+
peer-focus-within:scale-75
|
|
33
|
+
peer-focus-within:-translate-y-(--sp-space-4)
|
|
34
|
+
rtl:peer-focus-within:translate-x-1/4 rtl:peer-focus-within:left-auto
|
|
35
|
+
].freeze,
|
|
36
|
+
outlined: %w[
|
|
37
|
+
-translate-y-(--sp-space-4) scale-75 top-(--sp-space-2) z-10 start-1
|
|
38
|
+
bg-(--sp-color-bg) px-(--sp-space-2) peer-focus-within:px-(--sp-space-2)
|
|
39
|
+
peer-placeholder-shown:scale-100
|
|
40
|
+
peer-placeholder-shown:-translate-y-1/2
|
|
41
|
+
peer-placeholder-shown:top-1/2
|
|
42
|
+
peer-has-[input:not(:focus):placeholder-shown]:scale-100
|
|
43
|
+
peer-has-[input:not(:focus):placeholder-shown]:-translate-y-1/2
|
|
44
|
+
peer-has-[input:not(:focus):placeholder-shown]:top-1/2
|
|
45
|
+
peer-focus-within:top-(--sp-space-2) peer-focus-within:scale-75
|
|
46
|
+
peer-focus-within:-translate-y-(--sp-space-4)
|
|
47
|
+
rtl:peer-focus-within:translate-x-1/4 rtl:peer-focus-within:left-auto
|
|
48
|
+
].freeze,
|
|
49
|
+
standard: %w[
|
|
50
|
+
-translate-y-(--sp-space-6) scale-75 top-(--sp-space-3) -z-10 start-0
|
|
51
|
+
peer-focus-within:start-0
|
|
52
|
+
peer-placeholder-shown:scale-100
|
|
53
|
+
peer-placeholder-shown:translate-y-0
|
|
54
|
+
peer-has-[input:not(:focus):placeholder-shown]:scale-100
|
|
55
|
+
peer-has-[input:not(:focus):placeholder-shown]:translate-y-0
|
|
56
|
+
peer-focus-within:scale-75
|
|
57
|
+
peer-focus-within:-translate-y-(--sp-space-6)
|
|
58
|
+
rtl:peer-focus-within:translate-x-1/4 rtl:peer-focus-within:left-auto
|
|
59
|
+
].freeze
|
|
60
|
+
}.freeze
|
|
61
|
+
|
|
62
|
+
CHECKBOX_LABEL_TYPES = {
|
|
63
|
+
default: %w[
|
|
64
|
+
flex items-center gap-(--sp-space-2) cursor-pointer py-(--sp-space-0-5) select-none
|
|
65
|
+
text-(length:--sp-text-sm) text-(--sp-color-fg)
|
|
66
|
+
].freeze,
|
|
67
|
+
button: %w[
|
|
68
|
+
flex items-center gap-(--sp-space-3) flex-1 p-(--sp-space-4) cursor-pointer select-none
|
|
69
|
+
text-(length:--sp-text-sm) text-(--sp-color-muted-fg)
|
|
70
|
+
bg-(--sp-color-bg) border border-(--sp-color-border) rounded-(--sp-radius-md) shadow-(--sp-shadow-xs)
|
|
71
|
+
hover:bg-(--sp-color-muted)
|
|
72
|
+
].freeze,
|
|
73
|
+
card: %w[
|
|
74
|
+
flex justify-between items-center gap-(--sp-space-3) flex-1 p-(--sp-space-4) cursor-pointer select-none
|
|
75
|
+
text-(length:--sp-text-sm) text-(--sp-color-muted-fg)
|
|
76
|
+
bg-(--sp-color-bg) border border-(--sp-color-border) rounded-(--sp-radius-md) shadow-(--sp-shadow-xs)
|
|
77
|
+
hover:bg-(--sp-color-muted) hover:border-(--sp-color-border-strong) hover:text-(--sp-color-fg)
|
|
78
|
+
has-[:checked]:border-(--card-ring) has-[:checked]:bg-(--card-ring)/10
|
|
79
|
+
has-[:checked]:text-(--sp-color-fg) has-[:checked]:hover:bg-(--card-ring)/15
|
|
80
|
+
].freeze
|
|
81
|
+
}.freeze
|
|
82
|
+
|
|
83
|
+
RADIO_LABEL_TYPES = {
|
|
84
|
+
default: %w[
|
|
85
|
+
flex items-center gap-(--sp-space-2) cursor-pointer py-(--sp-space-0-5) select-none
|
|
86
|
+
text-(length:--sp-text-sm) text-(--sp-color-fg)
|
|
87
|
+
].freeze,
|
|
88
|
+
button: %w[
|
|
89
|
+
inline-flex items-center justify-between flex-1 p-(--sp-space-4) cursor-pointer select-none
|
|
90
|
+
text-(length:--sp-text-sm) text-(--sp-color-muted-fg)
|
|
91
|
+
bg-(--sp-color-bg) border border-(--sp-color-border) rounded-(--sp-radius-md)
|
|
92
|
+
hover:bg-(--sp-color-muted)
|
|
93
|
+
group-has-[:checked]:border-(--card-ring) group-has-[:checked]:bg-(--card-ring)/10
|
|
94
|
+
group-has-[:checked]:text-(--sp-color-fg) group-has-[:checked]:hover:bg-(--card-ring)/15
|
|
95
|
+
].freeze,
|
|
96
|
+
card: %w[
|
|
97
|
+
flex items-start flex-1 p-(--sp-space-4) cursor-pointer select-none
|
|
98
|
+
text-(length:--sp-text-sm) text-(--sp-color-muted-fg)
|
|
99
|
+
bg-(--sp-color-bg) border border-(--sp-color-border) rounded-(--sp-radius-md) shadow-(--sp-shadow-xs)
|
|
100
|
+
hover:bg-(--sp-color-muted) hover:border-(--sp-color-border-strong) hover:text-(--sp-color-fg)
|
|
101
|
+
group-has-[:checked]:border-(--card-ring) group-has-[:checked]:bg-(--card-ring)/10
|
|
102
|
+
group-has-[:checked]:text-(--sp-color-fg) group-has-[:checked]:hover:bg-(--card-ring)/15
|
|
103
|
+
].freeze
|
|
104
|
+
}.freeze
|
|
105
|
+
|
|
106
|
+
CHOICE_ITEMS_LAYOUT = {
|
|
107
|
+
stacked: %w[flex flex-col gap-(--sp-space-1)].freeze,
|
|
108
|
+
inline: %w[flex flex-row flex-wrap gap-x-(--sp-space-4) gap-y-(--sp-space-1)].freeze
|
|
109
|
+
}.freeze
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def form_field_input_group_classes(floating: nil)
|
|
114
|
+
{ classes: klasses(*FLOATING_GROUP_TYPES.fetch(floating, [])) }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def form_field_label_classes(floating: nil, hidden: false, error: false, **)
|
|
118
|
+
if floating
|
|
119
|
+
color = error ? FLOATING_LABEL_ERROR : FLOATING_LABEL_FOCUS
|
|
120
|
+
{ classes: klasses(*FLOATING_LABEL_BASE, *FLOATING_LABEL_TYPES.fetch(floating, []), *color) }
|
|
121
|
+
else
|
|
122
|
+
{ classes: klasses(*LABEL, hidden ? "sr-only" : nil) }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def form_field_required_mark_classes
|
|
127
|
+
{ classes: klasses(*REQUIRED_MARK) }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def form_field_hint_classes
|
|
131
|
+
{ classes: klasses(*HINT) }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def form_field_error_classes
|
|
135
|
+
{ classes: klasses(*ERROR_TEXT) }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def form_field_choice_items_classes(layout: :stacked)
|
|
139
|
+
{ classes: klasses(*CHOICE_ITEMS_LAYOUT.fetch(layout)) }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def form_field_checkbox_label_classes(type: :default, variant: :default)
|
|
143
|
+
card_color = type == :card ? Card::VARIANTS.fetch(variant, Card::VARIANTS[:tertiary]) : []
|
|
144
|
+
{ classes: klasses(*CHECKBOX_LABEL_TYPES.fetch(type), *card_color) }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def form_field_radio_label_classes(type: :default, variant: :default)
|
|
148
|
+
card_color = %i[button card].include?(type) ? Card::VARIANTS.fetch(variant, Card::VARIANTS[:tertiary]) : []
|
|
149
|
+
{ classes: klasses(*RADIO_LABEL_TYPES.fetch(type), *card_color) }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def form_field_radio_item_group_classes
|
|
153
|
+
{ classes: "contents group" }
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Tailwind
|
|
6
|
+
module Form
|
|
7
|
+
module Input
|
|
8
|
+
# ── Standalone input ──────────────────────────────────────────────────
|
|
9
|
+
INPUT_BASE = %w[
|
|
10
|
+
w-full rounded-(--sp-radius-md) border px-(--sp-space-3) py-(--sp-space-2)
|
|
11
|
+
text-(length:--sp-text-sm) text-(--sp-color-fg) bg-(--sp-color-bg)
|
|
12
|
+
focus:outline-none focus:ring-(length:--sp-focus-ring-width) focus:ring-offset-0
|
|
13
|
+
].freeze
|
|
14
|
+
INPUT_DEFAULT = %w[
|
|
15
|
+
border-(--sp-color-muted-fg) hover:border-(--sp-color-fg)
|
|
16
|
+
focus:ring-(--sp-focus-ring-color)
|
|
17
|
+
].freeze
|
|
18
|
+
INPUT_ERROR = %w[border-(--sp-color-error) focus:ring-(--sp-color-error)].freeze
|
|
19
|
+
|
|
20
|
+
# ── Floating input ────────────────────────────────────────────────────
|
|
21
|
+
FLOATING_INPUT_BASE = %w[
|
|
22
|
+
peer w-full text-(length:--sp-text-sm) text-(--sp-color-fg) appearance-none
|
|
23
|
+
focus:outline-none focus:ring-0
|
|
24
|
+
focus-visible:outline-none focus-visible:ring-0
|
|
25
|
+
].freeze
|
|
26
|
+
FLOATING_INPUT_TYPES = {
|
|
27
|
+
filled: %w[
|
|
28
|
+
rounded-t-(--sp-radius-md) px-(--sp-space-2-5) pb-(--sp-space-2-5) pt-(--sp-space-5)
|
|
29
|
+
bg-(--sp-color-bg-muted) border-0 border-b-2
|
|
30
|
+
].freeze,
|
|
31
|
+
outlined: %w[
|
|
32
|
+
px-(--sp-space-2-5) pb-(--sp-space-2-5) pt-(--sp-space-4)
|
|
33
|
+
bg-transparent rounded-(--sp-radius-md) border
|
|
34
|
+
].freeze,
|
|
35
|
+
standard: %w[
|
|
36
|
+
py-(--sp-space-2-5) px-0
|
|
37
|
+
bg-transparent border-0 border-b-2
|
|
38
|
+
].freeze
|
|
39
|
+
}.freeze
|
|
40
|
+
FLOATING_INPUT_DEFAULT = %w[
|
|
41
|
+
border-(--sp-color-muted-fg) hover:border-(--sp-color-fg)
|
|
42
|
+
focus:border-(--sp-color-primary)
|
|
43
|
+
].freeze
|
|
44
|
+
FLOATING_INPUT_ERROR = %w[border-(--sp-color-error)].freeze
|
|
45
|
+
|
|
46
|
+
# ── Input group ───────────────────────────────────────────────────────
|
|
47
|
+
INPUT_GROUP_BASE = %w[flex items-center overflow-hidden rounded-(--sp-radius-md) border].freeze
|
|
48
|
+
INPUT_GROUP_BORDER = { error: "border-(--sp-color-error)", default: "border-(--sp-color-muted-fg)" }.freeze
|
|
49
|
+
|
|
50
|
+
# ── Floating input group ──────────────────────────────────────────────
|
|
51
|
+
FLOATING_INPUT_GROUP_BASE = %w[flex items-center overflow-hidden peer].freeze
|
|
52
|
+
FLOATING_INPUT_GROUP_TYPES = {
|
|
53
|
+
filled: %w[rounded-t-(--sp-radius-md) bg-(--sp-color-bg-muted) border-0 border-b-2].freeze,
|
|
54
|
+
outlined: %w[rounded-(--sp-radius-md) border].freeze,
|
|
55
|
+
standard: %w[rounded-none bg-transparent border-0 border-b-2].freeze
|
|
56
|
+
}.freeze
|
|
57
|
+
FLOATING_INPUT_GROUP_DEFAULT = %w[
|
|
58
|
+
border-(--sp-color-muted-fg) hover:border-(--sp-color-fg)
|
|
59
|
+
focus-within:border-(--sp-color-primary)
|
|
60
|
+
].freeze
|
|
61
|
+
FLOATING_INPUT_GROUP_ERROR = %w[border-(--sp-color-error)].freeze
|
|
62
|
+
|
|
63
|
+
# ── Combobox wrappers ─────────────────────────────────────────────────
|
|
64
|
+
COMBOBOX_INPUT = %w[
|
|
65
|
+
[&>input:not([type=hidden])]:border-0
|
|
66
|
+
[&>input:not([type=hidden])]:rounded-none
|
|
67
|
+
[&>input:not([type=hidden])]:px-0
|
|
68
|
+
[&>input:not([type=hidden])]:py-0
|
|
69
|
+
[&>input:not([type=hidden])]:bg-transparent
|
|
70
|
+
[&>input:not([type=hidden])]:shadow-none
|
|
71
|
+
[&>input:not([type=hidden])]:focus:ring-0
|
|
72
|
+
].freeze
|
|
73
|
+
COMBOBOX_TRIGGER_GROUP = %w[
|
|
74
|
+
[&>div:first-child]:border-0
|
|
75
|
+
[&>div:first-child]:rounded-none
|
|
76
|
+
[&>div:first-child]:px-0
|
|
77
|
+
[&>div:first-child]:py-0
|
|
78
|
+
[&>div:first-child]:focus-within:ring-0
|
|
79
|
+
].freeze
|
|
80
|
+
|
|
81
|
+
# ── Choice inputs ─────────────────────────────────────────────────────
|
|
82
|
+
CHECKBOX_TYPES = {
|
|
83
|
+
default: %w[
|
|
84
|
+
size-(--sp-control-size) rounded-(--sp-radius-sm) shrink-0
|
|
85
|
+
border border-(--sp-color-border) bg-(--sp-color-muted)
|
|
86
|
+
focus:ring-(length:--sp-focus-ring-width) focus:ring-(--sp-focus-ring-color) focus:outline-none
|
|
87
|
+
disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer
|
|
88
|
+
].freeze,
|
|
89
|
+
button: %w[
|
|
90
|
+
size-(--sp-control-size) rounded-(--sp-radius-sm) shrink-0
|
|
91
|
+
border border-(--sp-color-border) bg-(--sp-color-muted)
|
|
92
|
+
focus:ring-(length:--sp-focus-ring-width) focus:ring-(--sp-focus-ring-color) focus:outline-none
|
|
93
|
+
disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer
|
|
94
|
+
].freeze,
|
|
95
|
+
card: %w[
|
|
96
|
+
size-(--sp-control-size) rounded-(--sp-radius-sm) shrink-0
|
|
97
|
+
border border-(--sp-color-border) bg-(--sp-color-muted)
|
|
98
|
+
checked:border-(--card-ring)
|
|
99
|
+
focus:ring-(length:--sp-focus-ring-width) focus:ring-(--card-ring) focus:outline-none
|
|
100
|
+
disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer
|
|
101
|
+
].freeze
|
|
102
|
+
}.freeze
|
|
103
|
+
|
|
104
|
+
RADIO_TYPES = {
|
|
105
|
+
default: %w[
|
|
106
|
+
size-(--sp-control-size) rounded-full shrink-0
|
|
107
|
+
[accent-color:var(--sp-color-primary)] cursor-pointer
|
|
108
|
+
focus:ring-(length:--sp-focus-ring-width) focus:ring-(--sp-focus-ring-color) focus:outline-none
|
|
109
|
+
disabled:opacity-50 disabled:cursor-not-allowed
|
|
110
|
+
].freeze,
|
|
111
|
+
button: %w[hidden].freeze,
|
|
112
|
+
card: %w[hidden].freeze
|
|
113
|
+
}.freeze
|
|
114
|
+
|
|
115
|
+
# ── Utility buttons ───────────────────────────────────────────────────
|
|
116
|
+
BUTTON_REVEAL = [
|
|
117
|
+
*Control::BASE,
|
|
118
|
+
"inline-flex items-center justify-center",
|
|
119
|
+
"focus-visible:ring-(--sp-focus-ring-color)",
|
|
120
|
+
"self-stretch px-(--sp-space-2) border-0 bg-transparent cursor-pointer text-(--sp-color-muted-fg)",
|
|
121
|
+
"rounded-(--sp-radius-sm) hover:bg-(--sp-color-muted) hover:text-(--sp-color-fg)"
|
|
122
|
+
].freeze
|
|
123
|
+
BUTTON_CLEAR = [
|
|
124
|
+
*Control::BASE,
|
|
125
|
+
"inline-flex items-center justify-center",
|
|
126
|
+
"focus-visible:ring-(--sp-focus-ring-color)",
|
|
127
|
+
"self-stretch px-(--sp-space-2) border-0 bg-transparent cursor-pointer text-(--sp-color-muted-fg)",
|
|
128
|
+
"rounded-(--sp-radius-sm) hover:bg-(--sp-color-muted) hover:text-(--sp-color-fg)"
|
|
129
|
+
].freeze
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
def form_field_input_classes(floating: nil, error: false)
|
|
134
|
+
if floating
|
|
135
|
+
{ classes: klasses(
|
|
136
|
+
*FLOATING_INPUT_BASE,
|
|
137
|
+
*FLOATING_INPUT_TYPES.fetch(floating, []),
|
|
138
|
+
*(error ? FLOATING_INPUT_ERROR : FLOATING_INPUT_DEFAULT)
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
else
|
|
142
|
+
{ classes: klasses(*INPUT_BASE, *(error ? INPUT_ERROR : INPUT_DEFAULT)) }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def form_field_input_textarea_classes(floating: nil, error: false)
|
|
147
|
+
form_field_input_classes(floating: floating, error: error)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def form_field_input_file_classes(floating: nil, error: false)
|
|
151
|
+
form_field_input_classes(floating: floating, error: error)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def form_field_input_select_classes(floating: nil, error: false)
|
|
155
|
+
form_field_input_classes(floating: floating, error: error)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def form_field_input_checkbox_classes(type: :default, variant: :default, **)
|
|
159
|
+
card_color = type == :card ? Card::VARIANTS.fetch(variant, Card::VARIANTS[:tertiary]) : []
|
|
160
|
+
{ classes: klasses(*CHECKBOX_TYPES.fetch(type), *card_color) }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def form_field_input_radio_classes(type: :default, variant: :default, **)
|
|
164
|
+
card_color = %i[button card].include?(type) ? Card::VARIANTS.fetch(variant, Card::VARIANTS[:tertiary]) : []
|
|
165
|
+
{ classes: klasses(*RADIO_TYPES.fetch(type), *card_color) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def input_group_classes(error: false, floating: nil)
|
|
169
|
+
if floating
|
|
170
|
+
color = error ? FLOATING_INPUT_GROUP_ERROR : FLOATING_INPUT_GROUP_DEFAULT
|
|
171
|
+
{ classes: klasses(*FLOATING_INPUT_GROUP_BASE, *FLOATING_INPUT_GROUP_TYPES.fetch(floating, []), *color) }
|
|
172
|
+
else
|
|
173
|
+
{ classes: klasses(*INPUT_GROUP_BASE, INPUT_GROUP_BORDER[error ? :error : :default]) }
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def form_field_input_combobox_classes(floating: nil, error: false)
|
|
178
|
+
if floating
|
|
179
|
+
form_field_input_combobox_floating_classes(floating: floating, error: error)
|
|
180
|
+
else
|
|
181
|
+
{
|
|
182
|
+
classes: klasses(
|
|
183
|
+
*INPUT_BASE,
|
|
184
|
+
*(error ? INPUT_ERROR : INPUT_DEFAULT),
|
|
185
|
+
*COMBOBOX_INPUT,
|
|
186
|
+
*COMBOBOX_TRIGGER_GROUP
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def form_field_input_combobox_floating_classes(floating: :standard, error: false)
|
|
193
|
+
{
|
|
194
|
+
classes: klasses(
|
|
195
|
+
*FLOATING_INPUT_BASE,
|
|
196
|
+
*FLOATING_INPUT_TYPES.fetch(floating, []),
|
|
197
|
+
*(error ? FLOATING_INPUT_ERROR : FLOATING_INPUT_DEFAULT),
|
|
198
|
+
*COMBOBOX_INPUT,
|
|
199
|
+
*COMBOBOX_TRIGGER_GROUP
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def form_field_input_reveal_classes(**)
|
|
205
|
+
{
|
|
206
|
+
classes: klasses(
|
|
207
|
+
"peer",
|
|
208
|
+
"[&>input]:border-0",
|
|
209
|
+
"[&>input]:rounded-none",
|
|
210
|
+
"[&>input]:bg-transparent",
|
|
211
|
+
"[&>input]:shadow-none",
|
|
212
|
+
"[&>input]:focus:ring-0"
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def form_field_input_clearable_classes
|
|
218
|
+
{
|
|
219
|
+
classes: klasses(
|
|
220
|
+
"[&>input]:border-0",
|
|
221
|
+
"[&>input]:rounded-none",
|
|
222
|
+
"[&>input]:bg-transparent",
|
|
223
|
+
"[&>input]:shadow-none",
|
|
224
|
+
"[&>input]:focus:ring-0"
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def form_field_input_button_reveal_classes
|
|
230
|
+
{ classes: klasses(*BUTTON_REVEAL) }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def form_field_input_button_clear_classes
|
|
234
|
+
{ classes: klasses(*BUTTON_CLEAR) }
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
@@ -4,38 +4,8 @@ module StimulusPlumbers
|
|
|
4
4
|
module Themes
|
|
5
5
|
module Tailwind
|
|
6
6
|
module Form
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
text-(--sp-color-fg) bg-(--sp-color-bg) focus:outline-none focus:ring-2 focus:ring-offset-0
|
|
10
|
-
[.sp-form-input-group_&]:border-0 [.sp-form-input-group_&]:rounded-none
|
|
11
|
-
[.sp-form-input-group_&]:bg-transparent [.sp-form-input-group_&]:shadow-none
|
|
12
|
-
[.sp-form-input-group_&]:focus:ring-0
|
|
13
|
-
].freeze
|
|
14
|
-
INPUT_ERROR = %w[border-(--sp-color-error) focus:ring-(--sp-color-error)].freeze
|
|
15
|
-
INPUT_DEFAULT = %w[border-(--sp-color-muted-fg) focus:ring-(--sp-focus-ring-color)].freeze
|
|
16
|
-
|
|
17
|
-
GROUP_BASE = %w[flex gap-(--sp-space-1) mb-(--sp-space-3)].freeze
|
|
18
|
-
GROUP_INLINE = %w[flex-row items-center].freeze
|
|
19
|
-
|
|
20
|
-
LABEL = %w[text-(length:--sp-text-sm) font-medium text-(--sp-color-fg)].freeze
|
|
21
|
-
REQUIRED_MARK = %w[text-(--sp-color-error) ml-(--sp-space-0-5)].freeze
|
|
22
|
-
DETAILS = %w[text-(length:--sp-text-xs) text-(--sp-color-muted-fg)].freeze
|
|
23
|
-
ERROR_TEXT = %w[text-(length:--sp-text-xs) text-(--sp-color-error)].freeze
|
|
24
|
-
CHECKBOX = %w[size-(--sp-control-size) rounded border-(--sp-color-muted-fg) text-(--sp-color-primary)].freeze
|
|
25
|
-
RADIO = %w[size-(--sp-control-size) border-(--sp-color-muted-fg) text-(--sp-color-primary)].freeze
|
|
26
|
-
|
|
27
|
-
INPUT_GROUP_BASE = %w[flex items-center overflow-hidden rounded-(--sp-radius-md) border].freeze
|
|
28
|
-
INPUT_GROUP_BORDER = { error: "border-(--sp-color-error)", default: "border-(--sp-color-muted-fg)" }.freeze
|
|
29
|
-
|
|
30
|
-
BUTTON_REVEAL = %w[
|
|
31
|
-
self-stretch border-0 bg-transparent px-(--sp-space-3) cursor-pointer text-(--sp-color-muted-fg)
|
|
32
|
-
hover:text-(--sp-color-fg) text-(length:--sp-text-sm)
|
|
33
|
-
].freeze
|
|
34
|
-
BUTTON_CLEAR = %w[
|
|
35
|
-
self-stretch border-0 bg-transparent px-(--sp-space-2) cursor-pointer text-(--sp-color-muted-fg)
|
|
36
|
-
hover:text-(--sp-color-fg) text-(length:--sp-text-sm)
|
|
37
|
-
].freeze
|
|
38
|
-
SUBMIT_LINK = %w[cursor-pointer text-(length:--sp-text-sm) font-medium text-(--sp-color-fg) hover:underline].freeze
|
|
7
|
+
GROUP_BASE = %w[flex gap-(--sp-space-1) mb-(--sp-space-3)].freeze
|
|
8
|
+
GROUP_INLINE = %w[flex-row items-center].freeze
|
|
39
9
|
|
|
40
10
|
private
|
|
41
11
|
|
|
@@ -43,78 +13,7 @@ module StimulusPlumbers
|
|
|
43
13
|
{ classes: klasses(*GROUP_BASE, layout == :inline ? GROUP_INLINE : "flex-col") }
|
|
44
14
|
end
|
|
45
15
|
|
|
46
|
-
def
|
|
47
|
-
{ classes: klasses(*LABEL, hidden ? "sr-only" : nil) }
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def form_required_mark_classes
|
|
51
|
-
{ classes: klasses(*REQUIRED_MARK) }
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def form_details_classes
|
|
55
|
-
{ classes: klasses(*DETAILS) }
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def form_error_classes
|
|
59
|
-
{ classes: klasses(*ERROR_TEXT) }
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def form_input_classes(error: false)
|
|
63
|
-
{ classes: klasses(*INPUT_BASE, *(error ? INPUT_ERROR : INPUT_DEFAULT)) }
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def form_textarea_classes(error: false)
|
|
67
|
-
form_input_classes(error: error)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def form_file_classes(error: false)
|
|
71
|
-
form_input_classes(error: error)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def form_select_classes(error: false)
|
|
75
|
-
form_input_classes(error: error)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def form_checkbox_classes(**)
|
|
79
|
-
{ classes: klasses(*CHECKBOX) }
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def form_radio_classes(**)
|
|
83
|
-
{ classes: klasses(*RADIO) }
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def input_group_classes(error: false)
|
|
87
|
-
{ classes: klasses(*INPUT_GROUP_BASE, INPUT_GROUP_BORDER[error ? :error : :default]) }
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def form_combobox_classes(error: false)
|
|
91
|
-
{ classes: klasses(*INPUT_BASE, *(error ? INPUT_ERROR : INPUT_DEFAULT), "sp-form-combobox") }
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def form_input_reveal_classes(**)
|
|
95
|
-
{ classes: "sp-form-input-group" }
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def form_input_clearable_classes
|
|
99
|
-
{ classes: "sp-form-input-group" }
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def form_button_reveal_classes
|
|
103
|
-
{ classes: klasses(*BUTTON_REVEAL) }
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def form_button_clear_classes
|
|
107
|
-
{ classes: klasses(*BUTTON_CLEAR) }
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def form_submit_classes(variant: :default)
|
|
111
|
-
case variant
|
|
112
|
-
when :button
|
|
113
|
-
{ classes: klasses(*Button::BASE, *Button::VARIANTS[:primary], *Button::SIZES[:md]) }
|
|
114
|
-
else
|
|
115
|
-
{ classes: klasses(*SUBMIT_LINK) }
|
|
116
|
-
end
|
|
117
|
-
end
|
|
16
|
+
def form_submit_classes(**) = {}
|
|
118
17
|
end
|
|
119
18
|
end
|
|
120
19
|
end
|
|
@@ -9,8 +9,14 @@ module StimulusPlumbers
|
|
|
9
9
|
module Tailwind
|
|
10
10
|
module Icon
|
|
11
11
|
ALIASES = {
|
|
12
|
-
"close"
|
|
13
|
-
"
|
|
12
|
+
"close" => "x-mark",
|
|
13
|
+
"download" => "arrow-down-tray",
|
|
14
|
+
"book" => "book-open",
|
|
15
|
+
"edit" => "pencil",
|
|
16
|
+
"email" => "envelope",
|
|
17
|
+
"calendar" => "calendar-days",
|
|
18
|
+
"external-link" => "arrow-top-right-on-square",
|
|
19
|
+
"reveal" => "eye"
|
|
14
20
|
}.freeze
|
|
15
21
|
|
|
16
22
|
ICONS = StimulusPlumbers::Themes::Icons::Registry.new(
|
|
@@ -25,7 +31,7 @@ module StimulusPlumbers
|
|
|
25
31
|
private
|
|
26
32
|
|
|
27
33
|
def icon_classes
|
|
28
|
-
{ classes: "size-(--sp-icon-
|
|
34
|
+
{ classes: "size-(--sp-icon-size-lg)" }
|
|
29
35
|
end
|
|
30
36
|
end
|
|
31
37
|
end
|