senren-ui 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +111 -0
- data/CONTRIBUTING.md +1 -0
- data/README.md +28 -0
- data/docs/visual_style.md +80 -0
- data/lib/generators/senren/install/install_generator.rb +3 -0
- data/lib/generators/senren/install/templates/base_component.rb.tt +97 -7
- data/lib/generators/senren/install/templates/conventions.md.tt +41 -0
- data/lib/generators/senren/install/templates/senren_themes.css.tt +339 -0
- data/lib/senren/rails/agent_rules_writer.rb +3 -0
- data/lib/senren/rails/version.rb +1 -1
- data/templates/components/accordion/accordion_component.html.erb +3 -3
- data/templates/components/alert_dialog/alert_dialog_component.html.erb +6 -3
- data/templates/components/alert_dialog/alert_dialog_component.rb +4 -0
- data/templates/components/api_key_field/api_key_field_component.html.erb +1 -1
- data/templates/components/breadcrumb/breadcrumb_component.html.erb +8 -6
- data/templates/components/button/button_component.html.erb +1 -1
- data/templates/components/button/button_component.rb +27 -1
- data/templates/components/calendar/calendar_component.html.erb +2 -2
- data/templates/components/card/card_component.html.erb +4 -6
- data/templates/components/carousel/carousel_component.html.erb +1 -1
- data/templates/components/cart/cart_component.html.erb +7 -1
- data/templates/components/clipboard/clipboard_component.html.erb +3 -3
- data/templates/components/collapsible/collapsible_component.html.erb +3 -3
- data/templates/components/combobox/combobox_component.html.erb +3 -3
- data/templates/components/command/command_component.html.erb +1 -1
- data/templates/components/context_menu/context_menu_component.html.erb +6 -3
- data/templates/components/date_picker/date_picker_component.html.erb +3 -3
- data/templates/components/dialog/dialog_component.html.erb +7 -3
- data/templates/components/dialog/dialog_component.rb +11 -0
- data/templates/components/dropdown_menu/dropdown_menu_component.html.erb +6 -3
- data/templates/components/dropdown_menu/dropdown_menu_component.rb +57 -5
- data/templates/components/hover_card/hover_card_component.html.erb +6 -3
- data/templates/components/invite_member_dialog/invite_member_dialog_component.html.erb +3 -2
- data/templates/components/link/link_component.html.erb +1 -1
- data/templates/components/native_select/native_select_component.html.erb +2 -2
- data/templates/components/pagination/pagination_component.html.erb +2 -2
- data/templates/components/popover/popover_component.html.erb +6 -3
- data/templates/components/product_card/product_card_component.html.erb +2 -2
- data/templates/components/progress/progress_component.html.erb +2 -2
- data/templates/components/search_input/search_input_component.html.erb +2 -2
- data/templates/components/sheet/sheet_component.html.erb +13 -4
- data/templates/components/sheet/sheet_component.rb +11 -0
- data/templates/components/shortcut_key/shortcut_key_component.html.erb +2 -2
- data/templates/components/sidebar/sidebar_component.html.erb +2 -2
- data/templates/components/switch/switch_component.html.erb +1 -1
- data/templates/components/table/table_component.html.erb +2 -2
- data/templates/components/tabs/tabs_component.html.erb +3 -3
- data/templates/components/tooltip/tooltip_component.html.erb +5 -3
- data/templates/components/top_nav/top_nav_component.html.erb +2 -2
- data/templates/controllers/cart_controller.js +3 -1
- metadata +2 -1
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Senren UI - optional palette presets.
|
|
3
|
+
*
|
|
4
|
+
* Link this after senren.css and set data-senren-theme on <html> to switch:
|
|
5
|
+
*
|
|
6
|
+
* <%%= stylesheet_link_tag "senren" %>
|
|
7
|
+
* <%%= stylesheet_link_tag "senren_themes" %>
|
|
8
|
+
*
|
|
9
|
+
* <html data-senren-theme="slate">
|
|
10
|
+
*
|
|
11
|
+
* Leave the attribute off for the default Spring Garden palette, which
|
|
12
|
+
* senren.css declares in :root.
|
|
13
|
+
*
|
|
14
|
+
* Every theme below is nothing but a re-declaration of the same token set that
|
|
15
|
+
* senren.css defines. No component is aware that themes exist: components read
|
|
16
|
+
* hsl(var(--senren-*)) and inherit whatever is in scope. Adding your own theme
|
|
17
|
+
* means copying one of these blocks and changing the values — there is no
|
|
18
|
+
* component to touch and no build step to run.
|
|
19
|
+
*
|
|
20
|
+
* Scoping: [data-senren-theme] on <html>, so it composes with the existing
|
|
21
|
+
* .dark class rather than replacing it. Each theme therefore ships a light and
|
|
22
|
+
* a dark block, and light/dark stays orthogonal to the palette choice.
|
|
23
|
+
*
|
|
24
|
+
* `senren` is the default and needs no block: it is what senren.css already
|
|
25
|
+
* declares in :root and .dark.
|
|
26
|
+
*
|
|
27
|
+
* test/themes_test.rb asserts every theme here declares the complete token set,
|
|
28
|
+
* so a palette cannot ship with a variable quietly missing.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/* ── slate ────────────────────────────────────────────────────────────────
|
|
32
|
+
Cool neutrals, near-black primary, tight radius. The restrained look most
|
|
33
|
+
developer tools converge on. */
|
|
34
|
+
[data-senren-theme="slate"] {
|
|
35
|
+
--senren-background: 0 0% 100%;
|
|
36
|
+
--senren-foreground: 222 47% 11%;
|
|
37
|
+
--senren-muted: 210 40% 96%;
|
|
38
|
+
--senren-muted-foreground: 215 16% 47%;
|
|
39
|
+
--senren-card: 0 0% 100%;
|
|
40
|
+
--senren-card-foreground: 222 47% 11%;
|
|
41
|
+
--senren-popover: 0 0% 100%;
|
|
42
|
+
--senren-popover-foreground: 222 47% 11%;
|
|
43
|
+
--senren-border: 214 32% 91%;
|
|
44
|
+
--senren-input: 214 32% 91%;
|
|
45
|
+
--senren-ring: 222 47% 11%;
|
|
46
|
+
--senren-primary: 222 47% 11%;
|
|
47
|
+
--senren-primary-foreground: 210 40% 98%;
|
|
48
|
+
--senren-secondary: 210 40% 96%;
|
|
49
|
+
--senren-secondary-foreground: 222 47% 11%;
|
|
50
|
+
--senren-accent: 210 40% 94%;
|
|
51
|
+
--senren-accent-foreground: 222 47% 11%;
|
|
52
|
+
--senren-destructive: 0 72% 51%;
|
|
53
|
+
--senren-destructive-foreground: 210 40% 98%;
|
|
54
|
+
--senren-success: 142 71% 36%;
|
|
55
|
+
--senren-success-foreground: 210 40% 98%;
|
|
56
|
+
--senren-warning: 38 92% 50%;
|
|
57
|
+
--senren-warning-foreground: 222 47% 11%;
|
|
58
|
+
--senren-radius: 0.375rem;
|
|
59
|
+
--senren-palette-sky: 217 91% 60%;
|
|
60
|
+
--senren-palette-sakura: 330 81% 60%;
|
|
61
|
+
--senren-palette-pond: 199 89% 48%;
|
|
62
|
+
--senren-palette-leaf: 142 71% 45%;
|
|
63
|
+
--senren-palette-iris: 258 90% 66%;
|
|
64
|
+
--senren-palette-paper: 210 40% 98%;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.dark[data-senren-theme="slate"] {
|
|
68
|
+
--senren-background: 222 47% 7%;
|
|
69
|
+
--senren-foreground: 210 40% 98%;
|
|
70
|
+
--senren-muted: 217 33% 17%;
|
|
71
|
+
--senren-muted-foreground: 215 20% 65%;
|
|
72
|
+
--senren-card: 222 47% 9%;
|
|
73
|
+
--senren-card-foreground: 210 40% 98%;
|
|
74
|
+
--senren-popover: 222 47% 9%;
|
|
75
|
+
--senren-popover-foreground: 210 40% 98%;
|
|
76
|
+
--senren-border: 217 33% 20%;
|
|
77
|
+
--senren-input: 217 33% 20%;
|
|
78
|
+
--senren-ring: 213 27% 84%;
|
|
79
|
+
--senren-primary: 210 40% 98%;
|
|
80
|
+
--senren-primary-foreground: 222 47% 11%;
|
|
81
|
+
--senren-secondary: 217 33% 17%;
|
|
82
|
+
--senren-secondary-foreground: 210 40% 98%;
|
|
83
|
+
--senren-accent: 217 33% 20%;
|
|
84
|
+
--senren-accent-foreground: 210 40% 98%;
|
|
85
|
+
--senren-destructive: 0 63% 46%;
|
|
86
|
+
--senren-destructive-foreground: 210 40% 98%;
|
|
87
|
+
--senren-success: 142 60% 40%;
|
|
88
|
+
--senren-success-foreground: 210 40% 98%;
|
|
89
|
+
--senren-warning: 38 85% 52%;
|
|
90
|
+
--senren-warning-foreground: 222 47% 11%;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* ── indigo ───────────────────────────────────────────────────────────────
|
|
94
|
+
The default of a great many B2B products: white surfaces, indigo action
|
|
95
|
+
colour, cool grey text. */
|
|
96
|
+
[data-senren-theme="indigo"] {
|
|
97
|
+
--senren-background: 0 0% 100%;
|
|
98
|
+
--senren-foreground: 224 71% 4%;
|
|
99
|
+
--senren-muted: 220 14% 96%;
|
|
100
|
+
--senren-muted-foreground: 220 9% 46%;
|
|
101
|
+
--senren-card: 0 0% 100%;
|
|
102
|
+
--senren-card-foreground: 224 71% 4%;
|
|
103
|
+
--senren-popover: 0 0% 100%;
|
|
104
|
+
--senren-popover-foreground: 224 71% 4%;
|
|
105
|
+
--senren-border: 220 13% 91%;
|
|
106
|
+
--senren-input: 220 13% 91%;
|
|
107
|
+
--senren-ring: 239 84% 67%;
|
|
108
|
+
--senren-primary: 243 75% 59%;
|
|
109
|
+
--senren-primary-foreground: 0 0% 100%;
|
|
110
|
+
--senren-secondary: 226 100% 97%;
|
|
111
|
+
--senren-secondary-foreground: 243 75% 40%;
|
|
112
|
+
--senren-accent: 226 100% 94%;
|
|
113
|
+
--senren-accent-foreground: 243 75% 40%;
|
|
114
|
+
--senren-destructive: 0 84% 60%;
|
|
115
|
+
--senren-destructive-foreground: 0 0% 100%;
|
|
116
|
+
--senren-success: 160 84% 32%;
|
|
117
|
+
--senren-success-foreground: 0 0% 100%;
|
|
118
|
+
--senren-warning: 32 95% 52%;
|
|
119
|
+
--senren-warning-foreground: 224 71% 4%;
|
|
120
|
+
--senren-radius: 0.5rem;
|
|
121
|
+
--senren-palette-sky: 217 91% 60%;
|
|
122
|
+
--senren-palette-sakura: 330 81% 70%;
|
|
123
|
+
--senren-palette-pond: 199 89% 60%;
|
|
124
|
+
--senren-palette-leaf: 160 84% 39%;
|
|
125
|
+
--senren-palette-iris: 243 75% 59%;
|
|
126
|
+
--senren-palette-paper: 220 14% 96%;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.dark[data-senren-theme="indigo"] {
|
|
130
|
+
--senren-background: 229 42% 8%;
|
|
131
|
+
--senren-foreground: 220 14% 96%;
|
|
132
|
+
--senren-muted: 229 30% 16%;
|
|
133
|
+
--senren-muted-foreground: 218 11% 65%;
|
|
134
|
+
--senren-card: 229 40% 11%;
|
|
135
|
+
--senren-card-foreground: 220 14% 96%;
|
|
136
|
+
--senren-popover: 229 40% 11%;
|
|
137
|
+
--senren-popover-foreground: 220 14% 96%;
|
|
138
|
+
--senren-border: 229 25% 22%;
|
|
139
|
+
--senren-input: 229 25% 22%;
|
|
140
|
+
--senren-ring: 239 84% 72%;
|
|
141
|
+
--senren-primary: 239 84% 70%;
|
|
142
|
+
--senren-primary-foreground: 229 42% 8%;
|
|
143
|
+
--senren-secondary: 229 30% 18%;
|
|
144
|
+
--senren-secondary-foreground: 226 100% 88%;
|
|
145
|
+
--senren-accent: 229 30% 22%;
|
|
146
|
+
--senren-accent-foreground: 226 100% 92%;
|
|
147
|
+
--senren-destructive: 0 72% 55%;
|
|
148
|
+
--senren-destructive-foreground: 0 0% 100%;
|
|
149
|
+
--senren-success: 160 70% 42%;
|
|
150
|
+
--senren-success-foreground: 229 42% 8%;
|
|
151
|
+
--senren-warning: 32 90% 58%;
|
|
152
|
+
--senren-warning-foreground: 229 42% 8%;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* ── emerald ──────────────────────────────────────────────────────────────
|
|
156
|
+
Green action colour on soft neutrals, with a generous radius. Common in
|
|
157
|
+
finance, health, and sustainability products. */
|
|
158
|
+
[data-senren-theme="emerald"] {
|
|
159
|
+
--senren-background: 150 20% 99%;
|
|
160
|
+
--senren-foreground: 160 40% 10%;
|
|
161
|
+
--senren-muted: 150 24% 95%;
|
|
162
|
+
--senren-muted-foreground: 160 12% 42%;
|
|
163
|
+
--senren-card: 0 0% 100%;
|
|
164
|
+
--senren-card-foreground: 160 40% 10%;
|
|
165
|
+
--senren-popover: 0 0% 100%;
|
|
166
|
+
--senren-popover-foreground: 160 40% 10%;
|
|
167
|
+
--senren-border: 152 22% 88%;
|
|
168
|
+
--senren-input: 152 22% 88%;
|
|
169
|
+
--senren-ring: 160 84% 39%;
|
|
170
|
+
--senren-primary: 160 84% 30%;
|
|
171
|
+
--senren-primary-foreground: 150 30% 98%;
|
|
172
|
+
--senren-secondary: 152 45% 92%;
|
|
173
|
+
--senren-secondary-foreground: 160 60% 20%;
|
|
174
|
+
--senren-accent: 168 60% 90%;
|
|
175
|
+
--senren-accent-foreground: 168 70% 18%;
|
|
176
|
+
--senren-destructive: 0 74% 50%;
|
|
177
|
+
--senren-destructive-foreground: 150 30% 98%;
|
|
178
|
+
--senren-success: 160 84% 32%;
|
|
179
|
+
--senren-success-foreground: 150 30% 98%;
|
|
180
|
+
--senren-warning: 40 95% 50%;
|
|
181
|
+
--senren-warning-foreground: 160 40% 10%;
|
|
182
|
+
--senren-radius: 0.75rem;
|
|
183
|
+
--senren-palette-sky: 190 80% 45%;
|
|
184
|
+
--senren-palette-sakura: 340 70% 82%;
|
|
185
|
+
--senren-palette-pond: 172 66% 78%;
|
|
186
|
+
--senren-palette-leaf: 145 63% 49%;
|
|
187
|
+
--senren-palette-iris: 250 60% 78%;
|
|
188
|
+
--senren-palette-paper: 150 24% 95%;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.dark[data-senren-theme="emerald"] {
|
|
192
|
+
--senren-background: 165 40% 6%;
|
|
193
|
+
--senren-foreground: 150 24% 95%;
|
|
194
|
+
--senren-muted: 165 28% 14%;
|
|
195
|
+
--senren-muted-foreground: 152 14% 62%;
|
|
196
|
+
--senren-card: 165 38% 9%;
|
|
197
|
+
--senren-card-foreground: 150 24% 95%;
|
|
198
|
+
--senren-popover: 165 38% 9%;
|
|
199
|
+
--senren-popover-foreground: 150 24% 95%;
|
|
200
|
+
--senren-border: 165 22% 20%;
|
|
201
|
+
--senren-input: 165 22% 20%;
|
|
202
|
+
--senren-ring: 160 84% 45%;
|
|
203
|
+
--senren-primary: 158 74% 44%;
|
|
204
|
+
--senren-primary-foreground: 165 40% 6%;
|
|
205
|
+
--senren-secondary: 165 28% 16%;
|
|
206
|
+
--senren-secondary-foreground: 152 45% 88%;
|
|
207
|
+
--senren-accent: 168 40% 20%;
|
|
208
|
+
--senren-accent-foreground: 168 60% 88%;
|
|
209
|
+
--senren-destructive: 0 66% 52%;
|
|
210
|
+
--senren-destructive-foreground: 150 30% 98%;
|
|
211
|
+
--senren-success: 158 70% 42%;
|
|
212
|
+
--senren-success-foreground: 165 40% 6%;
|
|
213
|
+
--senren-warning: 40 88% 56%;
|
|
214
|
+
--senren-warning-foreground: 165 40% 6%;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* ── rose ─────────────────────────────────────────────────────────────────
|
|
218
|
+
Warm neutrals and a soft pink action colour, with the roundest corners of
|
|
219
|
+
the set. The consumer-facing end of SaaS. */
|
|
220
|
+
[data-senren-theme="rose"] {
|
|
221
|
+
--senren-background: 20 40% 99%;
|
|
222
|
+
--senren-foreground: 340 30% 12%;
|
|
223
|
+
--senren-muted: 20 30% 96%;
|
|
224
|
+
--senren-muted-foreground: 340 8% 46%;
|
|
225
|
+
--senren-card: 0 0% 100%;
|
|
226
|
+
--senren-card-foreground: 340 30% 12%;
|
|
227
|
+
--senren-popover: 0 0% 100%;
|
|
228
|
+
--senren-popover-foreground: 340 30% 12%;
|
|
229
|
+
--senren-border: 20 25% 90%;
|
|
230
|
+
--senren-input: 20 25% 90%;
|
|
231
|
+
--senren-ring: 347 77% 60%;
|
|
232
|
+
--senren-primary: 347 77% 50%;
|
|
233
|
+
--senren-primary-foreground: 20 40% 99%;
|
|
234
|
+
--senren-secondary: 350 80% 95%;
|
|
235
|
+
--senren-secondary-foreground: 347 70% 32%;
|
|
236
|
+
--senren-accent: 350 80% 92%;
|
|
237
|
+
--senren-accent-foreground: 347 70% 30%;
|
|
238
|
+
--senren-destructive: 0 78% 52%;
|
|
239
|
+
--senren-destructive-foreground: 20 40% 99%;
|
|
240
|
+
--senren-success: 150 62% 36%;
|
|
241
|
+
--senren-success-foreground: 20 40% 99%;
|
|
242
|
+
--senren-warning: 35 95% 55%;
|
|
243
|
+
--senren-warning-foreground: 340 30% 12%;
|
|
244
|
+
--senren-radius: 1rem;
|
|
245
|
+
--senren-palette-sky: 205 85% 58%;
|
|
246
|
+
--senren-palette-sakura: 350 85% 84%;
|
|
247
|
+
--senren-palette-pond: 190 70% 82%;
|
|
248
|
+
--senren-palette-leaf: 140 55% 55%;
|
|
249
|
+
--senren-palette-iris: 280 70% 78%;
|
|
250
|
+
--senren-palette-paper: 20 30% 96%;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.dark[data-senren-theme="rose"] {
|
|
254
|
+
--senren-background: 340 22% 8%;
|
|
255
|
+
--senren-foreground: 20 30% 96%;
|
|
256
|
+
--senren-muted: 340 16% 16%;
|
|
257
|
+
--senren-muted-foreground: 340 8% 64%;
|
|
258
|
+
--senren-card: 340 20% 11%;
|
|
259
|
+
--senren-card-foreground: 20 30% 96%;
|
|
260
|
+
--senren-popover: 340 20% 11%;
|
|
261
|
+
--senren-popover-foreground: 20 30% 96%;
|
|
262
|
+
--senren-border: 340 14% 22%;
|
|
263
|
+
--senren-input: 340 14% 22%;
|
|
264
|
+
--senren-ring: 347 77% 66%;
|
|
265
|
+
--senren-primary: 347 82% 62%;
|
|
266
|
+
--senren-primary-foreground: 340 22% 8%;
|
|
267
|
+
--senren-secondary: 340 16% 18%;
|
|
268
|
+
--senren-secondary-foreground: 350 80% 90%;
|
|
269
|
+
--senren-accent: 340 20% 24%;
|
|
270
|
+
--senren-accent-foreground: 350 80% 90%;
|
|
271
|
+
--senren-destructive: 0 70% 55%;
|
|
272
|
+
--senren-destructive-foreground: 20 40% 99%;
|
|
273
|
+
--senren-success: 150 55% 44%;
|
|
274
|
+
--senren-success-foreground: 340 22% 8%;
|
|
275
|
+
--senren-warning: 35 90% 60%;
|
|
276
|
+
--senren-warning-foreground: 340 22% 8%;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* ── amber ────────────────────────────────────────────────────────────────
|
|
280
|
+
Sand-toned neutrals, amber action colour, near-square corners. The
|
|
281
|
+
document-and-workspace look. */
|
|
282
|
+
[data-senren-theme="amber"] {
|
|
283
|
+
--senren-background: 40 30% 98%;
|
|
284
|
+
--senren-foreground: 30 25% 12%;
|
|
285
|
+
--senren-muted: 40 24% 94%;
|
|
286
|
+
--senren-muted-foreground: 32 10% 44%;
|
|
287
|
+
--senren-card: 40 40% 99%;
|
|
288
|
+
--senren-card-foreground: 30 25% 12%;
|
|
289
|
+
--senren-popover: 40 40% 99%;
|
|
290
|
+
--senren-popover-foreground: 30 25% 12%;
|
|
291
|
+
--senren-border: 36 20% 87%;
|
|
292
|
+
--senren-input: 36 20% 87%;
|
|
293
|
+
--senren-ring: 32 95% 44%;
|
|
294
|
+
--senren-primary: 30 30% 16%;
|
|
295
|
+
--senren-primary-foreground: 40 30% 98%;
|
|
296
|
+
--senren-secondary: 40 60% 90%;
|
|
297
|
+
--senren-secondary-foreground: 30 40% 20%;
|
|
298
|
+
--senren-accent: 38 80% 88%;
|
|
299
|
+
--senren-accent-foreground: 30 45% 20%;
|
|
300
|
+
--senren-destructive: 4 74% 48%;
|
|
301
|
+
--senren-destructive-foreground: 40 30% 98%;
|
|
302
|
+
--senren-success: 140 55% 34%;
|
|
303
|
+
--senren-success-foreground: 40 30% 98%;
|
|
304
|
+
--senren-warning: 32 95% 46%;
|
|
305
|
+
--senren-warning-foreground: 40 30% 98%;
|
|
306
|
+
--senren-radius: 0.25rem;
|
|
307
|
+
--senren-palette-sky: 200 75% 50%;
|
|
308
|
+
--senren-palette-sakura: 20 80% 82%;
|
|
309
|
+
--senren-palette-pond: 190 60% 80%;
|
|
310
|
+
--senren-palette-leaf: 100 45% 50%;
|
|
311
|
+
--senren-palette-iris: 265 55% 74%;
|
|
312
|
+
--senren-palette-paper: 40 24% 94%;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.dark[data-senren-theme="amber"] {
|
|
316
|
+
--senren-background: 30 18% 8%;
|
|
317
|
+
--senren-foreground: 40 24% 94%;
|
|
318
|
+
--senren-muted: 30 14% 15%;
|
|
319
|
+
--senren-muted-foreground: 36 10% 62%;
|
|
320
|
+
--senren-card: 30 16% 11%;
|
|
321
|
+
--senren-card-foreground: 40 24% 94%;
|
|
322
|
+
--senren-popover: 30 16% 11%;
|
|
323
|
+
--senren-popover-foreground: 40 24% 94%;
|
|
324
|
+
--senren-border: 30 12% 21%;
|
|
325
|
+
--senren-input: 30 12% 21%;
|
|
326
|
+
--senren-ring: 32 95% 55%;
|
|
327
|
+
--senren-primary: 36 90% 58%;
|
|
328
|
+
--senren-primary-foreground: 30 25% 10%;
|
|
329
|
+
--senren-secondary: 30 14% 17%;
|
|
330
|
+
--senren-secondary-foreground: 40 60% 86%;
|
|
331
|
+
--senren-accent: 32 30% 22%;
|
|
332
|
+
--senren-accent-foreground: 38 80% 86%;
|
|
333
|
+
--senren-destructive: 4 68% 52%;
|
|
334
|
+
--senren-destructive-foreground: 40 30% 98%;
|
|
335
|
+
--senren-success: 140 50% 42%;
|
|
336
|
+
--senren-success-foreground: 30 18% 8%;
|
|
337
|
+
--senren-warning: 32 90% 56%;
|
|
338
|
+
--senren-warning-foreground: 30 18% 8%;
|
|
339
|
+
}
|
|
@@ -106,6 +106,9 @@ module Senren
|
|
|
106
106
|
publishes every file under an asset path, so component `.rb` and
|
|
107
107
|
`.html.erb` source is precompiled into `public/assets` and served
|
|
108
108
|
over HTTP. Put sidecar assets in `app/components/assets` instead.
|
|
109
|
+
- A button inside a form needs no `type:`. ButtonComponent omits the
|
|
110
|
+
attribute so the browser submits, as plain HTML does. Pass
|
|
111
|
+
`type: :button` only for a trigger that must not submit.
|
|
109
112
|
|
|
110
113
|
## Important Files
|
|
111
114
|
|
data/lib/senren/rails/version.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<%= tag.div(**root_attrs("divide-y divide-[hsl(var(--senren-border))] rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-background))]", data: { controller: "senren--accordion", "senren--accordion-multiple-value": multiple? })) do %>
|
|
2
2
|
<% items.each_with_index do |item, index| %>
|
|
3
3
|
<% expanded = open_item?(item, index) %>
|
|
4
4
|
<section>
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
<span aria-hidden="true" class="text-[hsl(var(--senren-muted-foreground))]">+</span>
|
|
9
9
|
</button>
|
|
10
10
|
</h3>
|
|
11
|
-
<div id="<%= item[:id] %>-panel" data-senren--accordion-target="panel" data-panel-id="<%= item[:id] %>"
|
|
11
|
+
<div id="<%= item[:id] %>-panel" data-senren--accordion-target="panel" data-panel-id="<%= item[:id] %>" <% unless expanded %>hidden<% end %> class="px-4 pb-4 text-sm leading-6 text-[hsl(var(--senren-muted-foreground))]">
|
|
12
12
|
<%= item[:content].presence || content %>
|
|
13
13
|
</div>
|
|
14
14
|
</section>
|
|
15
15
|
<% end %>
|
|
16
|
-
|
|
16
|
+
<% end %>
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
<%# Root goes through root_attrs so a caller's class: and data: reach it.
|
|
2
|
+
Hand-writing these attributes dropped both -- which is why a Stimulus
|
|
3
|
+
value could not be set from the server. %>
|
|
4
|
+
<%= tag.div(**wrapper_attrs(id: dom_id, data: { controller: "senren--alert-dialog" })) do %>
|
|
2
5
|
<% if trigger? %>
|
|
3
6
|
<span data-action="click->senren--alert-dialog#open" data-senren--alert-dialog-target="trigger">
|
|
4
7
|
<%= trigger %>
|
|
@@ -15,7 +18,7 @@
|
|
|
15
18
|
<div data-senren--alert-dialog-target="panel" hidden
|
|
16
19
|
role="alertdialog" aria-modal="true" aria-labelledby="<%= dom_id %>-title" aria-describedby="<%= dom_id %>-desc"
|
|
17
20
|
tabindex="-1"
|
|
18
|
-
class="fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-popover))] text-[hsl(var(--senren-popover-foreground))] p-6 shadow-lg">
|
|
21
|
+
class="<%= panel_class("fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-popover))] text-[hsl(var(--senren-popover-foreground))] p-6 shadow-lg") %>">
|
|
19
22
|
<% if title? %>
|
|
20
23
|
<h2 id="<%= dom_id %>-title" class="text-lg font-semibold"><%= title %></h2>
|
|
21
24
|
<% end %>
|
|
@@ -31,4 +34,4 @@
|
|
|
31
34
|
<% end %>
|
|
32
35
|
</div>
|
|
33
36
|
</div>
|
|
34
|
-
|
|
37
|
+
<% end %>
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Senren
|
|
4
|
+
# Unlike Dialog and Sheet, this does not close when the overlay is clicked.
|
|
5
|
+
# An alert dialog exists to make someone choose, and dismissing it by
|
|
6
|
+
# clicking beside it is indistinguishable from choosing Cancel by accident.
|
|
7
|
+
# Escape still closes it, because a keyboard user needs a way out.
|
|
4
8
|
class AlertDialogComponent < BaseComponent
|
|
5
9
|
renders_one :trigger
|
|
6
10
|
renders_one :title
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<% else %>
|
|
5
5
|
<label class="block text-sm font-medium text-[hsl(var(--senren-foreground))]"><%= label %></label>
|
|
6
6
|
<div class="flex flex-col gap-2 sm:flex-row">
|
|
7
|
-
<input type="password" readonly aria-label="<%= label %>" value="<%= value %>" data-senren--api-key-field-target="input" class="h-10 min-w-0 flex-1 rounded-(--senren-radius) border border-[hsl(var(--senren-input))] bg-[hsl(var(--senren-muted)/0.35)] px-3 font-mono text-sm text-[hsl(var(--senren-foreground))] outline-none focus:ring-2 focus:ring-[hsl(var(--senren-ring))]">
|
|
7
|
+
<input type="password" readonly autocomplete="off" aria-label="<%= label %>" value="<%= value %>" data-senren--api-key-field-target="input" class="h-10 min-w-0 flex-1 rounded-(--senren-radius) border border-[hsl(var(--senren-input))] bg-[hsl(var(--senren-muted)/0.35)] px-3 font-mono text-sm text-[hsl(var(--senren-foreground))] outline-none focus:ring-2 focus:ring-[hsl(var(--senren-ring))]">
|
|
8
8
|
<button type="button" data-senren--api-key-field-target="revealButton" data-action="click->senren--api-key-field#toggle" class="inline-flex h-10 cursor-pointer items-center justify-center rounded-(--senren-radius) border border-[hsl(var(--senren-border))] px-3 text-sm font-medium hover:bg-[hsl(var(--senren-accent))]"><%= reveal_label %></button>
|
|
9
9
|
<button type="button" data-action="click->senren--api-key-field#copy" class="inline-flex h-10 cursor-pointer items-center justify-center rounded-(--senren-radius) bg-[hsl(var(--senren-primary))] px-3 text-sm font-medium text-[hsl(var(--senren-primary-foreground))] hover:opacity-90"><%= copy_label %></button>
|
|
10
10
|
</div>
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
<%# The <nav> is outside the branch on purpose. It used to be written twice,
|
|
2
|
+
once per branch, with the same root_attrs call duplicated -- so a change to
|
|
3
|
+
the root had to be made in two places and the empty case was one edit away
|
|
4
|
+
from silently losing its aria-label. %>
|
|
5
|
+
<%= tag.nav(**root_attrs("text-sm text-[hsl(var(--senren-muted-foreground))]", aria: { label: label })) do %>
|
|
6
|
+
<% if items.any? %>
|
|
3
7
|
<ol class="flex flex-wrap items-center gap-2">
|
|
4
8
|
<% items.each_with_index do |item, index| %>
|
|
5
9
|
<% current = index == items.length - 1 || item[:href].blank? %>
|
|
@@ -15,9 +19,7 @@
|
|
|
15
19
|
</li>
|
|
16
20
|
<% end %>
|
|
17
21
|
</ol>
|
|
18
|
-
|
|
19
|
-
<% else %>
|
|
20
|
-
<nav <%= tag.attributes(**root_attrs("text-sm text-[hsl(var(--senren-muted-foreground))]", aria: { label: label })) %>>
|
|
22
|
+
<% else %>
|
|
21
23
|
<%= content %>
|
|
22
|
-
|
|
24
|
+
<% end %>
|
|
23
25
|
<% end %>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<% base = "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-(--senren-radius) font-medium transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--senren-ring))] disabled:opacity-50 disabled:pointer-events-none disabled:cursor-not-allowed" %>
|
|
2
2
|
<% if as == :a %>
|
|
3
|
-
<%= tag.a content,
|
|
3
|
+
<%= tag.a content, href: safe_url(href), **root_attrs(base) %>
|
|
4
4
|
<% else %>
|
|
5
5
|
<%= tag.button content, **root_attrs(base, type: type) %>
|
|
6
6
|
<% end %>
|
|
@@ -17,7 +17,33 @@ module Senren
|
|
|
17
17
|
lg: 'h-12 px-6 text-base'
|
|
18
18
|
}.freeze
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
# FOR AI AGENTS AND HUMANS -- how `type` behaves, and why it changed.
|
|
21
|
+
#
|
|
22
|
+
# `type:` defaults to nil, so the attribute is omitted and the browser's own
|
|
23
|
+
# rule applies: a <button> inside a <form> submits it, one outside a form
|
|
24
|
+
# does nothing. Write what you would write in plain HTML; it behaves the
|
|
25
|
+
# same.
|
|
26
|
+
#
|
|
27
|
+
# <%= form_with url: sessions_path do %>
|
|
28
|
+
# <%= render(Senren::ButtonComponent.new(variant: :primary)) { "Sign in" } %>
|
|
29
|
+
# <% end %> # submits, as HTML says
|
|
30
|
+
#
|
|
31
|
+
# <%= render(Senren::ButtonComponent.new(type: :button)) { "Open" } %>
|
|
32
|
+
# # a trigger: say so
|
|
33
|
+
#
|
|
34
|
+
# This used to default to "button", which silently overrode the platform. A
|
|
35
|
+
# button rendered inside a form did nothing when clicked -- no submit, no
|
|
36
|
+
# navigation, nothing in the console -- and that is what users reported:
|
|
37
|
+
# "I click submit and it does not redirect". Every auth example this library
|
|
38
|
+
# ships had the bug, so anyone copying a snippet inherited it.
|
|
39
|
+
#
|
|
40
|
+
# The trade is deliberate. The old default failed silently; this one fails
|
|
41
|
+
# loudly, by submitting a form you did not mean to submit. A visible wrong
|
|
42
|
+
# is cheaper to find than an invisible nothing.
|
|
43
|
+
#
|
|
44
|
+
# Agents: writing a form? Do not pass `type:` at all. Writing a trigger for
|
|
45
|
+
# a dialog, sheet, or menu, which must not submit? Pass `type: :button`.
|
|
46
|
+
def initialize(variant: :default, size: :md, type: nil, as: :button, href: nil, class_name: nil, **html)
|
|
21
47
|
super(variant: variant, size: size, class_name: class_name, **html)
|
|
22
48
|
@type = type
|
|
23
49
|
@as = href ? :a : as
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<%= tag.div(**root_attrs("w-72 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))] p-3 text-[hsl(var(--senren-card-foreground))]", data: { controller: "senren--calendar" })) do %>
|
|
2
2
|
<% if name.present? %>
|
|
3
3
|
<input type="hidden" name="<%= name %>" value="<%= selected&.iso8601 %>" data-senren--calendar-target="value">
|
|
4
4
|
<% end %>
|
|
@@ -18,4 +18,4 @@
|
|
|
18
18
|
</button>
|
|
19
19
|
<% end %>
|
|
20
20
|
</div>
|
|
21
|
-
|
|
21
|
+
<% end %>
|
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
<% if header? %>
|
|
3
3
|
<div class="flex flex-col space-y-1.5 p-6 border-b border-[hsl(var(--senren-border))]"><%= header %></div>
|
|
4
4
|
<% end %>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<div class="p-6"><%= content %></div>
|
|
9
|
-
<% end %>
|
|
5
|
+
<%# One <div>, not the same one in both branches: the padding is the card's, %>
|
|
6
|
+
<%# and duplicating the element meant a change to it had to be made twice. %>
|
|
7
|
+
<div class="p-6"><%= body? ? body : content %></div>
|
|
10
8
|
<% if footer? %>
|
|
11
|
-
<div class="flex items-center p-6
|
|
9
|
+
<div class="flex items-center gap-2 p-6 border-t border-[hsl(var(--senren-border))]"><%= footer %></div>
|
|
12
10
|
<% end %>
|
|
13
11
|
<% end %>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<% slides.each_with_index do |slide, index| %>
|
|
7
7
|
<article data-senren--carousel-target="slide" data-index="<%= index %>" class="<%= index.zero? ? '' : 'hidden' %>">
|
|
8
8
|
<% if slide[:image_url].present? %>
|
|
9
|
-
|
|
9
|
+
<%= image_tag slide[:image_url], alt: slide[:alt] || slide[:title], class: "aspect-video w-full object-cover" %>
|
|
10
10
|
<% else %>
|
|
11
11
|
<div class="relative flex aspect-video items-end overflow-hidden bg-[hsl(var(--senren-muted)/0.45)] p-6">
|
|
12
12
|
<svg aria-hidden="true" viewBox="0 0 960 540" preserveAspectRatio="none" class="absolute inset-0 h-full w-full">
|
|
@@ -12,9 +12,15 @@
|
|
|
12
12
|
<li class="flex items-center gap-3 px-4 py-3"
|
|
13
13
|
data-senren--cart-target="line"
|
|
14
14
|
data-line-id="<%= item[:id] %>"
|
|
15
|
+
<%# Emitted so the `removed` event can carry it. The component
|
|
16
|
+
accepted remove_url: and normalised it, then never wrote it
|
|
17
|
+
anywhere -- an advertised input that reached nothing, so the
|
|
18
|
+
consumer listening for the event had no way to learn where to
|
|
19
|
+
send the delete. %>
|
|
20
|
+
<% if item[:remove_url].present? %>data-remove-url="<%= safe_url(item[:remove_url]) %>"<% end %>
|
|
15
21
|
data-price-cents="<%= item[:price_cents] %>">
|
|
16
22
|
<% if (image = safe_media_url(item[:image_url])) %>
|
|
17
|
-
|
|
23
|
+
<%= image_tag image, alt: "", loading: "lazy", class: "h-12 w-12 shrink-0 rounded-(--senren-radius) object-cover" %>
|
|
18
24
|
<% else %>
|
|
19
25
|
<div class="h-12 w-12 shrink-0 rounded-(--senren-radius) bg-[hsl(var(--senren-muted)/0.5)]" aria-hidden="true"></div>
|
|
20
26
|
<% end %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
<input type="text" readonly aria-label="<%= label %>" value="<%= value %>" data-senren--clipboard-target="source" class="h-10 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-muted)/0.4)] px-3 font-mono text-sm text-[hsl(var(--senren-foreground))]">
|
|
1
|
+
<%= tag.div(**root_attrs("inline-flex items-center gap-2", data: { controller: "senren--clipboard", "senren--clipboard-copied-label-value": copied_label })) do %>
|
|
2
|
+
<input type="text" readonly autocomplete="off" aria-label="<%= label %>" value="<%= value %>" data-senren--clipboard-target="source" class="h-10 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-muted)/0.4)] px-3 font-mono text-sm text-[hsl(var(--senren-foreground))]">
|
|
3
3
|
<button type="button" data-senren--clipboard-target="button" data-action="click->senren--clipboard#copy" class="inline-flex h-10 cursor-pointer items-center rounded-(--senren-radius) bg-[hsl(var(--senren-primary))] px-3 text-sm font-medium text-[hsl(var(--senren-primary-foreground))] hover:opacity-90">
|
|
4
4
|
<%= label %>
|
|
5
5
|
</button>
|
|
6
6
|
<span class="sr-only" aria-live="polite" data-senren--clipboard-target="status"></span>
|
|
7
|
-
|
|
7
|
+
<% end %>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
<%= tag.div(**root_attrs("rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))]", data: { controller: "senren--collapsible" })) do %>
|
|
2
2
|
<button type="button" class="flex w-full cursor-pointer items-center justify-between gap-4 px-4 py-3 text-left text-sm font-medium text-[hsl(var(--senren-card-foreground))]" aria-expanded="<%= open? %>" data-senren--collapsible-target="trigger" data-action="click->senren--collapsible#toggle">
|
|
3
3
|
<span><%= trigger || title %></span>
|
|
4
4
|
<span aria-hidden="true" class="text-[hsl(var(--senren-muted-foreground))]">+</span>
|
|
5
5
|
</button>
|
|
6
|
-
<div data-senren--collapsible-target="panel"
|
|
6
|
+
<div data-senren--collapsible-target="panel" <% unless open? %>hidden<% end %> class="border-t border-[hsl(var(--senren-border))] px-4 py-3 text-sm text-[hsl(var(--senren-muted-foreground))]">
|
|
7
7
|
<%= body || content %>
|
|
8
8
|
</div>
|
|
9
|
-
|
|
9
|
+
<% end %>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<%= tag.div(**root_attrs("relative w-full", data: { controller: "senren--combobox" })) do %>
|
|
2
2
|
<input type="hidden" name="<%= name %>" value="<%= value %>" data-senren--combobox-target="value">
|
|
3
3
|
<button type="button" class="group flex h-10 w-full cursor-pointer items-center justify-between rounded-(--senren-radius) border bg-[hsl(var(--senren-background))] px-3 text-left text-sm text-[hsl(var(--senren-foreground))] transition-colors hover:bg-[hsl(var(--senren-muted)/0.35)] <%= self.class::VARIANTS[variant] %>" aria-haspopup="listbox" aria-expanded="false" data-state="closed" data-senren--combobox-target="button" data-action="click->senren--combobox#toggle">
|
|
4
4
|
<span data-senren--combobox-target="label"><%= selected_label || placeholder %></span>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</svg>
|
|
8
8
|
</button>
|
|
9
9
|
<div data-senren--combobox-target="panel" hidden class="absolute z-50 mt-2 w-full rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-popover))] p-2 shadow-md">
|
|
10
|
-
<input type="text" placeholder="<%= placeholder %>" data-senren--combobox-target="search" data-action="input->senren--combobox#filter keydown->senren--combobox#onKey" class="mb-2 h-9 w-full rounded-md border border-[hsl(var(--senren-input))] bg-[hsl(var(--senren-background))] px-2 text-sm outline-none focus:ring-2 focus:ring-[hsl(var(--senren-ring))]">
|
|
10
|
+
<input type="text" autocomplete="off" placeholder="<%= placeholder %>" data-senren--combobox-target="search" data-action="input->senren--combobox#filter keydown->senren--combobox#onKey" class="mb-2 h-9 w-full rounded-md border border-[hsl(var(--senren-input))] bg-[hsl(var(--senren-background))] px-2 text-sm outline-none focus:ring-2 focus:ring-[hsl(var(--senren-ring))]">
|
|
11
11
|
<div role="listbox" class="max-h-56 overflow-auto">
|
|
12
12
|
<% options.each do |option| %>
|
|
13
13
|
<button type="button" role="option" data-senren--combobox-target="option" data-value="<%= option[:value] %>" data-label="<%= option[:label] %>" data-action="click->senren--combobox#choose" class="block w-full cursor-pointer rounded-md px-2 py-2 text-left text-sm text-[hsl(var(--senren-popover-foreground))] hover:bg-[hsl(var(--senren-accent))]">
|
|
@@ -16,4 +16,4 @@
|
|
|
16
16
|
<% end %>
|
|
17
17
|
</div>
|
|
18
18
|
</div>
|
|
19
|
-
|
|
19
|
+
<% end %>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<% else %>
|
|
5
5
|
<label for="<%= dom_id %>-input" class="sr-only"><%= label %></label>
|
|
6
6
|
<div class="border-b border-[hsl(var(--senren-border))] p-2">
|
|
7
|
-
<input id="<%= dom_id %>-input" type="search" autocomplete="off" placeholder="<%= placeholder %>" aria-label="<%= label %>" aria-controls="<%= dom_id %>-list"
|
|
7
|
+
<input id="<%= dom_id %>-input" type="search" autocomplete="off" placeholder="<%= placeholder %>" aria-label="<%= label %>" aria-controls="<%= dom_id %>-list" data-senren--command-target="input" data-action="input->senren--command#filter keydown->senren--command#onKey" class="h-10 w-full rounded-(--senren-radius) bg-[hsl(var(--senren-background))] px-3 text-sm text-[hsl(var(--senren-foreground))] outline-none placeholder:text-[hsl(var(--senren-muted-foreground))] focus:ring-2 focus:ring-[hsl(var(--senren-ring))]">
|
|
8
8
|
</div>
|
|
9
9
|
<div id="<%= dom_id %>-list" role="listbox" aria-label="<%= label %>" data-senren--command-target="list" class="max-h-72 overflow-y-auto p-1.5">
|
|
10
10
|
<% items.each_with_index do |item, index| %>
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
<%# Root goes through root_attrs so a caller's class: and data: reach it.
|
|
2
|
+
Hand-writing these attributes dropped both -- which is why a Stimulus
|
|
3
|
+
value could not be set from the server. %>
|
|
4
|
+
<%= tag.div(**wrapper_attrs("relative", data: { controller: "senren--context-menu" })) do %>
|
|
2
5
|
<div data-senren--context-menu-target="trigger" data-action="contextmenu->senren--context-menu#open">
|
|
3
6
|
<%= trigger %>
|
|
4
7
|
</div>
|
|
5
8
|
<div data-senren--context-menu-target="menu" role="menu" hidden
|
|
6
|
-
class="absolute z-50 w-56 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-popover))] text-[hsl(var(--senren-popover-foreground))] p-1 shadow-md">
|
|
9
|
+
class="<%= panel_class("absolute z-50 w-56 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-popover))] text-[hsl(var(--senren-popover-foreground))] p-1 shadow-md") %>">
|
|
7
10
|
<% items.each do |it| %>
|
|
8
11
|
<%= it %>
|
|
9
12
|
<% end %>
|
|
10
13
|
</div>
|
|
11
|
-
|
|
14
|
+
<% end %>
|