maquina-components 0.5.1 → 0.6.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/README.md +76 -2
- data/app/assets/stylesheets/alert.css +169 -135
- data/app/assets/stylesheets/badge.css +143 -138
- data/app/assets/stylesheets/breadcrumbs.css +183 -166
- data/app/assets/stylesheets/calendar.css +219 -206
- data/app/assets/stylesheets/card.css +126 -120
- data/app/assets/stylesheets/combobox.css +208 -191
- data/app/assets/stylesheets/date_picker.css +144 -134
- data/app/assets/stylesheets/drawer.css +158 -125
- data/app/assets/stylesheets/dropdown_menu.css +203 -190
- data/app/assets/stylesheets/empty.css +125 -117
- data/app/assets/stylesheets/form.css +625 -525
- data/app/assets/stylesheets/header.css +63 -47
- data/app/assets/stylesheets/menu_button.css +121 -98
- data/app/assets/stylesheets/pagination.css +130 -125
- data/app/assets/stylesheets/separator.css +23 -11
- data/app/assets/stylesheets/sidebar.css +447 -401
- data/app/assets/stylesheets/stats.css +77 -67
- data/app/assets/stylesheets/table.css +184 -166
- data/app/assets/stylesheets/toast.css +287 -278
- data/app/assets/stylesheets/toggle_group.css +132 -139
- data/app/assets/tailwind/maquina_components_engine/engine.css +54 -15
- data/app/assets/tailwind/maquina_components_engine/tokens.css +101 -0
- data/app/helpers/maquina_components/combobox_helper.rb +32 -24
- data/app/helpers/maquina_components/components_helper.rb +30 -6
- data/app/helpers/maquina_components/dropdown_menu_helper.rb +12 -12
- data/app/helpers/maquina_components/icons_helper.rb +26 -2
- data/app/javascript/controllers/drawer_trigger_controller.js +24 -3
- data/app/views/components/_label.html.erb +14 -0
- data/app/views/components/_table.html.erb +2 -2
- data/app/views/components/calendar/_week.html.erb +4 -12
- data/app/views/components/drawer/_close.html.erb +1 -1
- data/app/views/components/drawer/_description.html.erb +6 -0
- data/app/views/components/drawer/_header.html.erb +1 -1
- data/app/views/components/drawer/_provider.html.erb +13 -4
- data/app/views/components/drawer/_section.html.erb +7 -0
- data/app/views/components/drawer/_separator.html.erb +11 -0
- data/app/views/components/drawer/_title.html.erb +6 -0
- data/app/views/components/drawer/_trigger.html.erb +24 -7
- data/app/views/components/sidebar/_group_action.html.erb +26 -0
- data/app/views/components/sidebar/_menu_action.html.erb +28 -0
- data/app/views/components/sidebar/_menu_badge.html.erb +10 -0
- data/app/views/components/sidebar/_menu_button.html.erb +3 -2
- data/app/views/components/sidebar/_menu_link.html.erb +3 -2
- data/app/views/components/sidebar/_separator.html.erb +12 -0
- data/lib/generators/maquina_components/install/install_generator.rb +36 -1
- data/lib/generators/maquina_components/install/templates/maquina_components_helper.rb.tt +5 -0
- data/lib/generators/maquina_components/install/templates/shape_state_tokens.css.tt +76 -0
- data/lib/generators/maquina_components/install/templates/theme.css.tt +10 -3
- data/lib/maquina-components.rb +29 -1
- data/lib/maquina_components/doctor.rb +280 -0
- data/lib/maquina_components/version.rb +1 -1
- data/lib/tasks/maquina_components_tasks.rake +16 -4
- metadata +14 -2
|
@@ -4,614 +4,714 @@
|
|
|
4
4
|
Usage: Add data-component="..." to Rails form helpers
|
|
5
5
|
=================================================================== */
|
|
6
6
|
|
|
7
|
-
/*
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
/* -------------------------------------------------------------------
|
|
8
|
+
Cascade contract (identical in every engine stylesheet)
|
|
9
|
+
|
|
10
|
+
Everything below lives in `@layer components` and every selector is
|
|
11
|
+
flattened to specificity 0,1,0 by pushing variant / size / state /
|
|
12
|
+
ancestor qualifiers into `:where()`. Two consequences, both deliberate:
|
|
13
|
+
|
|
14
|
+
* A caller's Tailwind utility (the `css_classes:` parameter) wins,
|
|
15
|
+
because `@layer utilities` is defined to beat `@layer components`.
|
|
16
|
+
A consumer's own unlayered override CSS wins too, whatever its
|
|
17
|
+
specificity.
|
|
18
|
+
* Nothing here can be resolved by specificity any more, so SOURCE
|
|
19
|
+
ORDER is the only tie-breaker. Rules must be written in this order:
|
|
20
|
+
|
|
21
|
+
1. base [data-component="x"]
|
|
22
|
+
2. sizes [data-component="x"]:where([data-size="…"])
|
|
23
|
+
3. variants [data-component="x"]:where([data-variant="…"])
|
|
24
|
+
4. states [data-component="x"]:where(:hover, :focus-visible,
|
|
25
|
+
:disabled, [aria-invalid], …)
|
|
26
|
+
5. dark — none; dark-mode differences are tokens, see
|
|
27
|
+
app/assets/tailwind/maquina_components_engine/tokens.css
|
|
28
|
+
|
|
29
|
+
States come last so a state always beats a variant, which is what
|
|
30
|
+
"state" means. Cross-file order is fixed in engine.css — do not
|
|
31
|
+
re-alphabetize the imports.
|
|
32
|
+
------------------------------------------------------------------- */
|
|
33
|
+
|
|
34
|
+
@layer components {
|
|
35
|
+
/* ===================================================================
|
|
36
|
+
FORM CONTAINER
|
|
37
|
+
Usage: <%= form_with model: @user, data: { component: "form" } do |f| %>
|
|
38
|
+
=================================================================== */
|
|
39
|
+
[data-component="form"] {
|
|
40
|
+
@apply grid gap-6;
|
|
41
|
+
/* Pack rows at the start so a stretched/height-constrained parent does not
|
|
42
|
+
distribute leftover height between implicit rows. */
|
|
43
|
+
align-content: start;
|
|
44
|
+
}
|
|
14
45
|
|
|
15
|
-
/* Form Group - wraps label + input + help/error text */
|
|
16
|
-
[data-form-part="group"] {
|
|
17
|
-
|
|
18
|
-
|
|
46
|
+
/* Form Group - wraps label + input + help/error text */
|
|
47
|
+
[data-form-part="group"] {
|
|
48
|
+
@apply grid gap-2;
|
|
49
|
+
align-content: start;
|
|
50
|
+
}
|
|
19
51
|
|
|
20
|
-
/* Inline form group (horizontal layout) */
|
|
21
|
-
[data-form-part="group"][data-layout="inline"] {
|
|
22
|
-
|
|
23
|
-
}
|
|
52
|
+
/* Inline form group (horizontal layout) */
|
|
53
|
+
[data-form-part="group"]:where([data-layout="inline"]) {
|
|
54
|
+
@apply flex items-center gap-3;
|
|
55
|
+
}
|
|
24
56
|
|
|
25
|
-
/* ===================================================================
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
[data-component="label"] {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
57
|
+
/* ===================================================================
|
|
58
|
+
LABEL
|
|
59
|
+
Usage: <%= f.label :email, data: { component: "label" } %>
|
|
60
|
+
=================================================================== */
|
|
61
|
+
[data-component="label"] {
|
|
62
|
+
@apply flex items-center gap-2;
|
|
63
|
+
@apply text-sm leading-none select-none;
|
|
64
|
+
font-weight: var(--label-weight, 500);
|
|
65
|
+
color: var(--foreground);
|
|
66
|
+
}
|
|
34
67
|
|
|
35
|
-
/*
|
|
36
|
-
[data-component="label"]:
|
|
37
|
-
:
|
|
38
|
-
|
|
39
|
-
|
|
68
|
+
/* Required indicator */
|
|
69
|
+
[data-component="label"]:where([data-required])::after {
|
|
70
|
+
content: "*";
|
|
71
|
+
@apply ml-0.5;
|
|
72
|
+
color: var(--destructive);
|
|
73
|
+
}
|
|
40
74
|
|
|
41
|
-
/*
|
|
42
|
-
[data-component="label"]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
75
|
+
/* Label disabled state (when used with peer) */
|
|
76
|
+
[data-component="label"]:where(:has(+ :disabled)),
|
|
77
|
+
:where(:disabled) + [data-component="label"] {
|
|
78
|
+
@apply cursor-not-allowed opacity-50;
|
|
79
|
+
}
|
|
47
80
|
|
|
48
|
-
/* ===================================================================
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
/* ===================================================================
|
|
82
|
+
INPUT
|
|
83
|
+
Usage: <%= f.text_field :email, data: { component: "input" } %>
|
|
84
|
+
Matches: h-9 w-full min-w-0 rounded-md border border-input bg-transparent
|
|
85
|
+
px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none
|
|
86
|
+
md:text-sm placeholder:text-muted-foreground
|
|
87
|
+
focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]
|
|
88
|
+
disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50
|
|
89
|
+
aria-invalid:ring-destructive/20 aria-invalid:border-destructive
|
|
90
|
+
Shape, focus ring and elevation route through the engine tokens; see
|
|
91
|
+
app/assets/tailwind/maquina_components_engine/tokens.css.
|
|
92
|
+
=================================================================== */
|
|
93
|
+
[data-component="input"] {
|
|
94
|
+
@apply h-9 w-full min-w-0;
|
|
95
|
+
@apply border px-3 py-1;
|
|
96
|
+
@apply text-base md:text-sm;
|
|
97
|
+
@apply outline-none;
|
|
98
|
+
@apply transition-[color,box-shadow,outline-color];
|
|
99
|
+
|
|
100
|
+
border-radius: var(--input-radius, var(--control-radius, 0.375rem));
|
|
101
|
+
background-color: var(--control-fill, var(--maquina-control-fill-default, transparent));
|
|
102
|
+
border-color: var(--input);
|
|
103
|
+
color: var(--foreground);
|
|
104
|
+
box-shadow: var(--elevation-control, var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05)));
|
|
105
|
+
}
|
|
70
106
|
|
|
71
|
-
/* Placeholder */
|
|
72
|
-
[data-component="input"]::placeholder {
|
|
73
|
-
|
|
74
|
-
}
|
|
107
|
+
/* Placeholder */
|
|
108
|
+
[data-component="input"]::placeholder {
|
|
109
|
+
color: var(--muted-foreground);
|
|
110
|
+
}
|
|
75
111
|
|
|
76
|
-
/* Selection */
|
|
77
|
-
[data-component="input"]::selection {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
112
|
+
/* Selection */
|
|
113
|
+
[data-component="input"]::selection {
|
|
114
|
+
background-color: var(--primary);
|
|
115
|
+
color: var(--primary-foreground);
|
|
116
|
+
}
|
|
81
117
|
|
|
82
|
-
/*
|
|
83
|
-
[data-component="input"]:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
box-shadow:
|
|
87
|
-
var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05)),
|
|
88
|
-
0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
|
|
89
|
-
}
|
|
118
|
+
/* ----- Sizes ----- */
|
|
119
|
+
[data-component="input"]:where([data-size="sm"]) {
|
|
120
|
+
@apply h-8 px-2.5 text-xs;
|
|
121
|
+
}
|
|
90
122
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
123
|
+
[data-component="input"]:where([data-size="lg"]) {
|
|
124
|
+
@apply h-11 px-4 text-base;
|
|
125
|
+
}
|
|
95
126
|
|
|
96
|
-
/*
|
|
97
|
-
[data-component="input"][
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
box-shadow:
|
|
101
|
-
var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05)),
|
|
102
|
-
0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
|
|
103
|
-
}
|
|
127
|
+
/* ----- Type variants ----- */
|
|
128
|
+
[data-component="input"]:where([type="file"]) {
|
|
129
|
+
@apply py-1.5;
|
|
130
|
+
}
|
|
104
131
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
132
|
+
[data-component="input"]:where([type="file"])::file-selector-button {
|
|
133
|
+
@apply inline-flex h-7 items-center border-0 bg-transparent;
|
|
134
|
+
@apply text-sm;
|
|
135
|
+
font-weight: var(--label-weight, 500);
|
|
136
|
+
color: var(--foreground);
|
|
137
|
+
}
|
|
109
138
|
|
|
110
|
-
|
|
111
|
-
@apply inline-flex h-7 items-center border-0 bg-transparent;
|
|
112
|
-
@apply text-sm font-medium;
|
|
113
|
-
color: var(--foreground);
|
|
114
|
-
}
|
|
139
|
+
/* ----- States ----- */
|
|
115
140
|
|
|
116
|
-
/*
|
|
117
|
-
[data-component="input"]
|
|
118
|
-
|
|
119
|
-
|
|
141
|
+
/* Focus state — keyboard only. The bare :focus used to ring on mouse click. */
|
|
142
|
+
[data-component="input"]:where(:focus-visible) {
|
|
143
|
+
border-color: var(--ring);
|
|
144
|
+
outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
|
|
145
|
+
var(--focus-ring-color, color-mix(in oklch, var(--ring) 50%, transparent));
|
|
146
|
+
outline-offset: var(--focus-ring-offset, 0px);
|
|
147
|
+
}
|
|
120
148
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
149
|
+
/* Disabled state */
|
|
150
|
+
[data-component="input"]:where(:disabled) {
|
|
151
|
+
@apply pointer-events-none cursor-not-allowed opacity-50;
|
|
152
|
+
}
|
|
124
153
|
|
|
125
|
-
/*
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
154
|
+
/* Invalid/error state — the destructive halo is themeable through
|
|
155
|
+
--focus-ring-color, which the :focus-visible rule above also reads. */
|
|
156
|
+
[data-component="input"]:where([aria-invalid="true"], :invalid:not(:placeholder-shown)) {
|
|
157
|
+
--focus-ring-color: color-mix(in oklch, var(--destructive) 20%, transparent);
|
|
158
|
+
border-color: var(--destructive);
|
|
159
|
+
outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
|
|
160
|
+
var(--focus-ring-color, color-mix(in oklch, var(--destructive) 20%, transparent));
|
|
161
|
+
outline-offset: var(--focus-ring-offset, 0px);
|
|
162
|
+
}
|
|
129
163
|
|
|
130
|
-
/* ===================================================================
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
164
|
+
/* ===================================================================
|
|
165
|
+
TEXTAREA
|
|
166
|
+
Usage: <%= f.text_area :bio, data: { component: "textarea" } %>
|
|
167
|
+
Matches: min-h-[60px] w-full rounded-md border border-input bg-transparent
|
|
168
|
+
px-3 py-2 text-base shadow-xs outline-none md:text-sm
|
|
169
|
+
placeholder:text-muted-foreground
|
|
170
|
+
focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]
|
|
171
|
+
disabled:cursor-not-allowed disabled:opacity-50
|
|
172
|
+
aria-invalid:ring-destructive/20 aria-invalid:border-destructive
|
|
173
|
+
Shape, focus ring and elevation route through the engine tokens; see
|
|
174
|
+
app/assets/tailwind/maquina_components_engine/tokens.css.
|
|
175
|
+
=================================================================== */
|
|
176
|
+
[data-component="textarea"] {
|
|
177
|
+
@apply w-full min-w-0 min-h-16;
|
|
178
|
+
@apply border px-3 py-2;
|
|
179
|
+
@apply text-base md:text-sm;
|
|
180
|
+
@apply outline-none resize-y;
|
|
181
|
+
@apply transition-[color,box-shadow,outline-color];
|
|
182
|
+
|
|
183
|
+
border-radius: var(--textarea-radius, var(--control-radius, 0.375rem));
|
|
184
|
+
background-color: var(--control-fill, var(--maquina-control-fill-default, transparent));
|
|
185
|
+
border-color: var(--input);
|
|
186
|
+
color: var(--foreground);
|
|
187
|
+
box-shadow: var(--elevation-control, var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05)));
|
|
188
|
+
}
|
|
152
189
|
|
|
153
|
-
[data-component="textarea"]::placeholder {
|
|
154
|
-
|
|
155
|
-
}
|
|
190
|
+
[data-component="textarea"]::placeholder {
|
|
191
|
+
color: var(--muted-foreground);
|
|
192
|
+
}
|
|
156
193
|
|
|
157
|
-
|
|
158
|
-
[data-component="textarea"]:focus-visible {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
194
|
+
/* ----- States ----- */
|
|
195
|
+
[data-component="textarea"]:where(:focus-visible) {
|
|
196
|
+
border-color: var(--ring);
|
|
197
|
+
outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
|
|
198
|
+
var(--focus-ring-color, color-mix(in oklch, var(--ring) 50%, transparent));
|
|
199
|
+
outline-offset: var(--focus-ring-offset, 0px);
|
|
200
|
+
}
|
|
164
201
|
|
|
165
|
-
[data-component="textarea"]:disabled {
|
|
166
|
-
|
|
167
|
-
}
|
|
202
|
+
[data-component="textarea"]:where(:disabled) {
|
|
203
|
+
@apply cursor-not-allowed opacity-50;
|
|
204
|
+
}
|
|
168
205
|
|
|
169
|
-
[data-component="textarea"][aria-invalid="true"],
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
206
|
+
[data-component="textarea"]:where([aria-invalid="true"], :invalid:not(:placeholder-shown)) {
|
|
207
|
+
--focus-ring-color: color-mix(in oklch, var(--destructive) 20%, transparent);
|
|
208
|
+
border-color: var(--destructive);
|
|
209
|
+
outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
|
|
210
|
+
var(--focus-ring-color, color-mix(in oklch, var(--destructive) 20%, transparent));
|
|
211
|
+
outline-offset: var(--focus-ring-offset, 0px);
|
|
212
|
+
}
|
|
176
213
|
|
|
177
|
-
/*
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
214
|
+
/* ===================================================================
|
|
215
|
+
SELECT (Native)
|
|
216
|
+
Usage: <%= f.select :country, options, {}, data: { component: "select" } %>
|
|
217
|
+
=================================================================== */
|
|
218
|
+
[data-component="select"] {
|
|
219
|
+
@apply h-9 w-full min-w-0;
|
|
220
|
+
@apply border px-3 py-1 pr-8;
|
|
221
|
+
@apply text-base md:text-sm;
|
|
222
|
+
@apply outline-none appearance-none cursor-pointer;
|
|
223
|
+
@apply transition-[color,box-shadow,outline-color];
|
|
224
|
+
|
|
225
|
+
border-radius: var(--select-radius, var(--control-radius, 0.375rem));
|
|
226
|
+
background-color: var(--control-fill, var(--maquina-select-fill-default, var(--background)));
|
|
227
|
+
border-color: var(--input);
|
|
228
|
+
color: var(--foreground);
|
|
229
|
+
box-shadow: var(--elevation-control, var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05)));
|
|
230
|
+
|
|
231
|
+
/* Chevron icon.
|
|
232
|
+
var() cannot be interpolated into url() and a data URI is a separate SVG
|
|
233
|
+
document, so currentColor never resolves inside it — the whole image is
|
|
234
|
+
exposed as an overridable property instead, with ready-made second and
|
|
235
|
+
third inks below. */
|
|
236
|
+
background-image: var(--select-chevron-image,
|
|
237
|
+
var(--maquina-select-chevron-default,
|
|
238
|
+
url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")));
|
|
239
|
+
background-position: right 0.5rem center;
|
|
240
|
+
background-repeat: no-repeat;
|
|
241
|
+
background-size: 1.25rem 1.25rem;
|
|
242
|
+
}
|
|
181
243
|
|
|
182
|
-
/*
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
@apply outline-none appearance-none cursor-pointer;
|
|
191
|
-
@apply transition-[color,box-shadow];
|
|
192
|
-
|
|
193
|
-
background-color: var(--background);
|
|
194
|
-
border-color: var(--input);
|
|
195
|
-
color: var(--foreground);
|
|
196
|
-
box-shadow: var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05));
|
|
197
|
-
|
|
198
|
-
/* Chevron icon */
|
|
199
|
-
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
|
200
|
-
background-position: right 0.5rem center;
|
|
201
|
-
background-repeat: no-repeat;
|
|
202
|
-
background-size: 1.25rem 1.25rem;
|
|
203
|
-
}
|
|
244
|
+
/* ----- Mark inks -----
|
|
245
|
+
Declared on the control, so they beat both the engine's inherited default
|
|
246
|
+
and a consumer's :root override — an explicit opt-in should win over a
|
|
247
|
+
global default. Works in dark mode too, now that the dark ink is an
|
|
248
|
+
inherited default rather than a rule on the control. */
|
|
249
|
+
[data-component="select"]:where([data-mark="dark"]) {
|
|
250
|
+
--select-chevron-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23000000' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
|
251
|
+
}
|
|
204
252
|
|
|
205
|
-
[data-component="select"]:
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
box-shadow:
|
|
209
|
-
var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05)),
|
|
210
|
-
0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
|
|
211
|
-
}
|
|
253
|
+
[data-component="select"]:where([data-mark="light"]) {
|
|
254
|
+
--select-chevron-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
|
255
|
+
}
|
|
212
256
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
257
|
+
/* ----- States ----- */
|
|
258
|
+
[data-component="select"]:where(:focus-visible) {
|
|
259
|
+
border-color: var(--ring);
|
|
260
|
+
outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
|
|
261
|
+
var(--focus-ring-color, color-mix(in oklch, var(--ring) 50%, transparent));
|
|
262
|
+
outline-offset: var(--focus-ring-offset, 0px);
|
|
263
|
+
}
|
|
216
264
|
|
|
217
|
-
[data-component="select"]
|
|
218
|
-
|
|
219
|
-
}
|
|
265
|
+
[data-component="select"]:where(:disabled) {
|
|
266
|
+
@apply pointer-events-none cursor-not-allowed opacity-50;
|
|
267
|
+
}
|
|
220
268
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
269
|
+
[data-component="select"]:where([aria-invalid="true"]) {
|
|
270
|
+
--focus-ring-color: color-mix(in oklch, var(--destructive) 20%, transparent);
|
|
271
|
+
border-color: var(--destructive);
|
|
272
|
+
}
|
|
225
273
|
|
|
226
|
-
/* ===================================================================
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
274
|
+
/* ===================================================================
|
|
275
|
+
CHECKBOX
|
|
276
|
+
Usage: <%= f.check_box :terms, data: { component: "checkbox" } %>
|
|
277
|
+
Matches: peer size-4 shrink-0 rounded-[4px] border border-input shadow-xs
|
|
278
|
+
transition-shadow outline-none
|
|
279
|
+
data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground
|
|
280
|
+
data-[state=checked]:border-primary
|
|
281
|
+
focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]
|
|
282
|
+
disabled:cursor-not-allowed disabled:opacity-50
|
|
283
|
+
aria-invalid:ring-destructive/20 aria-invalid:border-destructive
|
|
284
|
+
Shape, focus ring and elevation route through the engine tokens; see
|
|
285
|
+
app/assets/tailwind/maquina_components_engine/tokens.css.
|
|
286
|
+
=================================================================== */
|
|
287
|
+
[data-component="checkbox"] {
|
|
288
|
+
@apply size-4 shrink-0;
|
|
289
|
+
@apply border;
|
|
290
|
+
@apply cursor-pointer appearance-none;
|
|
291
|
+
@apply transition-shadow;
|
|
292
|
+
|
|
293
|
+
border-radius: var(--checkbox-radius, var(--mark-radius, 4px));
|
|
294
|
+
border-color: var(--input);
|
|
295
|
+
background-color: var(--control-fill, var(--maquina-control-fill-default, transparent));
|
|
296
|
+
box-shadow: var(--elevation-control, var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05)));
|
|
297
|
+
}
|
|
247
298
|
|
|
248
|
-
|
|
249
|
-
[data-component="checkbox"]:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05)),
|
|
254
|
-
0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
|
|
255
|
-
}
|
|
299
|
+
/* Dark ink — one attribute fixes a light --primary */
|
|
300
|
+
[data-component="checkbox"]:where([data-mark="dark"]) {
|
|
301
|
+
--checkbox-mark-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23000000' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
|
|
302
|
+
--checkbox-indeterminate-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23000000' xmlns='http://www.w3.org/2000/svg'%3e%3crect x='3' y='7' width='10' height='2' rx='1'/%3e%3c/svg%3e");
|
|
303
|
+
}
|
|
256
304
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
305
|
+
/* ----- States ----- */
|
|
306
|
+
[data-component="checkbox"]:where(:checked) {
|
|
307
|
+
background-color: var(--primary);
|
|
308
|
+
border-color: var(--primary);
|
|
309
|
+
|
|
310
|
+
/* Checkmark icon.
|
|
311
|
+
Marks are overridable images, not masks: mask-image would mask the whole
|
|
312
|
+
element (box, border and shadow included), appearance:none inputs cannot
|
|
313
|
+
carry ::before/::after reliably, and neither currentColor nor var()
|
|
314
|
+
resolves inside a data URI. A light --primary makes the white ink measure
|
|
315
|
+
1.15:1, so ship a second ink rather than baking one in.
|
|
316
|
+
|
|
317
|
+
The default is the fallback rather than a declaration on the control,
|
|
318
|
+
because an element's own custom property beats an inherited one — so
|
|
319
|
+
declaring it here would put it out of reach of :root. Setting it at :root
|
|
320
|
+
retints every control; the data-mark attributes below still win, since an
|
|
321
|
+
explicit opt-in should beat a global default. */
|
|
322
|
+
background-image: var(--checkbox-mark-image,
|
|
323
|
+
url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23ffffff' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"));
|
|
324
|
+
background-position: center;
|
|
325
|
+
background-repeat: no-repeat;
|
|
326
|
+
background-size: 100% 100%;
|
|
327
|
+
}
|
|
260
328
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
329
|
+
[data-component="checkbox"]:where(:indeterminate) {
|
|
330
|
+
background-color: var(--primary);
|
|
331
|
+
border-color: var(--primary);
|
|
332
|
+
background-image: var(--checkbox-indeterminate-image,
|
|
333
|
+
url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23ffffff' xmlns='http://www.w3.org/2000/svg'%3e%3crect x='3' y='7' width='10' height='2' rx='1'/%3e%3c/svg%3e"));
|
|
334
|
+
background-position: center;
|
|
335
|
+
background-repeat: no-repeat;
|
|
336
|
+
background-size: 100% 100%;
|
|
337
|
+
}
|
|
267
338
|
|
|
268
|
-
[data-component="checkbox"]:
|
|
269
|
-
|
|
270
|
-
|
|
339
|
+
[data-component="checkbox"]:where(:focus-visible) {
|
|
340
|
+
border-color: var(--ring);
|
|
341
|
+
outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
|
|
342
|
+
var(--focus-ring-color, color-mix(in oklch, var(--ring) 50%, transparent));
|
|
343
|
+
outline-offset: var(--focus-ring-offset, 0px);
|
|
344
|
+
}
|
|
271
345
|
|
|
272
|
-
[data-component="checkbox"]
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05)),
|
|
276
|
-
0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
|
|
277
|
-
}
|
|
346
|
+
[data-component="checkbox"]:where(:disabled) {
|
|
347
|
+
@apply cursor-not-allowed opacity-50;
|
|
348
|
+
}
|
|
278
349
|
|
|
279
|
-
[data-component="checkbox"]:
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
350
|
+
[data-component="checkbox"]:where([aria-invalid="true"]) {
|
|
351
|
+
--focus-ring-color: color-mix(in oklch, var(--destructive) 20%, transparent);
|
|
352
|
+
border-color: var(--destructive);
|
|
353
|
+
outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
|
|
354
|
+
var(--focus-ring-color, color-mix(in oklch, var(--destructive) 20%, transparent));
|
|
355
|
+
outline-offset: var(--focus-ring-offset, 0px);
|
|
356
|
+
}
|
|
287
357
|
|
|
288
|
-
/*
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
358
|
+
/* ===================================================================
|
|
359
|
+
RADIO
|
|
360
|
+
Usage: <%= f.radio_button :plan, "basic", data: { component: "radio" } %>
|
|
361
|
+
=================================================================== */
|
|
362
|
+
[data-component="radio"] {
|
|
363
|
+
@apply size-4 shrink-0;
|
|
364
|
+
@apply border;
|
|
365
|
+
@apply cursor-pointer appearance-none;
|
|
366
|
+
@apply transition-shadow;
|
|
367
|
+
|
|
368
|
+
border-radius: var(--radio-radius, var(--pill-radius, calc(infinity * 1px)));
|
|
369
|
+
border-color: var(--input);
|
|
370
|
+
background-color: var(--control-fill, var(--maquina-control-fill-default, transparent));
|
|
371
|
+
box-shadow: var(--elevation-control, var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05)));
|
|
372
|
+
|
|
373
|
+
/* Inner dot — overridable image, see the checkbox note above */
|
|
374
|
+
}
|
|
292
375
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
=================================================================== */
|
|
297
|
-
[data-component="radio"] {
|
|
298
|
-
@apply size-4 shrink-0;
|
|
299
|
-
@apply rounded-full border;
|
|
300
|
-
@apply cursor-pointer appearance-none;
|
|
301
|
-
@apply transition-shadow;
|
|
302
|
-
|
|
303
|
-
border-color: var(--input);
|
|
304
|
-
background-color: transparent;
|
|
305
|
-
box-shadow: var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05));
|
|
306
|
-
}
|
|
376
|
+
[data-component="radio"]:where([data-mark="dark"]) {
|
|
377
|
+
--radio-mark-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23000000' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
|
|
378
|
+
}
|
|
307
379
|
|
|
308
|
-
|
|
309
|
-
[data-component="radio"]:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
380
|
+
/* ----- States ----- */
|
|
381
|
+
[data-component="radio"]:where(:checked) {
|
|
382
|
+
border-color: var(--primary);
|
|
383
|
+
background-color: var(--primary);
|
|
384
|
+
|
|
385
|
+
/* Inner dot */
|
|
386
|
+
background-image: var(--radio-mark-image,
|
|
387
|
+
url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23ffffff' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"));
|
|
388
|
+
background-position: center;
|
|
389
|
+
background-repeat: no-repeat;
|
|
390
|
+
background-size: 100% 100%;
|
|
391
|
+
}
|
|
316
392
|
|
|
317
|
-
[data-component="radio"]:
|
|
318
|
-
|
|
319
|
-
|
|
393
|
+
[data-component="radio"]:where(:focus-visible) {
|
|
394
|
+
border-color: var(--ring);
|
|
395
|
+
outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
|
|
396
|
+
var(--focus-ring-color, color-mix(in oklch, var(--ring) 50%, transparent));
|
|
397
|
+
outline-offset: var(--focus-ring-offset, 0px);
|
|
398
|
+
}
|
|
320
399
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
background-repeat: no-repeat;
|
|
325
|
-
background-size: 100% 100%;
|
|
326
|
-
}
|
|
400
|
+
[data-component="radio"]:where(:disabled) {
|
|
401
|
+
@apply cursor-not-allowed opacity-50;
|
|
402
|
+
}
|
|
327
403
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
404
|
+
/* ===================================================================
|
|
405
|
+
SWITCH (Toggle)
|
|
406
|
+
Usage: <%= f.check_box :notifications, data: { component: "switch" } %>
|
|
407
|
+
=================================================================== */
|
|
408
|
+
[data-component="switch"] {
|
|
409
|
+
@apply h-5 w-9 shrink-0;
|
|
410
|
+
@apply border-2 border-transparent;
|
|
411
|
+
@apply cursor-pointer appearance-none;
|
|
412
|
+
@apply transition-all duration-200;
|
|
413
|
+
|
|
414
|
+
border-radius: var(--switch-radius, var(--pill-radius, calc(infinity * 1px)));
|
|
415
|
+
background-color: var(--input);
|
|
416
|
+
|
|
417
|
+
/* Thumb — overridable image, see the checkbox note above.
|
|
418
|
+
The thumb slides via background-position, so these must stay longhands:
|
|
419
|
+
the background shorthand would reset the position on every state change. */
|
|
420
|
+
background-image: var(--switch-thumb-image,
|
|
421
|
+
url("data:image/svg+xml,%3csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='10' cy='10' r='8' fill='%23ffffff'/%3e%3c/svg%3e"));
|
|
422
|
+
background-position: left 2px center;
|
|
423
|
+
background-repeat: no-repeat;
|
|
424
|
+
background-size: 16px 16px;
|
|
425
|
+
}
|
|
331
426
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
427
|
+
[data-component="switch"]:where([data-mark="dark"]) {
|
|
428
|
+
--switch-thumb-image: url("data:image/svg+xml,%3csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='10' cy='10' r='8' fill='%23000000'/%3e%3c/svg%3e");
|
|
429
|
+
}
|
|
336
430
|
|
|
337
|
-
/*
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
@apply h-5 w-9 shrink-0;
|
|
343
|
-
@apply rounded-full border-2 border-transparent;
|
|
344
|
-
@apply cursor-pointer appearance-none;
|
|
345
|
-
@apply transition-all duration-200;
|
|
346
|
-
|
|
347
|
-
background-color: var(--input);
|
|
348
|
-
|
|
349
|
-
/* Thumb */
|
|
350
|
-
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='10' cy='10' r='8' fill='white'/%3e%3c/svg%3e");
|
|
351
|
-
background-position: left 2px center;
|
|
352
|
-
background-repeat: no-repeat;
|
|
353
|
-
background-size: 16px 16px;
|
|
354
|
-
}
|
|
431
|
+
/* ----- States ----- */
|
|
432
|
+
[data-component="switch"]:where(:checked) {
|
|
433
|
+
background-color: var(--primary);
|
|
434
|
+
background-position: right 2px center;
|
|
435
|
+
}
|
|
355
436
|
|
|
356
|
-
[data-component="switch"]:focus
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
437
|
+
[data-component="switch"]:where(:focus-visible) {
|
|
438
|
+
outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
|
|
439
|
+
var(--focus-ring-color, color-mix(in oklch, var(--ring) 50%, transparent));
|
|
440
|
+
outline-offset: var(--focus-ring-offset, 0px);
|
|
441
|
+
}
|
|
361
442
|
|
|
362
|
-
[data-component="switch"]:
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
443
|
+
[data-component="switch"]:where(:disabled) {
|
|
444
|
+
@apply cursor-not-allowed opacity-50;
|
|
445
|
+
}
|
|
366
446
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
447
|
+
/* ===================================================================
|
|
448
|
+
BUTTON
|
|
449
|
+
Usage:
|
|
450
|
+
<%= f.submit "Save", data: { component: "button", variant: "primary" } %>
|
|
451
|
+
<%= button_to "Delete", path, data: { component: "button", variant: "destructive" } %>
|
|
452
|
+
<%= link_to "Cancel", path, data: { component: "button", variant: "outline" } %>
|
|
453
|
+
Matches: inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md
|
|
454
|
+
text-sm font-medium transition-colors outline-none
|
|
455
|
+
focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
|
|
456
|
+
disabled:pointer-events-none disabled:opacity-50
|
|
457
|
+
[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0
|
|
458
|
+
Shape, focus ring and elevation route through the engine tokens; see
|
|
459
|
+
app/assets/tailwind/maquina_components_engine/tokens.css.
|
|
460
|
+
=================================================================== */
|
|
461
|
+
|
|
462
|
+
/* ----- Base: every button, no exceptions -----
|
|
463
|
+
The un-varianted default treatment used to be folded into this selector,
|
|
464
|
+
which meant a theme's own `[data-component="button"]` rule at 0,1,0 beat
|
|
465
|
+
un-varianted buttons but lost to every explicitly-varianted one. The
|
|
466
|
+
default treatment now lives with the other variants, keyed off
|
|
467
|
+
:not([data-variant]). */
|
|
468
|
+
[data-component="button"] {
|
|
469
|
+
@apply inline-flex items-center justify-center gap-2;
|
|
470
|
+
@apply whitespace-nowrap;
|
|
471
|
+
border-radius: var(--button-radius, var(--control-radius, 0.375rem));
|
|
472
|
+
@apply text-sm;
|
|
473
|
+
font-weight: var(--label-weight, 500);
|
|
474
|
+
@apply h-9 px-4 py-2;
|
|
475
|
+
@apply cursor-default;
|
|
476
|
+
@apply outline-none;
|
|
477
|
+
@apply transition-colors;
|
|
478
|
+
@apply select-none;
|
|
479
|
+
|
|
480
|
+
/* SVG icons inside buttons */
|
|
481
|
+
& svg {
|
|
482
|
+
@apply pointer-events-none size-4 shrink-0;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
370
485
|
|
|
371
|
-
/*
|
|
372
|
-
BUTTON
|
|
373
|
-
Usage:
|
|
374
|
-
<%= f.submit "Save", data: { component: "button", variant: "primary" } %>
|
|
375
|
-
<%= button_to "Delete", path, data: { component: "button", variant: "destructive" } %>
|
|
376
|
-
<%= link_to "Cancel", path, data: { component: "button", variant: "outline" } %>
|
|
377
|
-
Matches: inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md
|
|
378
|
-
text-sm font-medium transition-colors outline-none
|
|
379
|
-
focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
|
|
380
|
-
disabled:pointer-events-none disabled:opacity-50
|
|
381
|
-
[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0
|
|
382
|
-
=================================================================== */
|
|
486
|
+
/* ----- Sizes ----- */
|
|
383
487
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
@apply inline-flex items-center justify-center gap-2;
|
|
387
|
-
@apply whitespace-nowrap rounded-md;
|
|
388
|
-
@apply text-sm font-medium;
|
|
389
|
-
@apply h-9 px-4 py-2;
|
|
390
|
-
@apply cursor-default;
|
|
391
|
-
@apply outline-none;
|
|
392
|
-
@apply transition-colors;
|
|
393
|
-
@apply select-none;
|
|
394
|
-
|
|
395
|
-
/* SVG icons inside buttons */
|
|
396
|
-
& svg {
|
|
397
|
-
@apply pointer-events-none size-4 shrink-0;
|
|
488
|
+
[data-component="button"]:where([data-size="sm"]) {
|
|
489
|
+
@apply h-8 px-3 text-xs;
|
|
398
490
|
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/* Disabled state - applies to all variants */
|
|
402
|
-
[data-component="button"]:disabled,
|
|
403
|
-
[data-component="button"][aria-disabled="true"] {
|
|
404
|
-
@apply pointer-events-none opacity-50;
|
|
405
|
-
}
|
|
406
491
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
0 0 0 2px var(--background),
|
|
411
|
-
0 0 0 4px var(--ring);
|
|
412
|
-
}
|
|
492
|
+
[data-component="button"]:where([data-size="lg"]) {
|
|
493
|
+
@apply h-10 px-8;
|
|
494
|
+
}
|
|
413
495
|
|
|
414
|
-
|
|
496
|
+
[data-component="button"]:where([data-size="icon"]) {
|
|
497
|
+
@apply size-9 p-0;
|
|
498
|
+
}
|
|
415
499
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
[data-component="button"][data-variant="primary"] {
|
|
420
|
-
background-color: var(--primary);
|
|
421
|
-
color: var(--primary-foreground);
|
|
422
|
-
box-shadow: var(--shadow-sm, 0 1px 2px 0 rgb(0 0 0 / 0.05));
|
|
423
|
-
}
|
|
500
|
+
[data-component="button"]:where([data-size="icon-sm"]) {
|
|
501
|
+
@apply size-8 p-0;
|
|
502
|
+
}
|
|
424
503
|
|
|
425
|
-
[data-component="button"]:
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
background-color: color-mix(in oklch, var(--primary) 90%, black);
|
|
429
|
-
}
|
|
504
|
+
[data-component="button"]:where([data-size="icon-lg"]) {
|
|
505
|
+
@apply size-10 p-0;
|
|
506
|
+
}
|
|
430
507
|
|
|
431
|
-
/*
|
|
432
|
-
[data-component="button"][data-variant="destructive"] {
|
|
433
|
-
background-color: var(--destructive);
|
|
434
|
-
color: var(--destructive-foreground);
|
|
435
|
-
box-shadow: var(--shadow-sm, 0 1px 2px 0 rgb(0 0 0 / 0.05));
|
|
436
|
-
}
|
|
508
|
+
/* ----- Variants ----- */
|
|
437
509
|
|
|
438
|
-
[data-
|
|
439
|
-
|
|
440
|
-
|
|
510
|
+
/* Default (Primary) — :not([data-variant]) covers the un-varianted case */
|
|
511
|
+
[data-component="button"]:where(:not([data-variant]), [data-variant="default"], [data-variant="primary"]) {
|
|
512
|
+
background-color: var(--primary);
|
|
513
|
+
color: var(--primary-foreground);
|
|
514
|
+
box-shadow: var(--elevation-raised, var(--shadow-sm, 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)));
|
|
515
|
+
}
|
|
441
516
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
517
|
+
/* Destructive */
|
|
518
|
+
[data-component="button"]:where([data-variant="destructive"]) {
|
|
519
|
+
background-color: var(--destructive);
|
|
520
|
+
color: var(--destructive-foreground);
|
|
521
|
+
box-shadow: var(--elevation-raised, var(--shadow-sm, 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)));
|
|
522
|
+
}
|
|
447
523
|
|
|
448
|
-
/* Outline */
|
|
449
|
-
[data-component="button"][data-variant="outline"] {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
524
|
+
/* Outline */
|
|
525
|
+
[data-component="button"]:where([data-variant="outline"]) {
|
|
526
|
+
/* transparent, not --background: an outline button has to work on top of a
|
|
527
|
+
card or popover, where painting the page background makes it a light box */
|
|
528
|
+
background-color: transparent;
|
|
529
|
+
color: var(--foreground);
|
|
530
|
+
border: 1px solid var(--input);
|
|
531
|
+
box-shadow: var(--elevation-raised, var(--shadow-sm, 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)));
|
|
532
|
+
}
|
|
455
533
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
534
|
+
/* Secondary */
|
|
535
|
+
[data-component="button"]:where([data-variant="secondary"]) {
|
|
536
|
+
background-color: var(--secondary);
|
|
537
|
+
color: var(--secondary-foreground);
|
|
538
|
+
box-shadow: var(--elevation-raised, var(--shadow-sm, 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)));
|
|
539
|
+
}
|
|
460
540
|
|
|
461
|
-
/*
|
|
462
|
-
[data-component="button"][data-variant="
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
541
|
+
/* Ghost */
|
|
542
|
+
[data-component="button"]:where([data-variant="ghost"]) {
|
|
543
|
+
background-color: transparent;
|
|
544
|
+
color: var(--foreground);
|
|
545
|
+
box-shadow: var(--elevation-none, none);
|
|
546
|
+
}
|
|
467
547
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
548
|
+
/* Link — deliberately after the sizes: a link button has no box, so it must
|
|
549
|
+
win over the height and padding of whatever size it was given */
|
|
550
|
+
[data-component="button"]:where([data-variant="link"]) {
|
|
551
|
+
background-color: transparent;
|
|
552
|
+
color: var(--primary);
|
|
553
|
+
text-decoration-line: underline;
|
|
554
|
+
text-underline-offset: 4px;
|
|
555
|
+
box-shadow: var(--elevation-none, none);
|
|
556
|
+
@apply h-auto px-0 py-0;
|
|
557
|
+
}
|
|
471
558
|
|
|
472
|
-
/*
|
|
473
|
-
[data-component="button"][data-variant="ghost"] {
|
|
474
|
-
background-color: transparent;
|
|
475
|
-
color: var(--foreground);
|
|
476
|
-
box-shadow: none;
|
|
477
|
-
}
|
|
559
|
+
/* ----- States ----- */
|
|
478
560
|
|
|
479
|
-
[data-component="button"][data-variant="
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
561
|
+
[data-component="button"]:where(:not([data-variant]):hover, [data-variant="default"]:hover, [data-variant="primary"]:hover) {
|
|
562
|
+
/* Tokenize the outcome, not the recipe: darkening is wrong in dark mode,
|
|
563
|
+
where --primary is already light. */
|
|
564
|
+
background-color: var(--primary-hover, color-mix(in oklch, var(--primary) 90%, black));
|
|
565
|
+
}
|
|
483
566
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
color: var(--primary);
|
|
488
|
-
text-decoration-line: underline;
|
|
489
|
-
text-underline-offset: 4px;
|
|
490
|
-
box-shadow: none;
|
|
491
|
-
@apply h-auto px-0 py-0;
|
|
492
|
-
}
|
|
567
|
+
[data-component="button"]:where([data-variant="destructive"]:hover) {
|
|
568
|
+
background-color: var(--destructive-hover, color-mix(in oklch, var(--destructive) 90%, black));
|
|
569
|
+
}
|
|
493
570
|
|
|
494
|
-
[data-component="button"][data-variant="
|
|
495
|
-
|
|
496
|
-
|
|
571
|
+
[data-component="button"]:where([data-variant="outline"]:hover) {
|
|
572
|
+
background-color: var(--accent-hover, var(--accent));
|
|
573
|
+
color: var(--accent-foreground);
|
|
574
|
+
}
|
|
497
575
|
|
|
498
|
-
|
|
576
|
+
[data-component="button"]:where([data-variant="secondary"]:hover) {
|
|
577
|
+
background-color: var(--secondary-hover, color-mix(in oklch, var(--secondary) 80%, black));
|
|
578
|
+
}
|
|
499
579
|
|
|
500
|
-
[data-component="button"][data-
|
|
501
|
-
|
|
502
|
-
|
|
580
|
+
[data-component="button"]:where([data-variant="ghost"]:hover) {
|
|
581
|
+
background-color: var(--accent-hover, var(--accent));
|
|
582
|
+
color: var(--accent-foreground);
|
|
583
|
+
}
|
|
503
584
|
|
|
504
|
-
[data-component="button"][data-
|
|
505
|
-
|
|
506
|
-
}
|
|
585
|
+
[data-component="button"]:where([data-variant="link"]:hover) {
|
|
586
|
+
text-decoration-line: underline;
|
|
587
|
+
}
|
|
507
588
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
589
|
+
/* Focus ring.
|
|
590
|
+
* outline, not box-shadow. The ring no longer collides with the elevation each
|
|
591
|
+
* variant declares, so a single selector suffices and no variant can clobber
|
|
592
|
+
* it; it also survives forced-colors mode and overflow-hidden ancestors, and
|
|
593
|
+
* needs no knowledge of the backdrop color.
|
|
594
|
+
*/
|
|
595
|
+
[data-component="button"]:where(:focus-visible) {
|
|
596
|
+
outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
|
|
597
|
+
var(--focus-ring-color, var(--ring));
|
|
598
|
+
outline-offset: var(--focus-ring-offset, 0px);
|
|
599
|
+
}
|
|
511
600
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
601
|
+
/* Destructive keeps its own red ring. This only sets a custom property, so it
|
|
602
|
+
competes with nothing but other --focus-ring-color declarations; the shared
|
|
603
|
+
outline above resolves it at computed-value time. */
|
|
604
|
+
[data-component="button"]:where([data-variant="destructive"]:focus-visible) {
|
|
605
|
+
--focus-ring-color: var(--destructive);
|
|
606
|
+
}
|
|
515
607
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
608
|
+
/* Disabled - applies to all variants */
|
|
609
|
+
[data-component="button"]:where(:disabled, [aria-disabled="true"]) {
|
|
610
|
+
@apply pointer-events-none opacity-50;
|
|
611
|
+
}
|
|
519
612
|
|
|
520
|
-
/* ===================================================================
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
[data-form-part="description"] {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
}
|
|
613
|
+
/* ===================================================================
|
|
614
|
+
HELP TEXT / DESCRIPTION
|
|
615
|
+
Usage: <p data-form-part="description">We'll never share your email.</p>
|
|
616
|
+
=================================================================== */
|
|
617
|
+
[data-form-part="description"] {
|
|
618
|
+
@apply text-sm;
|
|
619
|
+
color: var(--muted-foreground);
|
|
620
|
+
}
|
|
528
621
|
|
|
529
|
-
/* ===================================================================
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
[data-form-part="error"] {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
622
|
+
/* ===================================================================
|
|
623
|
+
ERROR TEXT / MESSAGE
|
|
624
|
+
Usage: <p data-form-part="error">Email is required</p>
|
|
625
|
+
=================================================================== */
|
|
626
|
+
[data-form-part="error"] {
|
|
627
|
+
@apply text-sm;
|
|
628
|
+
font-weight: var(--label-weight, 500);
|
|
629
|
+
color: var(--destructive-foreground);
|
|
630
|
+
}
|
|
537
631
|
|
|
538
|
-
/* Rails error wrapper compatibility */
|
|
539
|
-
.field_with_errors {
|
|
540
|
-
|
|
541
|
-
}
|
|
632
|
+
/* Rails error wrapper compatibility */
|
|
633
|
+
.field_with_errors {
|
|
634
|
+
@apply contents;
|
|
635
|
+
}
|
|
542
636
|
|
|
543
|
-
.field_with_errors [data-component="input"],
|
|
544
|
-
.field_with_errors [data-component="textarea"],
|
|
545
|
-
.field_with_errors [data-component="select"] {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
var(--
|
|
549
|
-
|
|
550
|
-
|
|
637
|
+
:where(.field_with_errors) [data-component="input"],
|
|
638
|
+
:where(.field_with_errors) [data-component="textarea"],
|
|
639
|
+
:where(.field_with_errors) [data-component="select"] {
|
|
640
|
+
--focus-ring-color: color-mix(in oklch, var(--destructive) 20%, transparent);
|
|
641
|
+
border-color: var(--destructive);
|
|
642
|
+
outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
|
|
643
|
+
var(--focus-ring-color, color-mix(in oklch, var(--destructive) 20%, transparent));
|
|
644
|
+
outline-offset: var(--focus-ring-offset, 0px);
|
|
645
|
+
}
|
|
551
646
|
|
|
552
|
-
/* ===================================================================
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
[data-form-part="actions"] {
|
|
557
|
-
|
|
558
|
-
}
|
|
647
|
+
/* ===================================================================
|
|
648
|
+
FORM ACTIONS (Submit area)
|
|
649
|
+
Usage: <div data-form-part="actions">
|
|
650
|
+
=================================================================== */
|
|
651
|
+
[data-form-part="actions"] {
|
|
652
|
+
@apply flex items-center gap-3 pt-4;
|
|
653
|
+
}
|
|
559
654
|
|
|
560
|
-
[data-form-part="actions"][data-align="end"] {
|
|
561
|
-
|
|
562
|
-
}
|
|
655
|
+
[data-form-part="actions"]:where([data-align="end"]) {
|
|
656
|
+
@apply justify-end;
|
|
657
|
+
}
|
|
563
658
|
|
|
564
|
-
[data-form-part="actions"][data-align="between"] {
|
|
565
|
-
|
|
566
|
-
}
|
|
659
|
+
[data-form-part="actions"]:where([data-align="between"]) {
|
|
660
|
+
@apply justify-between;
|
|
661
|
+
}
|
|
567
662
|
|
|
568
|
-
/* ===================================================================
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
[data-component="fieldset"] {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
663
|
+
/* ===================================================================
|
|
664
|
+
FIELDSET & LEGEND
|
|
665
|
+
=================================================================== */
|
|
666
|
+
[data-component="fieldset"] {
|
|
667
|
+
@apply space-y-4 border p-4;
|
|
668
|
+
border-radius: var(--fieldset-radius, var(--surface-radius, 0.5rem));
|
|
669
|
+
border-color: var(--border);
|
|
670
|
+
}
|
|
575
671
|
|
|
576
|
-
[data-component="legend"],
|
|
577
|
-
[data-component="fieldset"] > legend {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
672
|
+
[data-component="legend"],
|
|
673
|
+
:where([data-component="fieldset"]) > legend {
|
|
674
|
+
@apply px-2 text-sm;
|
|
675
|
+
font-weight: var(--label-weight, 500);
|
|
676
|
+
color: var(--foreground);
|
|
677
|
+
}
|
|
581
678
|
|
|
582
|
-
/* ===================================================================
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
679
|
+
/* ===================================================================
|
|
680
|
+
INPUT GROUP (Prefix/Suffix support)
|
|
681
|
+
Declared after the input sizes on purpose: the prefix/suffix padding has
|
|
682
|
+
to win over both the base px-3 and the data-size paddings.
|
|
683
|
+
=================================================================== */
|
|
684
|
+
[data-component="input-group"] {
|
|
685
|
+
@apply relative flex items-center;
|
|
686
|
+
}
|
|
588
687
|
|
|
589
|
-
[data-component="input-group"] [data-component="input"] {
|
|
590
|
-
|
|
591
|
-
}
|
|
688
|
+
:where([data-component="input-group"]) [data-component="input"] {
|
|
689
|
+
@apply flex-1;
|
|
690
|
+
}
|
|
592
691
|
|
|
593
|
-
[data-input-group-part="prefix"],
|
|
594
|
-
[data-input-group-part="suffix"] {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
692
|
+
[data-input-group-part="prefix"],
|
|
693
|
+
[data-input-group-part="suffix"] {
|
|
694
|
+
@apply absolute flex items-center justify-center;
|
|
695
|
+
@apply h-full px-3;
|
|
696
|
+
@apply text-sm;
|
|
697
|
+
color: var(--muted-foreground);
|
|
698
|
+
pointer-events: none;
|
|
699
|
+
}
|
|
601
700
|
|
|
602
|
-
[data-input-group-part="prefix"] {
|
|
603
|
-
|
|
604
|
-
}
|
|
701
|
+
[data-input-group-part="prefix"] {
|
|
702
|
+
@apply left-0;
|
|
703
|
+
}
|
|
605
704
|
|
|
606
|
-
[data-input-group-part="suffix"] {
|
|
607
|
-
|
|
608
|
-
}
|
|
705
|
+
[data-input-group-part="suffix"] {
|
|
706
|
+
@apply right-0;
|
|
707
|
+
}
|
|
609
708
|
|
|
610
|
-
/* Adjust input padding when prefix/suffix present */
|
|
611
|
-
[data-component="input-group"]:has([data-input-group-part="prefix"]) [data-component="input"] {
|
|
612
|
-
|
|
613
|
-
}
|
|
709
|
+
/* Adjust input padding when prefix/suffix present */
|
|
710
|
+
:where([data-component="input-group"]:has([data-input-group-part="prefix"])) [data-component="input"] {
|
|
711
|
+
@apply pl-9;
|
|
712
|
+
}
|
|
614
713
|
|
|
615
|
-
[data-component="input-group"]:has([data-input-group-part="suffix"]) [data-component="input"] {
|
|
616
|
-
|
|
714
|
+
:where([data-component="input-group"]:has([data-input-group-part="suffix"])) [data-component="input"] {
|
|
715
|
+
@apply pr-9;
|
|
716
|
+
}
|
|
617
717
|
}
|