lostcoords-ui 0.5.0 → 0.7.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 +111 -49
- data/app/assets/tailwind/lostcoords/ui/card.css +5 -2
- data/app/assets/tailwind/lostcoords/ui/{disclosure.css → collapsible.css} +1 -1
- data/app/assets/tailwind/lostcoords/ui/components/code-block.css +5 -0
- data/app/assets/tailwind/lostcoords/ui/components/collapsible.css +5 -0
- data/app/assets/tailwind/lostcoords/ui/components/dialog.css +9 -0
- data/app/assets/tailwind/lostcoords/ui/components/emoji-picker.css +5 -0
- data/app/assets/tailwind/lostcoords/ui/components/menu.css +5 -0
- data/app/assets/tailwind/lostcoords/ui/components/prose.css +3 -0
- data/app/assets/tailwind/lostcoords/ui/components/table.css +3 -0
- data/app/assets/tailwind/lostcoords/ui/components/toast.css +5 -0
- data/app/assets/tailwind/lostcoords/ui/components/tooltip.css +4 -0
- data/app/assets/tailwind/lostcoords/ui/components.css +12 -25
- data/app/assets/tailwind/lostcoords/ui/core.css +21 -0
- data/app/assets/tailwind/lostcoords/ui/emoji-picker.css +45 -47
- data/app/assets/tailwind/lostcoords/ui/popover.css +0 -52
- data/app/assets/tailwind/lostcoords/ui/{reading.css → prose.css} +6 -0
- data/app/assets/tailwind/lostcoords/ui/sheet.css +3 -64
- data/app/assets/tailwind/lostcoords/ui/theme.css +16 -1
- data/app/assets/tailwind/lostcoords/ui/toast.css +23 -0
- data/app/assets/tailwind/lostcoords/ui/tooltip.css +12 -11
- data/app/assets/tailwind/lostcoords/ui/typography.css +1 -7
- data/app/helpers/lostcoords/ui/badge_helper.rb +2 -2
- data/app/helpers/lostcoords/ui/button_helper.rb +12 -6
- data/app/helpers/lostcoords/ui/choice_helper.rb +11 -7
- data/app/helpers/lostcoords/ui/collapsible_helper.rb +9 -0
- data/app/helpers/lostcoords/ui/dialog_helper.rb +10 -5
- data/app/helpers/lostcoords/ui/emoji_picker_helper.rb +6 -0
- data/app/helpers/lostcoords/ui/feedback_helper.rb +2 -2
- data/app/helpers/lostcoords/ui/form_helper.rb +4 -3
- data/app/helpers/lostcoords/ui/glass_helper.rb +1 -1
- data/app/helpers/lostcoords/ui/menu_helper.rb +37 -2
- data/app/helpers/lostcoords/ui/navigation_helper.rb +17 -4
- data/app/helpers/lostcoords/ui/render_attributes_helper.rb +17 -1
- data/app/helpers/lostcoords/ui/sheet_helper.rb +4 -0
- data/app/helpers/lostcoords/ui/structures_helper.rb +7 -95
- data/app/helpers/lostcoords/ui/toast_helper.rb +22 -0
- data/app/helpers/lostcoords/ui/{overlay_helper.rb → tooltip_helper.rb} +3 -3
- data/app/helpers/lostcoords/ui/typography_helper.rb +30 -0
- data/app/javascript/lostcoords/ui/controllers/lc_collapsible_controller.js +1 -1
- data/app/javascript/lostcoords/ui/controllers/lc_emoji_picker_controller.js +34 -6
- data/app/javascript/lostcoords/ui/controllers/lc_kbd_controller.js +12 -0
- data/app/javascript/lostcoords/ui/controllers/lc_menu_controller.js +23 -1
- data/app/javascript/lostcoords/ui/controllers/lc_selection_controller.js +8 -16
- data/app/javascript/lostcoords/ui/controllers/lc_sheet_controller.js +15 -3
- data/app/javascript/lostcoords/ui/controllers/lc_shortcut_controller.js +45 -0
- data/app/javascript/lostcoords/ui/controllers/lc_tabs_controller.js +18 -15
- data/app/javascript/lostcoords/ui/controllers/lc_toast_region_controller.js +62 -0
- data/app/javascript/lostcoords/ui/controllers/lc_tooltip_controller.js +46 -6
- data/app/javascript/lostcoords/ui/helpers/catalog_picker.js +72 -20
- data/app/javascript/lostcoords/ui/helpers/dismiss_stack.js +23 -0
- data/app/javascript/lostcoords/ui/helpers/floating_panel.js +6 -7
- data/app/javascript/lostcoords/ui/helpers/menu_keys.js +19 -18
- data/app/javascript/lostcoords/ui/helpers/roving_focus.js +175 -0
- data/app/javascript/lostcoords/ui/helpers/shortcuts.js +162 -0
- data/app/javascript/lostcoords/ui/helpers/top_layer.js +48 -10
- data/app/javascript/lostcoords/ui/index.js +6 -0
- data/app/views/lostcoords/ui/components/_emoji_picker.html.erb +27 -27
- data/app/views/lostcoords/ui/components/_emoji_picker_grid.html.erb +3 -3
- data/app/views/lostcoords/ui/components/_lc_code_block.html.erb +1 -1
- data/lib/lostcoords/ui/action_view.rb +1 -0
- data/lib/lostcoords/ui/builders.rb +2 -2
- data/lib/lostcoords/ui/form_builder.rb +55 -10
- data/lib/lostcoords/ui/stylesheets.rb +25 -0
- data/lib/lostcoords/ui/version.rb +1 -1
- data/lib/tasks/lostcoords_ui.rake +11 -0
- metadata +23 -6
- data/config/helper_renames.json +0 -111
- data/script/migrate_helpers.rb +0 -136
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
@layer components {
|
|
2
|
-
.emoji-picker {
|
|
2
|
+
.lc-emoji-picker {
|
|
3
3
|
display: flex;
|
|
4
4
|
gap: 6px;
|
|
5
5
|
flex-wrap: wrap;
|
|
6
6
|
align-items: center;
|
|
7
7
|
|
|
8
|
-
/* Sit above the mobile app nav (z-100 + 25) so the sheet covers it. */
|
|
9
|
-
--lc-emoji-sheet-z: calc(100 + 30);
|
|
10
8
|
--lc-emoji-cell: 2.25rem;
|
|
11
9
|
}
|
|
12
10
|
|
|
13
11
|
/* ---- Trigger + quick choices -------------------------------------- */
|
|
14
|
-
.emoji-picker__choice,
|
|
15
|
-
.emoji-picker__trigger {
|
|
12
|
+
.lc-emoji-picker__choice,
|
|
13
|
+
.lc-emoji-picker__trigger {
|
|
16
14
|
display: inline-grid;
|
|
17
15
|
place-items: center;
|
|
18
16
|
inline-size: 44px;
|
|
@@ -38,12 +36,12 @@
|
|
|
38
36
|
}
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
.emoji-picker__trigger {
|
|
39
|
+
.lc-emoji-picker__trigger {
|
|
42
40
|
color: var(--muted-foreground);
|
|
43
41
|
font-weight: var(--font-weight-normal, 400);
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
.emoji-picker__more {
|
|
44
|
+
.lc-emoji-picker__more {
|
|
47
45
|
position: relative;
|
|
48
46
|
display: inline-flex;
|
|
49
47
|
}
|
|
@@ -58,7 +56,7 @@
|
|
|
58
56
|
dims everything beneath it — including the app nav. The allow-discrete
|
|
59
57
|
transitions keep it in the top layer while it eases out, so it fades
|
|
60
58
|
both ways instead of snapping away on close. */
|
|
61
|
-
.emoji-picker__popover::backdrop {
|
|
59
|
+
.lc-emoji-picker__popover::backdrop {
|
|
62
60
|
background: var(--lc-scrim);
|
|
63
61
|
opacity: 0;
|
|
64
62
|
transition:
|
|
@@ -72,7 +70,7 @@
|
|
|
72
70
|
/* Only the phone sheet dims the page; the desktop popover rides the top
|
|
73
71
|
layer too (so nothing paints over it) but floats over an undimmed page. */
|
|
74
72
|
@media (width < 40rem) {
|
|
75
|
-
.emoji-picker__popover:popover-open::backdrop {
|
|
73
|
+
.lc-emoji-picker__popover:popover-open::backdrop {
|
|
76
74
|
opacity: 1;
|
|
77
75
|
|
|
78
76
|
@starting-style { opacity: 0; }
|
|
@@ -82,7 +80,7 @@
|
|
|
82
80
|
/* Phones get a solid sheet; on desktop the host paints the popover as a
|
|
83
81
|
floating surface (glass in wariwari), so the gem leaves it unpainted. */
|
|
84
82
|
@media (width < 40rem) {
|
|
85
|
-
.emoji-picker__popover {
|
|
83
|
+
.lc-emoji-picker__popover {
|
|
86
84
|
border: 1px solid var(--lc-glass-edge, var(--border));
|
|
87
85
|
border-block-end: 0;
|
|
88
86
|
background: var(--background);
|
|
@@ -90,11 +88,11 @@
|
|
|
90
88
|
}
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
.emoji-picker__popover {
|
|
91
|
+
.lc-emoji-picker__popover {
|
|
94
92
|
position: fixed;
|
|
95
93
|
inset: auto 0 0;
|
|
96
94
|
margin: 0;
|
|
97
|
-
z-index: var(--lc-
|
|
95
|
+
z-index: var(--lc-z-overlay);
|
|
98
96
|
display: none;
|
|
99
97
|
max-inline-size: none;
|
|
100
98
|
color: var(--foreground);
|
|
@@ -125,7 +123,7 @@
|
|
|
125
123
|
|
|
126
124
|
/* Fallback for browsers without the Popover API: class-driven display. */
|
|
127
125
|
@supports not selector(:popover-open) {
|
|
128
|
-
.emoji-picker__popover[data-open] {
|
|
126
|
+
.lc-emoji-picker__popover[data-open] {
|
|
129
127
|
display: flex;
|
|
130
128
|
transform: translateY(0);
|
|
131
129
|
}
|
|
@@ -135,7 +133,7 @@
|
|
|
135
133
|
is large; the host's sheet draws the pill.
|
|
136
134
|
touch-action: none lets a vertical drag pull the sheet down rather than
|
|
137
135
|
scroll. Drag-to-dismiss is wired by the sheet controller. */
|
|
138
|
-
.emoji-picker__handle {
|
|
136
|
+
.lc-emoji-picker__handle {
|
|
139
137
|
flex: 0 0 auto;
|
|
140
138
|
display: grid;
|
|
141
139
|
place-items: center;
|
|
@@ -145,10 +143,10 @@
|
|
|
145
143
|
cursor: grab;
|
|
146
144
|
}
|
|
147
145
|
|
|
148
|
-
.emoji-picker__handle:active { cursor: grabbing; }
|
|
146
|
+
.lc-emoji-picker__handle:active { cursor: grabbing; }
|
|
149
147
|
|
|
150
148
|
/* ---- Search row --------------------------------------------------- */
|
|
151
|
-
.emoji-picker__search {
|
|
149
|
+
.lc-emoji-picker__search {
|
|
152
150
|
flex: 0 0 auto;
|
|
153
151
|
display: flex;
|
|
154
152
|
gap: 0.5rem;
|
|
@@ -156,14 +154,14 @@
|
|
|
156
154
|
padding: 0.75rem 0.75rem 0.5rem;
|
|
157
155
|
}
|
|
158
156
|
|
|
159
|
-
.emoji-picker__search-field {
|
|
157
|
+
.lc-emoji-picker__search-field {
|
|
160
158
|
position: relative;
|
|
161
159
|
flex: 1 1 auto;
|
|
162
160
|
display: flex;
|
|
163
161
|
align-items: center;
|
|
164
162
|
}
|
|
165
163
|
|
|
166
|
-
.emoji-picker__search-icon {
|
|
164
|
+
.lc-emoji-picker__search-icon {
|
|
167
165
|
position: absolute;
|
|
168
166
|
inset-inline-start: 14px;
|
|
169
167
|
inline-size: 18px;
|
|
@@ -172,7 +170,7 @@
|
|
|
172
170
|
pointer-events: none;
|
|
173
171
|
}
|
|
174
172
|
|
|
175
|
-
.emoji-picker__search-input {
|
|
173
|
+
.lc-emoji-picker__search-input {
|
|
176
174
|
inline-size: 100%;
|
|
177
175
|
min-block-size: 44px;
|
|
178
176
|
padding-inline: 40px 1rem;
|
|
@@ -198,12 +196,12 @@
|
|
|
198
196
|
}
|
|
199
197
|
|
|
200
198
|
/* ---- Skin-tone control ------------------------------------------- */
|
|
201
|
-
.emoji-picker__tones {
|
|
199
|
+
.lc-emoji-picker__tones {
|
|
202
200
|
position: relative;
|
|
203
201
|
flex: 0 0 auto;
|
|
204
202
|
}
|
|
205
203
|
|
|
206
|
-
.emoji-picker__tone-toggle {
|
|
204
|
+
.lc-emoji-picker__tone-toggle {
|
|
207
205
|
display: inline-grid;
|
|
208
206
|
place-items: center;
|
|
209
207
|
inline-size: 44px;
|
|
@@ -221,7 +219,7 @@
|
|
|
221
219
|
&[aria-expanded="true"] { background: color-mix(in oklab, var(--primary) 12%, var(--background)); }
|
|
222
220
|
}
|
|
223
221
|
|
|
224
|
-
.emoji-picker__tone-menu {
|
|
222
|
+
.lc-emoji-picker__tone-menu {
|
|
225
223
|
position: absolute;
|
|
226
224
|
inset-block-end: calc(100% + 6px);
|
|
227
225
|
inset-inline-end: 0;
|
|
@@ -234,7 +232,7 @@
|
|
|
234
232
|
&[hidden] { display: none; }
|
|
235
233
|
}
|
|
236
234
|
|
|
237
|
-
.emoji-picker__tone {
|
|
235
|
+
.lc-emoji-picker__tone {
|
|
238
236
|
display: inline-grid;
|
|
239
237
|
place-items: center;
|
|
240
238
|
inline-size: 36px;
|
|
@@ -255,7 +253,7 @@
|
|
|
255
253
|
|
|
256
254
|
/* Category tabs: a segmented track, like the app's tab strips — a paper-2
|
|
257
255
|
track with the active tab raised on a paper thumb. */
|
|
258
|
-
.emoji-picker__tabs {
|
|
256
|
+
.lc-emoji-picker__tabs {
|
|
259
257
|
flex: 0 0 auto;
|
|
260
258
|
display: flex;
|
|
261
259
|
gap: 2px;
|
|
@@ -269,7 +267,7 @@
|
|
|
269
267
|
&::-webkit-scrollbar { display: none; }
|
|
270
268
|
}
|
|
271
269
|
|
|
272
|
-
.emoji-picker__tab {
|
|
270
|
+
.lc-emoji-picker__tab {
|
|
273
271
|
flex: 0 0 auto;
|
|
274
272
|
display: inline-grid;
|
|
275
273
|
place-items: center;
|
|
@@ -294,7 +292,7 @@
|
|
|
294
292
|
}
|
|
295
293
|
|
|
296
294
|
/* ---- Scrollable body --------------------------------------------- */
|
|
297
|
-
.emoji-picker__body {
|
|
295
|
+
.lc-emoji-picker__body {
|
|
298
296
|
position: relative;
|
|
299
297
|
flex: 1 1 auto;
|
|
300
298
|
overflow-y: auto;
|
|
@@ -309,15 +307,15 @@
|
|
|
309
307
|
@media (prefers-reduced-motion: reduce) { scroll-behavior: auto; }
|
|
310
308
|
}
|
|
311
309
|
|
|
312
|
-
.emoji-picker__category + .emoji-picker__category,
|
|
313
|
-
.emoji-picker__recent + .emoji-picker__catalog,
|
|
314
|
-
.emoji-picker__results + * {
|
|
310
|
+
.lc-emoji-picker__category + .lc-emoji-picker__category,
|
|
311
|
+
.lc-emoji-picker__recent + .lc-emoji-picker__catalog,
|
|
312
|
+
.lc-emoji-picker__results + * {
|
|
315
313
|
margin-block-start: 0.5rem;
|
|
316
314
|
}
|
|
317
315
|
|
|
318
316
|
/* Heads scroll with their section: the tab track already marks the current
|
|
319
317
|
category, and a sticky band would sit as a stripe across the panel. */
|
|
320
|
-
.emoji-picker__category-head {
|
|
318
|
+
.lc-emoji-picker__category-head {
|
|
321
319
|
display: flex;
|
|
322
320
|
align-items: baseline;
|
|
323
321
|
justify-content: space-between;
|
|
@@ -338,7 +336,7 @@
|
|
|
338
336
|
}
|
|
339
337
|
}
|
|
340
338
|
|
|
341
|
-
.emoji-picker__grid {
|
|
339
|
+
.lc-emoji-picker__grid {
|
|
342
340
|
display: grid;
|
|
343
341
|
grid-template-columns: repeat(auto-fill, minmax(var(--lc-emoji-cell), 1fr));
|
|
344
342
|
gap: 2px;
|
|
@@ -370,13 +368,13 @@
|
|
|
370
368
|
}
|
|
371
369
|
|
|
372
370
|
/* Section that owns the sticky header needs the scroll anchor, not the grid */
|
|
373
|
-
.emoji-picker__category,
|
|
374
|
-
.emoji-picker__recent,
|
|
375
|
-
.emoji-picker__results {
|
|
371
|
+
.lc-emoji-picker__category,
|
|
372
|
+
.lc-emoji-picker__recent,
|
|
373
|
+
.lc-emoji-picker__results {
|
|
376
374
|
scroll-margin-block-start: 0;
|
|
377
375
|
}
|
|
378
376
|
|
|
379
|
-
.emoji-picker__empty {
|
|
377
|
+
.lc-emoji-picker__empty {
|
|
380
378
|
margin: 0;
|
|
381
379
|
padding: 1rem 0.5rem;
|
|
382
380
|
color: var(--muted-foreground);
|
|
@@ -385,16 +383,16 @@
|
|
|
385
383
|
}
|
|
386
384
|
|
|
387
385
|
/* ---- Search mode: swap catalog for a single ranked result list ---- */
|
|
388
|
-
.emoji-picker__popover:not([data-searching]) .emoji-picker__results { display: none; }
|
|
386
|
+
.lc-emoji-picker__popover:not([data-searching]) .lc-emoji-picker__results { display: none; }
|
|
389
387
|
|
|
390
|
-
.emoji-picker__popover[data-searching] {
|
|
391
|
-
.emoji-picker__tabs,
|
|
392
|
-
.emoji-picker__recent,
|
|
393
|
-
.emoji-picker__catalog { display: none; }
|
|
388
|
+
.lc-emoji-picker__popover[data-searching] {
|
|
389
|
+
.lc-emoji-picker__tabs,
|
|
390
|
+
.lc-emoji-picker__recent,
|
|
391
|
+
.lc-emoji-picker__catalog { display: none; }
|
|
394
392
|
}
|
|
395
393
|
|
|
396
394
|
/* ---- Preview footer ---------------------------------------------- */
|
|
397
|
-
.emoji-picker__preview {
|
|
395
|
+
.lc-emoji-picker__preview {
|
|
398
396
|
flex: 0 0 auto;
|
|
399
397
|
display: flex;
|
|
400
398
|
gap: 0.5rem;
|
|
@@ -403,12 +401,12 @@
|
|
|
403
401
|
padding: 0.25rem 1rem 0.75rem;
|
|
404
402
|
}
|
|
405
403
|
|
|
406
|
-
.emoji-picker__preview-emoji {
|
|
404
|
+
.lc-emoji-picker__preview-emoji {
|
|
407
405
|
font-size: 1.5rem;
|
|
408
406
|
line-height: 1;
|
|
409
407
|
}
|
|
410
408
|
|
|
411
|
-
.emoji-picker__preview-name {
|
|
409
|
+
.lc-emoji-picker__preview-name {
|
|
412
410
|
overflow: hidden;
|
|
413
411
|
font-size: 1rem;
|
|
414
412
|
color: var(--muted-foreground);
|
|
@@ -422,7 +420,7 @@
|
|
|
422
420
|
|
|
423
421
|
/* ==================================================================== */
|
|
424
422
|
@media (width >= 40rem) {
|
|
425
|
-
.emoji-picker__popover {
|
|
423
|
+
.lc-emoji-picker__popover {
|
|
426
424
|
position: absolute;
|
|
427
425
|
inset-block: calc(100% + 8px) auto;
|
|
428
426
|
inset-inline: 0 auto;
|
|
@@ -452,10 +450,10 @@
|
|
|
452
450
|
@media (prefers-reduced-motion: reduce) { transition: none; }
|
|
453
451
|
}
|
|
454
452
|
|
|
455
|
-
.emoji-picker__handle { display: none; }
|
|
453
|
+
.lc-emoji-picker__handle { display: none; }
|
|
456
454
|
|
|
457
|
-
main:has(.emoji-picker__popover[data-open]),
|
|
458
|
-
.emoji-picker:has(.emoji-picker__popover[data-open]) {
|
|
455
|
+
main:has(.lc-emoji-picker__popover[data-open]),
|
|
456
|
+
.lc-emoji-picker:has(.lc-emoji-picker__popover[data-open]) {
|
|
459
457
|
z-index: var(--lc-z-overlay);
|
|
460
458
|
}
|
|
461
459
|
}
|
|
@@ -21,55 +21,3 @@
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
@layer components {
|
|
26
|
-
/* Desktop: the .lc-sheet becomes an anchored popover instead of a bottom sheet. */
|
|
27
|
-
@media (width >= 40rem) {
|
|
28
|
-
.lc-sheet--anchored {
|
|
29
|
-
@apply lc-popover-shell;
|
|
30
|
-
|
|
31
|
-
display: none;
|
|
32
|
-
flex-direction: column;
|
|
33
|
-
position: absolute;
|
|
34
|
-
inset-block: calc(100% + --spacing(2)) auto;
|
|
35
|
-
inset-inline: auto 0;
|
|
36
|
-
inline-size: min(24rem, calc(100vw - --spacing(8)));
|
|
37
|
-
max-block-size: min(28rem, calc(100vh - --spacing(24)));
|
|
38
|
-
padding-block-end: 0;
|
|
39
|
-
border-radius: var(--radius-3xl);
|
|
40
|
-
pointer-events: auto;
|
|
41
|
-
|
|
42
|
-
&[data-open] {
|
|
43
|
-
display: flex;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/* Enter/exit for small overlays, whichever way they open: a [hidden] toggle
|
|
49
|
-
(amount calculator, colour picker), the Popover API (phone nav menus) or a
|
|
50
|
-
modal <dialog>. The closed state is the exit pose, so close animates too;
|
|
51
|
-
allow-discrete holds display/overlay until it ends. --lc-pop-y sets the
|
|
52
|
-
direction: popovers drop from their trigger, dialogs rise. */
|
|
53
|
-
.lc-popover-pop {
|
|
54
|
-
--lc-pop-from: translateY(var(--lc-pop-y, -0.25rem)) scale(0.96);
|
|
55
|
-
|
|
56
|
-
transform-origin: var(--lc-pop-origin, top);
|
|
57
|
-
transition:
|
|
58
|
-
opacity 160ms var(--lc-ease-out-expo),
|
|
59
|
-
transform 240ms var(--lc-ease-out-expo),
|
|
60
|
-
display 240ms allow-discrete,
|
|
61
|
-
overlay 240ms allow-discrete;
|
|
62
|
-
|
|
63
|
-
&:is([hidden], [popover]:not(:popover-open), dialog:not([open])) {
|
|
64
|
-
opacity: 0;
|
|
65
|
-
transform: var(--lc-pop-from);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
&:is(:not([hidden], [popover], dialog), :popover-open, dialog[open]) {
|
|
69
|
-
@starting-style {
|
|
70
|
-
opacity: 0;
|
|
71
|
-
transform: var(--lc-pop-from);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
@@ -1,66 +1,5 @@
|
|
|
1
1
|
@layer components {
|
|
2
2
|
@media (width < 40rem) {
|
|
3
|
-
.lc-sheet {
|
|
4
|
-
position: fixed;
|
|
5
|
-
|
|
6
|
-
/* Anchored from the top edge at the screen height it opened at
|
|
7
|
-
(sheet_controller), then lifted by its own height. In-app browsers
|
|
8
|
-
(Brave, Chrome) resize the page under the keyboard; anything hung
|
|
9
|
-
from the bottom edge jumps a frame as they do, but the top holds. */
|
|
10
|
-
inset: var(--lc-sheet-floor, 100dvh) 0 auto;
|
|
11
|
-
translate: 0 -100%;
|
|
12
|
-
margin: 0;
|
|
13
|
-
z-index: var(--lc-z-sheet);
|
|
14
|
-
inline-size: 100%;
|
|
15
|
-
max-block-size: calc(var(--lc-sheet-floor, 100dvh) * 0.9);
|
|
16
|
-
overflow-y: auto;
|
|
17
|
-
overscroll-behavior: contain;
|
|
18
|
-
padding-block-end: max(env(safe-area-inset-bottom), 1rem);
|
|
19
|
-
border: 1px solid var(--lc-glass-edge);
|
|
20
|
-
border-start-start-radius: var(--radius-3xl);
|
|
21
|
-
border-start-end-radius: var(--radius-3xl);
|
|
22
|
-
|
|
23
|
-
/* Published as a variable, not just applied, because the sticky chrome
|
|
24
|
-
that rides on top of the sheet — the grabber bar, the pinned action
|
|
25
|
-
row — has to paint the *same* surface or it reads as a seam. In dark
|
|
26
|
-
mode the sheet is a lightened paper-2/paper-3 mix while --background is
|
|
27
|
-
0.045 lightness darker, which is exactly the band you could see. */
|
|
28
|
-
--lc-sheet-bg: var(--background);
|
|
29
|
-
@supports (background: light-dark(white, black)) {
|
|
30
|
-
--lc-sheet-bg: light-dark(var(--background), color-mix(in oklab, var(--muted) 92%, var(--accent)));
|
|
31
|
-
}
|
|
32
|
-
background: var(--lc-sheet-bg);
|
|
33
|
-
color: var(--foreground);
|
|
34
|
-
box-shadow: 0 -12px 40px -12px color-mix(in oklab, var(--foreground) 45%, transparent);
|
|
35
|
-
transform: translateY(var(--lc-sheet-offscreen));
|
|
36
|
-
transition:
|
|
37
|
-
transform var(--lc-sheet-duration) var(--lc-ease-sheet),
|
|
38
|
-
display var(--lc-sheet-duration) allow-discrete,
|
|
39
|
-
overlay var(--lc-sheet-duration) allow-discrete;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/* Popover-API reveal (currency): top layer drives display, so showPopover()/
|
|
43
|
-
hidePopover() animate the slide both ways, with a native ::backdrop. */
|
|
44
|
-
.lc-sheet[popover] { display: none; flex-direction: column; max-block-size: min(calc(var(--lc-sheet-floor, 100svh) * 0.72), 34rem); }
|
|
45
|
-
.lc-sheet[popover]:popover-open {
|
|
46
|
-
display: flex;
|
|
47
|
-
transform: translateY(0);
|
|
48
|
-
|
|
49
|
-
@starting-style { transform: translateY(var(--lc-sheet-offscreen)); }
|
|
50
|
-
}
|
|
51
|
-
.lc-sheet[popover]::backdrop {
|
|
52
|
-
background: var(--lc-scrim);
|
|
53
|
-
backdrop-filter: blur(2px);
|
|
54
|
-
opacity: 0;
|
|
55
|
-
transition:
|
|
56
|
-
opacity var(--lc-sheet-duration) var(--lc-ease-sheet),
|
|
57
|
-
display var(--lc-sheet-duration) allow-discrete,
|
|
58
|
-
overlay var(--lc-sheet-duration) allow-discrete;
|
|
59
|
-
|
|
60
|
-
@media (prefers-reduced-motion: reduce) { transition: none; }
|
|
61
|
-
}
|
|
62
|
-
.lc-sheet[popover]:popover-open::backdrop { opacity: 1; @starting-style { opacity: 0; } }
|
|
63
|
-
|
|
64
3
|
/* UIKit's 36×5 grabber. */
|
|
65
4
|
:is([data-lc-sheet-target~="handle"], dialog[data-controller~="lc-sheet"])::before {
|
|
66
5
|
content: "";
|
|
@@ -74,7 +13,7 @@
|
|
|
74
13
|
}
|
|
75
14
|
|
|
76
15
|
@layer modules {
|
|
77
|
-
/* Phones: every modal dialog is an iOS sheet (
|
|
16
|
+
/* Phones: every modal dialog is an iOS sheet (lc-sheet). It floats
|
|
78
17
|
inset with all corners round while it is short, and docks to the screen
|
|
79
18
|
edges as it reaches the large detent: --lc-sheet-p runs 0 → 1 between them.
|
|
80
19
|
Lives in this layer so it out-ranks a dialog's own frame. */
|
|
@@ -88,7 +27,7 @@
|
|
|
88
27
|
|
|
89
28
|
position: fixed;
|
|
90
29
|
|
|
91
|
-
/* Anchored from the top edge
|
|
30
|
+
/* Anchored from the top edge: the keyboard covers it and
|
|
92
31
|
nothing moves. */
|
|
93
32
|
inset: calc(var(--lc-sheet-floor, 100dvh) - var(--lc-sheet-gap)) 0 auto;
|
|
94
33
|
translate: 0 -100%;
|
|
@@ -151,7 +90,7 @@
|
|
|
151
90
|
}
|
|
152
91
|
|
|
153
92
|
/* Behind a large sheet the page shrinks back and rounds off, over black,
|
|
154
|
-
as UIKit's page sheet does;
|
|
93
|
+
as UIKit's page sheet does; lc-sheet drives the variables. */
|
|
155
94
|
html[data-lc-sheet-page] {
|
|
156
95
|
background: #000;
|
|
157
96
|
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
--color-secondary-foreground: var(--secondary-foreground);
|
|
16
16
|
--color-muted: var(--muted);
|
|
17
17
|
--color-muted-foreground: var(--muted-foreground);
|
|
18
|
+
|
|
19
|
+
/* Muted fills that a card switches to stay visible on it (card.css): bg-tile for choice tiles,
|
|
20
|
+
alerts, neutral badges, skeletons and key hints; bg-track for toggle group tracks. */
|
|
21
|
+
--color-tile: var(--lc-tile-bg, var(--muted));
|
|
22
|
+
--color-track: var(--lc-track-bg, var(--muted));
|
|
18
23
|
--color-accent: var(--accent);
|
|
19
24
|
--color-accent-foreground: var(--accent-foreground);
|
|
20
25
|
--color-destructive: var(--destructive);
|
|
@@ -56,6 +61,11 @@
|
|
|
56
61
|
--spacing-control-sm: var(--lc-control-sm);
|
|
57
62
|
--spacing-control-md: var(--lc-control-md);
|
|
58
63
|
--spacing-control-lg: var(--lc-control-lg);
|
|
64
|
+
|
|
65
|
+
/* In-page layers: z-raised lifts content over its neighbours, z-sticky keeps
|
|
66
|
+
app chrome above content. Overlays need no z-index; see --lc-z-popover. */
|
|
67
|
+
--z-index-raised: var(--lc-z-raised);
|
|
68
|
+
--z-index-sticky: var(--lc-z-sticky);
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
@theme {
|
|
@@ -116,8 +126,13 @@
|
|
|
116
126
|
--lc-sheet-duration: 500ms;
|
|
117
127
|
--lc-sheet-offscreen: calc(100% + 2rem);
|
|
118
128
|
|
|
129
|
+
--lc-z-raised: 1;
|
|
130
|
+
--lc-z-sticky: 20;
|
|
131
|
+
|
|
132
|
+
/* Dialogs, sheets, menus, popovers, tooltips and toasts open in the
|
|
133
|
+
browser's top layer, above every z-index. These order them only where
|
|
134
|
+
the Popover API is missing. */
|
|
119
135
|
--lc-z-popover: 30;
|
|
120
|
-
--lc-z-sheet: 50;
|
|
121
136
|
--lc-z-overlay: 60;
|
|
122
137
|
--lc-z-toast: 70;
|
|
123
138
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* lc-toast-region opens it as a popover; these rules also override the
|
|
3
|
+
popover defaults, which would centre it and hide it while closed. */
|
|
4
|
+
.lc-toast-region {
|
|
5
|
+
position: fixed;
|
|
6
|
+
inset: auto var(--spacing-gutter) var(--spacing-gutter) auto;
|
|
7
|
+
z-index: var(--lc-z-toast);
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
align-items: flex-end;
|
|
11
|
+
gap: var(--spacing-related);
|
|
12
|
+
max-inline-size: calc(100vw - var(--spacing-gutter) * 2);
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 0;
|
|
15
|
+
overflow: visible;
|
|
16
|
+
border: 0;
|
|
17
|
+
background: none;
|
|
18
|
+
color: inherit;
|
|
19
|
+
|
|
20
|
+
&:dir(rtl) { inset: auto auto var(--spacing-gutter) var(--spacing-gutter); }
|
|
21
|
+
&:empty { display: none; }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
@layer components {
|
|
2
|
+
/* lc-tooltip lifts the content into the top layer while it shows. Without
|
|
3
|
+
JavaScript or the Popover API it shows in place, over its neighbours. */
|
|
2
4
|
.lc-tooltip__content {
|
|
3
5
|
position: absolute;
|
|
4
6
|
inset-block-end: calc(100% + var(--spacing-related));
|
|
@@ -6,7 +8,9 @@
|
|
|
6
8
|
z-index: var(--lc-z-popover);
|
|
7
9
|
inline-size: max-content;
|
|
8
10
|
max-inline-size: min(16rem, calc(100vw - 2rem));
|
|
11
|
+
overflow: visible;
|
|
9
12
|
padding: var(--spacing-tight) var(--spacing-related);
|
|
13
|
+
border: 0;
|
|
10
14
|
border-radius: var(--radius-md);
|
|
11
15
|
background: var(--foreground);
|
|
12
16
|
color: var(--background);
|
|
@@ -17,31 +21,28 @@
|
|
|
17
21
|
pointer-events: none;
|
|
18
22
|
opacity: 0;
|
|
19
23
|
visibility: hidden;
|
|
20
|
-
translate: -50% 0
|
|
24
|
+
translate: -50% 0;
|
|
25
|
+
|
|
26
|
+
/* allow-discrete keeps a lifted tooltip in the top layer while it fades out. */
|
|
21
27
|
transition:
|
|
22
28
|
opacity 120ms var(--lc-ease-out),
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
visibility 120ms,
|
|
30
|
+
display 120ms allow-discrete,
|
|
31
|
+
overlay 120ms allow-discrete;
|
|
25
32
|
|
|
26
|
-
&:dir(rtl) { translate: 50% 0
|
|
33
|
+
&:dir(rtl) { translate: 50% 0; }
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
.lc-tooltip[data-side="bottom"] > .lc-tooltip__content {
|
|
30
37
|
inset-block: calc(100% + var(--spacing-related)) auto;
|
|
31
|
-
translate: -50% -0.125rem;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
.lc-tooltip:is(:hover, :has(:focus-visible)) > .lc-tooltip__content {
|
|
40
|
+
.lc-tooltip:is([data-open], :hover, :has(:focus-visible)) > .lc-tooltip__content {
|
|
35
41
|
opacity: 1;
|
|
36
42
|
visibility: visible;
|
|
37
|
-
translate: -50% 0;
|
|
38
43
|
transition-delay: 300ms;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
.lc-tooltip:is(:hover, :has(:focus-visible)) > .lc-tooltip__content:dir(rtl) {
|
|
42
|
-
translate: 50% 0;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
46
|
.lc-tooltip[data-dismissed] > .lc-tooltip__content {
|
|
46
47
|
opacity: 0;
|
|
47
48
|
visibility: hidden;
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
text-box: trim-both cap alphabetic; /* browser-compat: enhancement */
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
/* Inline code inside running text. base.css applies it to every <code>; prose
|
|
34
|
+
/* Inline code inside running text. base.css applies it to every <code>; prose.css
|
|
35
35
|
applies it inside .lc-prose. */
|
|
36
36
|
@utility lc-inline-code {
|
|
37
37
|
font-family: var(--font-mono);
|
|
@@ -49,9 +49,3 @@
|
|
|
49
49
|
content: none;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
@layer utilities {
|
|
54
|
-
.lc-prose :where(code):not(:where(pre code)) {
|
|
55
|
-
@apply lc-inline-code;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -58,7 +58,7 @@ module Lostcoords::Ui
|
|
|
58
58
|
"font-sans text-caption font-medium whitespace-nowrap"
|
|
59
59
|
|
|
60
60
|
BADGE_TONES = {
|
|
61
|
-
neutral: "bg-
|
|
61
|
+
neutral: "bg-tile text-muted-foreground",
|
|
62
62
|
success: "bg-success-soft text-success",
|
|
63
63
|
warning: "bg-warning-soft text-warning",
|
|
64
64
|
destructive: "bg-destructive-soft text-destructive",
|
|
@@ -86,7 +86,7 @@ module Lostcoords::Ui
|
|
|
86
86
|
shape = lc_option!(:shape, shape, ICON_BADGE_SHAPES.keys)
|
|
87
87
|
tone = lc_option!(:tone, tone, BADGE_TONES.keys)
|
|
88
88
|
lc_tw(ICON_BADGE_BASE, ICON_BADGE_SIZES.fetch(size), ICON_BADGE_SHAPES.fetch(shape),
|
|
89
|
-
(tone == :neutral ? "bg-
|
|
89
|
+
(tone == :neutral ? "bg-tile text-foreground" : BADGE_TONES.fetch(tone)), extra)
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
def lc_avatar_class(size: :md, ink: false, linked: false, extra: nil)
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
module Lostcoords::Ui
|
|
2
2
|
module ButtonHelper
|
|
3
|
+
# Disabled controls drop their variant colours for a faint tint and dim ink, so they read as unavailable on any surface.
|
|
4
|
+
BUTTON_DISABLED =
|
|
5
|
+
"disabled:cursor-not-allowed disabled:shadow-none " \
|
|
6
|
+
"disabled:[--lc-button-bg:color-mix(in_oklab,var(--foreground)_10%,transparent)] " \
|
|
7
|
+
"disabled:text-[color-mix(in_oklab,var(--foreground)_38%,transparent)]"
|
|
8
|
+
|
|
3
9
|
BUTTON_BASE =
|
|
4
10
|
"inline-flex items-center justify-center gap-related rounded-pill font-sans " \
|
|
5
11
|
"whitespace-nowrap no-underline cursor-pointer " \
|
|
6
12
|
"transition-[background-color,border-color,color,box-shadow,transform,translate,scale,filter] duration-(--lc-motion-control) ease-(--lc-ease-out) " \
|
|
7
|
-
"motion-safe:hover:-translate-y-px motion-safe:active:translate-y-px active:shadow-none
|
|
13
|
+
"motion-safe:not-disabled:hover:-translate-y-px motion-safe:not-disabled:active:translate-y-px active:shadow-none #{BUTTON_DISABLED} " \
|
|
8
14
|
"[&_svg]:block [&_svg:not([class*='size-'])]:size-[1.1em] [&_svg]:shrink-0"
|
|
9
15
|
|
|
10
16
|
# Height comes from the shared control sizes; an icon trims the padding on its side.
|
|
@@ -15,15 +21,15 @@ module Lostcoords::Ui
|
|
|
15
21
|
}.freeze
|
|
16
22
|
|
|
17
23
|
BUTTON_HOVER =
|
|
18
|
-
"hover:bg-[color-mix(in_oklab,var(--lc-button-bg,var(--secondary))_94%,var(--lc-button-ink,var(--foreground)))] " \
|
|
19
|
-
"active:bg-[color-mix(in_oklab,var(--lc-button-bg,var(--secondary))_94%,var(--lc-button-ink,var(--foreground)))]"
|
|
24
|
+
"not-disabled:hover:bg-[color-mix(in_oklab,var(--lc-button-bg,var(--secondary))_94%,var(--lc-button-ink,var(--foreground)))] " \
|
|
25
|
+
"not-disabled:active:bg-[color-mix(in_oklab,var(--lc-button-bg,var(--secondary))_94%,var(--lc-button-ink,var(--foreground)))]"
|
|
20
26
|
|
|
21
27
|
BUTTON_VARIANTS = {
|
|
22
28
|
primary: "border border-transparent [--lc-button-bg:var(--primary)] [--lc-button-ink:var(--primary-foreground)] bg-(--lc-button-bg) text-(--lc-button-ink) #{BUTTON_HOVER}",
|
|
23
29
|
secondary: "border border-transparent [--lc-button-bg:var(--secondary)] [--lc-button-ink:var(--secondary-foreground)] bg-(--lc-button-bg) text-(--lc-button-ink) #{BUTTON_HOVER}",
|
|
24
30
|
outline: "border border-border bg-transparent text-(--lc-button-ink,var(--foreground)) #{BUTTON_HOVER}",
|
|
25
31
|
glass: "[--lc-button-bg:var(--_lc-glass-fill)] text-(--lc-button-ink,var(--foreground)) #{BUTTON_HOVER}",
|
|
26
|
-
ghost: "border border-transparent bg-transparent text-muted-foreground hover:text-(--lc-button-ink,var(--foreground)) #{BUTTON_HOVER}",
|
|
32
|
+
ghost: "border border-transparent bg-transparent text-muted-foreground not-disabled:hover:text-(--lc-button-ink,var(--foreground)) #{BUTTON_HOVER}",
|
|
27
33
|
destructive: "border border-transparent [--lc-button-bg:color-mix(in_oklab,var(--destructive)_60%,black)] [--lc-button-ink:#fff] bg-(--lc-button-bg) text-(--lc-button-ink) #{BUTTON_HOVER}"
|
|
28
34
|
}.freeze
|
|
29
35
|
|
|
@@ -31,8 +37,8 @@ module Lostcoords::Ui
|
|
|
31
37
|
"lc-press inline-grid place-items-center shrink-0 border-0 bg-transparent " \
|
|
32
38
|
"text-muted-foreground cursor-pointer no-underline " \
|
|
33
39
|
"transition-[background-color,color,transform,scale] duration-(--lc-motion-quick) ease-(--lc-ease-out) " \
|
|
34
|
-
"hover:text-(--lc-button-ink,var(--foreground)) #{BUTTON_HOVER} " \
|
|
35
|
-
"
|
|
40
|
+
"not-disabled:hover:text-(--lc-button-ink,var(--foreground)) #{BUTTON_HOVER} " \
|
|
41
|
+
"#{BUTTON_DISABLED} " \
|
|
36
42
|
"[&_svg]:block [&_svg]:shrink-0"
|
|
37
43
|
|
|
38
44
|
ICON_BUTTON_SHAPES = {
|