stimulus_plumbers_tailwind 0.3.3 → 0.4.0

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.
@@ -0,0 +1,172 @@
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_INPUT_BASE = %w[
14
+ peer w-full text-(length:--sp-text-sm) text-(--sp-color-fg) appearance-none
15
+ focus:outline-none focus:ring-0
16
+ ].freeze
17
+ FLOATING_INPUT_TYPES = {
18
+ filled: %w[
19
+ rounded-t-(--sp-radius-md) px-(--sp-space-2-5) pb-(--sp-space-2-5) pt-(--sp-space-5)
20
+ bg-(--sp-color-bg-muted) border-0 border-b-2
21
+ ].freeze,
22
+ outlined: %w[
23
+ px-(--sp-space-2-5) pb-(--sp-space-2-5) pt-(--sp-space-4)
24
+ bg-transparent rounded-(--sp-radius-md) border
25
+ ].freeze,
26
+ standard: %w[
27
+ py-(--sp-space-2-5) px-0
28
+ bg-transparent border-0 border-b-2
29
+ ].freeze
30
+ }.freeze
31
+ FLOATING_INPUT_ERROR = %w[border-(--sp-color-error)].freeze
32
+ FLOATING_INPUT_DEFAULT = %w[border-(--sp-color-muted-fg) focus:border-(--sp-color-primary)].freeze
33
+
34
+ FLOATING_GROUP_TYPES = {
35
+ filled: %w[relative].freeze,
36
+ outlined: %w[relative].freeze,
37
+ standard: %w[relative z-0].freeze
38
+ }.freeze
39
+
40
+ FLOATING_LABEL_BASE = %w[
41
+ absolute text-(length:--sp-text-sm) text-(--sp-color-muted-fg)
42
+ duration-300 transform origin-[0]
43
+ ].freeze
44
+ FLOATING_LABEL_FOCUS = %w[peer-focus:text-(--sp-color-primary)].freeze
45
+ FLOATING_LABEL_ERROR = %w[text-(--sp-color-error)].freeze
46
+ FLOATING_LABEL_TYPES = {
47
+ filled: %w[
48
+ -translate-y-(--sp-space-4) scale-75 top-(--sp-space-4) z-10 start-(--sp-space-2-5)
49
+ peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0
50
+ peer-focus:scale-75 peer-focus:-translate-y-(--sp-space-4)
51
+ rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto
52
+ ].freeze,
53
+ outlined: %w[
54
+ -translate-y-(--sp-space-4) scale-75 top-(--sp-space-2) z-10 start-1
55
+ bg-(--sp-color-bg) px-(--sp-space-2) peer-focus:px-(--sp-space-2)
56
+ peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2
57
+ peer-focus:top-(--sp-space-2) peer-focus:scale-75 peer-focus:-translate-y-(--sp-space-4)
58
+ rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto
59
+ ].freeze,
60
+ standard: %w[
61
+ -translate-y-(--sp-space-6) scale-75 top-(--sp-space-3) -z-10 start-0
62
+ peer-focus:start-0
63
+ peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0
64
+ peer-focus:scale-75 peer-focus:-translate-y-(--sp-space-6)
65
+ rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto
66
+ ].freeze
67
+ }.freeze
68
+
69
+ CHECKBOX_LABEL_TYPES = {
70
+ default: %w[
71
+ flex items-center gap-(--sp-space-2) cursor-pointer py-(--sp-space-0-5) select-none
72
+ text-(length:--sp-text-sm) text-(--sp-color-fg)
73
+ ].freeze,
74
+ button: %w[
75
+ flex items-center gap-(--sp-space-3) flex-1 p-(--sp-space-4) cursor-pointer select-none
76
+ text-(length:--sp-text-sm) text-(--sp-color-muted-fg)
77
+ bg-(--sp-color-bg) border border-(--sp-color-border) rounded-(--sp-radius-md) shadow-(--sp-shadow-xs)
78
+ hover:bg-(--sp-color-muted)
79
+ ].freeze,
80
+ card: %w[
81
+ flex justify-between items-start flex-1 p-(--sp-space-4) cursor-pointer select-none
82
+ text-(length:--sp-text-sm) text-(--sp-color-muted-fg)
83
+ bg-(--sp-color-bg) border border-(--sp-color-border) rounded-(--sp-radius-md) shadow-(--sp-shadow-xs)
84
+ hover:bg-(--sp-color-muted) hover:border-(--sp-color-border-strong) hover:text-(--sp-color-fg)
85
+ has-[:checked]:border-(--card-ring) has-[:checked]:bg-(--card-ring)/10
86
+ has-[:checked]:text-(--sp-color-fg) has-[:checked]:hover:bg-(--card-ring)/15
87
+ ].freeze
88
+ }.freeze
89
+
90
+ RADIO_LABEL_TYPES = {
91
+ default: %w[
92
+ flex items-center gap-(--sp-space-2) cursor-pointer py-(--sp-space-0-5) select-none
93
+ text-(length:--sp-text-sm) text-(--sp-color-fg)
94
+ ].freeze,
95
+ button: %w[
96
+ inline-flex items-center justify-between flex-1 p-(--sp-space-4) cursor-pointer select-none
97
+ text-(length:--sp-text-sm) text-(--sp-color-muted-fg)
98
+ bg-(--sp-color-bg) border border-(--sp-color-border) rounded-(--sp-radius-md)
99
+ hover:bg-(--sp-color-muted)
100
+ peer-checked:border-(--card-ring) peer-checked:bg-(--card-ring)/10
101
+ peer-checked:text-(--sp-color-fg) peer-checked:hover:bg-(--card-ring)/15
102
+ ].freeze,
103
+ card: %w[
104
+ flex items-start flex-1 p-(--sp-space-4) cursor-pointer select-none
105
+ text-(length:--sp-text-sm) text-(--sp-color-muted-fg)
106
+ bg-(--sp-color-bg) border border-(--sp-color-border) rounded-(--sp-radius-md) shadow-(--sp-shadow-xs)
107
+ hover:bg-(--sp-color-muted) hover:border-(--sp-color-border-strong) hover:text-(--sp-color-fg)
108
+ peer-checked:border-(--card-ring) peer-checked:bg-(--card-ring)/10
109
+ peer-checked:text-(--sp-color-fg) peer-checked:hover:bg-(--card-ring)/15
110
+ ].freeze
111
+ }.freeze
112
+
113
+ CHOICE_ITEMS_LAYOUT = {
114
+ stacked: %w[flex flex-col gap-(--sp-space-1)].freeze,
115
+ inline: %w[flex flex-row flex-wrap gap-x-(--sp-space-4) gap-y-(--sp-space-1)].freeze
116
+ }.freeze
117
+
118
+ private
119
+
120
+ def form_field_floating_classes(type: nil, error: false)
121
+ {
122
+ classes: klasses(
123
+ *FLOATING_INPUT_BASE,
124
+ *FLOATING_INPUT_TYPES.fetch(type, []),
125
+ *(error ? FLOATING_INPUT_ERROR : FLOATING_INPUT_DEFAULT)
126
+ )
127
+ }
128
+ end
129
+
130
+ def form_field_floating_group_classes(type: nil)
131
+ { classes: klasses(*FLOATING_GROUP_TYPES.fetch(type, [])) }
132
+ end
133
+
134
+ def form_field_floating_label_classes(type: nil, error: false)
135
+ color = error ? FLOATING_LABEL_ERROR : FLOATING_LABEL_FOCUS
136
+ { classes: klasses(*FLOATING_LABEL_BASE, *FLOATING_LABEL_TYPES.fetch(type, []), *color) }
137
+ end
138
+
139
+ def form_field_label_classes(hidden: false, **)
140
+ { classes: klasses(*LABEL, hidden ? "sr-only" : nil) }
141
+ end
142
+
143
+ def form_field_required_mark_classes
144
+ { classes: klasses(*REQUIRED_MARK) }
145
+ end
146
+
147
+ def form_field_hint_classes
148
+ { classes: klasses(*HINT) }
149
+ end
150
+
151
+ def form_field_error_classes
152
+ { classes: klasses(*ERROR_TEXT) }
153
+ end
154
+
155
+ def form_field_choice_items_classes(layout: :stacked)
156
+ { classes: klasses(*CHOICE_ITEMS_LAYOUT.fetch(layout)) }
157
+ end
158
+
159
+ def form_field_checkbox_label_classes(type: :default, variant: :default)
160
+ card_color = type == :card ? Card::VARIANTS.fetch(variant, Card::VARIANTS[:tertiary]) : []
161
+ { classes: klasses(*CHECKBOX_LABEL_TYPES.fetch(type), *card_color) }
162
+ end
163
+
164
+ def form_field_radio_label_classes(type: :default, variant: :default)
165
+ card_color = %i[button card].include?(type) ? Card::VARIANTS.fetch(variant, Card::VARIANTS[:tertiary]) : []
166
+ { classes: klasses(*RADIO_LABEL_TYPES.fetch(type), *card_color) }
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Themes
5
+ module Tailwind
6
+ module Form
7
+ module Input
8
+ INPUT_BASE = %w[
9
+ w-full rounded-(--sp-radius-md) border px-(--sp-space-3) py-(--sp-space-2) text-(length:--sp-text-sm)
10
+ text-(--sp-color-fg) bg-(--sp-color-bg) focus:outline-none focus:ring-2 focus:ring-offset-0
11
+ ].freeze
12
+ INPUT_ERROR = %w[border-(--sp-color-error) focus:ring-(--sp-color-error)].freeze
13
+ INPUT_DEFAULT = %w[border-(--sp-color-muted-fg) focus:ring-(--sp-focus-ring-color)].freeze
14
+
15
+ CHECKBOX_TYPES = {
16
+ default: %w[
17
+ size-(--sp-control-size) rounded-(--sp-radius-sm) shrink-0
18
+ border border-(--sp-color-border) bg-(--sp-color-muted)
19
+ focus:ring-2 focus:ring-(--sp-focus-ring-color) focus:outline-none
20
+ disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer
21
+ ].freeze,
22
+ button: %w[
23
+ size-(--sp-control-size) rounded-(--sp-radius-sm) shrink-0
24
+ border border-(--sp-color-border) bg-(--sp-color-muted)
25
+ focus:ring-2 focus:ring-(--sp-focus-ring-color) focus:outline-none
26
+ disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer
27
+ ].freeze,
28
+ card: %w[
29
+ size-(--sp-control-size) rounded-(--sp-radius-sm) shrink-0
30
+ border border-(--sp-color-border) bg-(--sp-color-muted)
31
+ checked:border-(--card-ring)
32
+ focus:ring-2 focus:ring-(--card-ring) focus:outline-none
33
+ disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer
34
+ ].freeze
35
+ }.freeze
36
+
37
+ RADIO_TYPES = {
38
+ default: %w[
39
+ size-(--sp-control-size) rounded-full shrink-0
40
+ [accent-color:var(--sp-color-primary)] cursor-pointer
41
+ focus:ring-2 focus:ring-(--sp-focus-ring-color) focus:outline-none
42
+ disabled:opacity-50 disabled:cursor-not-allowed
43
+ ].freeze,
44
+ button: %w[hidden peer].freeze,
45
+ card: %w[hidden peer].freeze
46
+ }.freeze
47
+
48
+ INPUT_GROUP_BASE = %w[flex items-center overflow-hidden rounded-(--sp-radius-md) border].freeze
49
+ INPUT_GROUP_BORDER = { error: "border-(--sp-color-error)", default: "border-(--sp-color-muted-fg)" }.freeze
50
+
51
+ COMBOBOX_INPUT = %w[
52
+ [&>input:not([type=hidden])]:border-0
53
+ [&>input:not([type=hidden])]:rounded-none
54
+ [&>input:not([type=hidden])]:px-0
55
+ [&>input:not([type=hidden])]:py-0
56
+ [&>input:not([type=hidden])]:bg-transparent
57
+ [&>input:not([type=hidden])]:shadow-none
58
+ [&>input:not([type=hidden])]:focus:ring-0
59
+ ].freeze
60
+ COMBOBOX_TRIGGER_GROUP = %w[
61
+ [&>div:first-child]:border-0
62
+ [&>div:first-child]:rounded-none
63
+ [&>div:first-child]:px-0
64
+ [&>div:first-child]:py-0
65
+ [&>div:first-child]:focus-within:ring-0
66
+ ].freeze
67
+
68
+ BUTTON_REVEAL = %w[
69
+ self-stretch border-0 bg-transparent px-(--sp-space-3) cursor-pointer text-(--sp-color-muted-fg)
70
+ hover:text-(--sp-color-fg) text-(length:--sp-text-sm)
71
+ ].freeze
72
+ BUTTON_CLEAR = %w[
73
+ self-stretch border-0 bg-transparent px-(--sp-space-2) cursor-pointer text-(--sp-color-muted-fg)
74
+ hover:text-(--sp-color-fg) text-(length:--sp-text-sm)
75
+ ].freeze
76
+
77
+ private
78
+
79
+ def form_field_input_classes(error: false)
80
+ { classes: klasses(*INPUT_BASE, *(error ? INPUT_ERROR : INPUT_DEFAULT)) }
81
+ end
82
+
83
+ def form_field_input_textarea_classes(error: false)
84
+ form_field_input_classes(error: error)
85
+ end
86
+
87
+ def form_field_input_file_classes(error: false)
88
+ form_field_input_classes(error: error)
89
+ end
90
+
91
+ def form_field_input_select_classes(error: false)
92
+ form_field_input_classes(error: error)
93
+ end
94
+
95
+ def form_field_input_checkbox_classes(type: :default, variant: :default, **)
96
+ card_color = type == :card ? Card::VARIANTS.fetch(variant, Card::VARIANTS[:tertiary]) : []
97
+ { classes: klasses(*CHECKBOX_TYPES.fetch(type), *card_color) }
98
+ end
99
+
100
+ def form_field_input_radio_classes(type: :default, variant: :default, **)
101
+ card_color = %i[button card].include?(type) ? Card::VARIANTS.fetch(variant, Card::VARIANTS[:tertiary]) : []
102
+ { classes: klasses(*RADIO_TYPES.fetch(type), *card_color) }
103
+ end
104
+
105
+ def input_group_classes(error: false)
106
+ { classes: klasses(*INPUT_GROUP_BASE, INPUT_GROUP_BORDER[error ? :error : :default]) }
107
+ end
108
+
109
+ def form_field_input_combobox_classes(error: false)
110
+ { classes: klasses(
111
+ *INPUT_BASE,
112
+ *(error ? INPUT_ERROR : INPUT_DEFAULT),
113
+ *COMBOBOX_INPUT,
114
+ *COMBOBOX_TRIGGER_GROUP
115
+ )
116
+ }
117
+ end
118
+
119
+ def form_field_input_reveal_classes(**)
120
+ {
121
+ classes: klasses(
122
+ "[&>input]:border-0",
123
+ "[&>input]:rounded-none",
124
+ "[&>input]:bg-transparent",
125
+ "[&>input]:shadow-none",
126
+ "[&>input]:focus:ring-0"
127
+ )
128
+ }
129
+ end
130
+
131
+ def form_field_input_clearable_classes
132
+ {
133
+ classes: klasses(
134
+ "[&>input]:border-0",
135
+ "[&>input]:rounded-none",
136
+ "[&>input]:bg-transparent",
137
+ "[&>input]:shadow-none",
138
+ "[&>input]:focus:ring-0"
139
+ )
140
+ }
141
+ end
142
+
143
+ def form_field_input_button_reveal_classes
144
+ { classes: klasses(*BUTTON_REVEAL) }
145
+ end
146
+
147
+ def form_field_input_button_clear_classes
148
+ { classes: klasses(*BUTTON_CLEAR) }
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -4,38 +4,8 @@ module StimulusPlumbers
4
4
  module Themes
5
5
  module Tailwind
6
6
  module Form
7
- INPUT_BASE = %w[
8
- w-full rounded-(--sp-radius-md) border px-(--sp-space-3) py-(--sp-space-2) text-(length:--sp-text-sm)
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,77 +13,15 @@ module StimulusPlumbers
43
13
  { classes: klasses(*GROUP_BASE, layout == :inline ? GROUP_INLINE : "flex-col") }
44
14
  end
45
15
 
46
- def form_label_classes(hidden: false, **)
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
16
+ def form_submit_classes(type: :default, variant: :primary)
17
+ { classes: klasses(
18
+ *Button::BASE,
19
+ *Button::LAYOUT,
20
+ *Button::VARIANTS.fetch(variant, Button::VARIANTS[:primary]),
21
+ *Button::TYPES.fetch(type, Button::TYPES[:default]),
22
+ *(type == :card ? [] : Button::SIZES[:md])
23
+ )
24
+ }
117
25
  end
118
26
  end
119
27
  end
@@ -9,8 +9,9 @@ module StimulusPlumbers
9
9
  module Tailwind
10
10
  module Icon
11
11
  ALIASES = {
12
- "close" => "x-mark",
13
- "calendar" => "calendar-days"
12
+ "close" => "x-mark",
13
+ "calendar" => "calendar-days",
14
+ "external-link" => "arrow-top-right-on-square"
14
15
  }.freeze
15
16
 
16
17
  ICONS = StimulusPlumbers::Themes::Icons::Registry.new(
@@ -7,8 +7,18 @@ module StimulusPlumbers
7
7
  DIVIDER_SEPARATOR = %w[border-t border-(--sp-color-border) my-(--sp-space-1)].freeze
8
8
  DIVIDER = %w[flex items-center gap-(--sp-space-3)].freeze
9
9
  DIVIDER_LABEL = %w[text-(length:--sp-text-sm) text-(--sp-color-fg-muted) whitespace-nowrap font-medium].freeze
10
+ POPOVER_WRAPPER = %w[relative inline-block].freeze
11
+ POPOVER_TRIGGER = [
12
+ *Control::BASE,
13
+ "inline-flex items-center justify-center gap-(--sp-space-2)",
14
+ "rounded-(--sp-radius-md)",
15
+ "focus-visible:ring-(--sp-focus-ring-color)",
16
+ "border border-(--sp-color-border) bg-transparent text-(--sp-color-fg)",
17
+ "hover:bg-(--sp-color-muted)",
18
+ "h-9 px-(--sp-space-4) py-(--sp-space-2) text-(length:--sp-text-sm)"
19
+ ].freeze
10
20
  POPOVER = %w[
11
- rounded-(--sp-radius-lg) border border-(--sp-color-border)
21
+ rounded-(--sp-radius-md) border border-(--sp-color-border)
12
22
  bg-(--sp-color-bg) shadow-(--sp-shadow-md) z-(--sp-z-popover)
13
23
  ].freeze
14
24
 
@@ -26,6 +36,14 @@ module StimulusPlumbers
26
36
  { classes: klasses(*DIVIDER_LABEL) }
27
37
  end
28
38
 
39
+ def popover_wrapper_classes
40
+ { classes: klasses(*POPOVER_WRAPPER) }
41
+ end
42
+
43
+ def popover_trigger_classes
44
+ { classes: klasses(*POPOVER_TRIGGER) }
45
+ end
46
+
29
47
  def popover_classes
30
48
  { classes: klasses(*POPOVER) }
31
49
  end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Themes
5
+ module Tailwind
6
+ module Link
7
+ VARIANTS = {
8
+ default: %w[
9
+ [--link-color:var(--sp-color-primary)]
10
+ [--link-ring:var(--sp-color-primary)]
11
+ [--link-bg:var(--sp-color-primary)]
12
+ ].freeze,
13
+ success: %w[
14
+ [--link-color:var(--sp-color-success)]
15
+ [--link-ring:var(--sp-color-success-ring)]
16
+ [--link-bg:var(--sp-color-success)]
17
+ ].freeze,
18
+ destructive: %w[
19
+ [--link-color:var(--sp-color-destructive)]
20
+ [--link-ring:var(--sp-color-destructive)]
21
+ [--link-bg:var(--sp-color-destructive)]
22
+ ].freeze,
23
+ warning: %w[
24
+ [--link-color:var(--sp-color-warning)]
25
+ [--link-ring:var(--sp-color-warning-ring)]
26
+ [--link-bg:var(--sp-color-warning)]
27
+ ].freeze,
28
+ info: %w[
29
+ [--link-color:var(--sp-color-info)]
30
+ [--link-ring:var(--sp-color-info-ring)]
31
+ [--link-bg:var(--sp-color-info)]
32
+ ].freeze
33
+ }.freeze
34
+
35
+ BASE = [
36
+ *Control::BASE,
37
+ "inline-flex items-center gap-(--sp-space-1)",
38
+ "text-(--link-color)",
39
+ "hover:underline",
40
+ "focus-visible:ring-(--link-ring)"
41
+ ].freeze
42
+
43
+ BUTTON = [
44
+ *Control::BASE,
45
+ "whitespace-nowrap",
46
+ "inline-flex items-center justify-center gap-(--sp-space-2)",
47
+ "h-9 px-(--sp-space-4) text-(length:--sp-text-base)",
48
+ "[&:not(:has(>span))]:aspect-square",
49
+ "[&:not(:has(>span))]:px-0",
50
+ "rounded-(--sp-radius-md)",
51
+ "bg-(--sp-color-bg) text-(--link-color)",
52
+ "border border-(--link-color)",
53
+ "hover:bg-(--link-bg)/10",
54
+ "focus-visible:ring-(--link-ring)"
55
+ ].freeze
56
+
57
+ CARD = [
58
+ *Control::BASE,
59
+ "whitespace-nowrap",
60
+ *Button::CARD,
61
+ "rounded-(--sp-radius-md)",
62
+ "bg-(--sp-color-bg) text-(--link-color)",
63
+ "border border-(--link-color) shadow-(--sp-shadow-xs)",
64
+ "hover:bg-(--link-bg)/10",
65
+ "focus-visible:ring-(--link-ring)"
66
+ ].freeze
67
+
68
+ private
69
+
70
+ def link_classes(type: :default, variant: :default)
71
+ variant_classes = VARIANTS.fetch(variant, VARIANTS[:default])
72
+ case type
73
+ when :button then { classes: klasses(*BUTTON, *variant_classes) }
74
+ when :card then { classes: klasses(*CARD, *variant_classes) }
75
+ else { classes: klasses(*BASE, *variant_classes) }
76
+ end
77
+ end
78
+
79
+ def link_icon_classes
80
+ { classes: klasses("size-(--sp-control-size)", "stroke-current") }
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Themes
5
+ module Tailwind
6
+ module List
7
+ ITEM_BASE = [
8
+ *Control::BASE,
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-(length:--sp-text-sm)",
12
+ "cursor-pointer select-none",
13
+ "text-(--sp-color-fg)",
14
+ "focus-visible:ring-(--sp-color-primary-ring)",
15
+ "hover:bg-(--sp-color-muted) focus:bg-(--sp-color-muted) focus:text-(--sp-color-fg)",
16
+ "aria-[current]:bg-(--sp-color-primary)/10 aria-[current]:text-(--sp-color-primary)"
17
+ ].freeze
18
+
19
+ ITEM_CONTENT_BASE = %w[
20
+ flex flex-col flex-1 min-w-0
21
+ ].freeze
22
+
23
+ ITEM_TITLE_BASE = %w[
24
+ text-(length:--sp-text-sm) font-medium text-(--sp-color-fg)
25
+ ].freeze
26
+
27
+ ITEM_DESCRIPTION_BASE = %w[
28
+ text-(length:--sp-text-xs) text-(--sp-color-muted-fg)
29
+ ].freeze
30
+
31
+ SECTION_TITLE_BASE = %w[
32
+ block px-(--sp-space-2) pb-(--sp-space-1)
33
+ text-(length:--sp-text-xs) font-semibold uppercase tracking-wider
34
+ text-(--sp-color-muted-fg)
35
+ ].freeze
36
+
37
+ SECTION_DESCRIPTION_BASE = %w[
38
+ block px-(--sp-space-2) pb-(--sp-space-1)
39
+ text-(length:--sp-text-xs) text-(--sp-color-muted-fg)
40
+ ].freeze
41
+
42
+ private
43
+
44
+ def list_classes
45
+ { classes: klasses("py-(--sp-space-1) divide-y divide-(--sp-color-border)") }
46
+ end
47
+
48
+ def list_section_classes
49
+ { classes: klasses("py-(--sp-space-2)") }
50
+ end
51
+
52
+ def list_section_title_classes
53
+ { classes: klasses(*SECTION_TITLE_BASE) }
54
+ end
55
+
56
+ def list_section_description_classes
57
+ { classes: klasses(*SECTION_DESCRIPTION_BASE) }
58
+ end
59
+
60
+ def list_item_classes
61
+ { classes: klasses(*ITEM_BASE) }
62
+ end
63
+
64
+ def list_item_icon_classes
65
+ { classes: klasses("size-(--sp-control-size)", "stroke-current") }
66
+ end
67
+
68
+ def list_item_content_classes
69
+ { classes: klasses(*ITEM_CONTENT_BASE) }
70
+ end
71
+
72
+ def list_item_title_classes
73
+ { classes: klasses(*ITEM_TITLE_BASE) }
74
+ end
75
+
76
+ def list_item_description_classes
77
+ { classes: klasses(*ITEM_DESCRIPTION_BASE) }
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end