@akonwi/mica 0.3.0 → 0.5.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.
- package/README.md +2 -2
- package/drawer.js +5 -4
- package/mica.css +38 -38
- package/package.json +2 -2
- package/toast.js +11 -7
package/README.md
CHANGED
|
@@ -12,8 +12,8 @@ there's nothing there but HTML and a stylesheet.
|
|
|
12
12
|
<m-vstack gap="lg">
|
|
13
13
|
<h1>That's it</h1>
|
|
14
14
|
<m-hstack gap="sm">
|
|
15
|
-
<button
|
|
16
|
-
<button
|
|
15
|
+
<button data-variant="primary">Save</button>
|
|
16
|
+
<button data-variant="ghost">Cancel</button>
|
|
17
17
|
</m-hstack>
|
|
18
18
|
</m-vstack>
|
|
19
19
|
```
|
package/drawer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/* mica/drawer.js — Tier 2: swipe-to-dismiss for dialog
|
|
2
|
-
* sheets on small screens.
|
|
1
|
+
/* mica/drawer.js — Tier 2: swipe-to-dismiss for dialog[data-drawer]
|
|
2
|
+
* bottom sheets on small screens.
|
|
3
3
|
*
|
|
4
4
|
* Enhances working markup, never replaces it: without this module the
|
|
5
5
|
* drawer opens/closes via buttons and Esc; with it, the mobile sheet
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
const SHEET = matchMedia("(max-width: 40rem)");
|
|
17
17
|
const CLOSE_DISTANCE = 1 / 3; // of sheet height
|
|
18
18
|
const CLOSE_VELOCITY = 0.6; // px/ms, downward flick
|
|
19
|
+
const DRAWER = "dialog[data-drawer]";
|
|
19
20
|
|
|
20
21
|
document.documentElement.setAttribute("data-drawer-gestures", "");
|
|
21
22
|
|
|
@@ -23,13 +24,13 @@ document.addEventListener("pointerdown", (down) => {
|
|
|
23
24
|
if (!SHEET.matches) return;
|
|
24
25
|
const dialog =
|
|
25
26
|
down.target instanceof Element &&
|
|
26
|
-
down.target.closest(
|
|
27
|
+
down.target.closest(`${DRAWER}[open]`);
|
|
27
28
|
if (!dialog) return;
|
|
28
29
|
if (down.target.closest("button, a, input, select, textarea")) return;
|
|
29
30
|
|
|
30
31
|
// start only from the header or the top (handle) strip
|
|
31
32
|
const rect = dialog.getBoundingClientRect();
|
|
32
|
-
const inHeader = down.target.closest(
|
|
33
|
+
const inHeader = down.target.closest(`${DRAWER} > header`);
|
|
33
34
|
if (!inHeader && down.clientY - rect.top > 32) return;
|
|
34
35
|
|
|
35
36
|
let dy = 0;
|
package/mica.css
CHANGED
|
@@ -268,8 +268,8 @@
|
|
|
268
268
|
|
|
269
269
|
/* ------------------------------------------------------------------ *
|
|
270
270
|
* Elements — Tier 1. Native elements styled directly; behavior is the
|
|
271
|
-
* browser's.
|
|
272
|
-
*
|
|
271
|
+
* browser's. Presentation APIs are attributes; user CSS still wins
|
|
272
|
+
* because these rules live in a layer.
|
|
273
273
|
* ------------------------------------------------------------------ */
|
|
274
274
|
@layer mica.elements {
|
|
275
275
|
/* --- button (and things dressed as buttons) ---------------------- */
|
|
@@ -298,8 +298,7 @@
|
|
|
298
298
|
background: var(--neutral-4);
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
|
|
302
|
-
&:where(.primary) {
|
|
301
|
+
&:where([data-variant="primary"]) {
|
|
303
302
|
background: var(--color-primary);
|
|
304
303
|
border-color: transparent;
|
|
305
304
|
color: var(--color-on-primary);
|
|
@@ -316,7 +315,7 @@
|
|
|
316
315
|
secondary button when primary is near-black */
|
|
317
316
|
}
|
|
318
317
|
|
|
319
|
-
&:where(
|
|
318
|
+
&:where([data-variant="danger"]) {
|
|
320
319
|
background: var(--color-danger);
|
|
321
320
|
border-color: transparent;
|
|
322
321
|
color: var(--color-on-danger);
|
|
@@ -333,9 +332,9 @@
|
|
|
333
332
|
}
|
|
334
333
|
|
|
335
334
|
/* deliberately zero rest affordance — a ghost is invisible until
|
|
336
|
-
hover/focus. use
|
|
337
|
-
lone action on a page. */
|
|
338
|
-
&:where(
|
|
335
|
+
hover/focus. use data-variant="ghost" for toolbars and icon rows,
|
|
336
|
+
not as the lone action on a page. */
|
|
337
|
+
&:where([data-variant="ghost"]) {
|
|
339
338
|
background: transparent;
|
|
340
339
|
border-color: transparent;
|
|
341
340
|
|
|
@@ -348,11 +347,11 @@
|
|
|
348
347
|
}
|
|
349
348
|
|
|
350
349
|
/* sizes (shadcn: h-8 px-3 / h-10 px-6, text-sm throughout) */
|
|
351
|
-
&:where(
|
|
350
|
+
&:where([data-size="small"]) {
|
|
352
351
|
block-size: var(--control-height-sm);
|
|
353
352
|
padding-inline: var(--space-sm);
|
|
354
353
|
}
|
|
355
|
-
&:where(
|
|
354
|
+
&:where([data-size="large"]) {
|
|
356
355
|
block-size: var(--control-height-lg);
|
|
357
356
|
padding-inline: var(--space-lg);
|
|
358
357
|
}
|
|
@@ -572,7 +571,7 @@
|
|
|
572
571
|
}
|
|
573
572
|
}
|
|
574
573
|
|
|
575
|
-
:where(input[type="checkbox"]:not(
|
|
574
|
+
:where(input[type="checkbox"]:not([role="switch"])) {
|
|
576
575
|
&:checked {
|
|
577
576
|
background-image: var(--check-glyph);
|
|
578
577
|
}
|
|
@@ -668,8 +667,8 @@
|
|
|
668
667
|
|
|
669
668
|
/* switch: a checkbox wearing a track; the thumb is a positioned
|
|
670
669
|
gradient (pseudo-elements don't render on inputs). square, per the
|
|
671
|
-
design language — a solid linear-gradient block */
|
|
672
|
-
:where(input[type="checkbox"]
|
|
670
|
+
design language — a solid linear-gradient block. */
|
|
671
|
+
:where(input[type="checkbox"][role="switch"]) {
|
|
673
672
|
inline-size: 2.25rem;
|
|
674
673
|
border-color: transparent;
|
|
675
674
|
border-radius: var(--radius-sm);
|
|
@@ -690,8 +689,8 @@
|
|
|
690
689
|
}
|
|
691
690
|
|
|
692
691
|
/* disabled + on: desaturate toward neutral, same recipe as disabled
|
|
693
|
-
|
|
694
|
-
block so it wins its background-color at equal specificity) */
|
|
692
|
+
data-variant="primary" buttons — inert, not just faded (placed after
|
|
693
|
+
the switch block so it wins its background-color at equal specificity) */
|
|
695
694
|
:where(input:is([type="checkbox"], [type="radio"])):disabled:is(
|
|
696
695
|
:checked,
|
|
697
696
|
:indeterminate
|
|
@@ -1081,8 +1080,8 @@
|
|
|
1081
1080
|
|
|
1082
1081
|
/* drawer (shadcn sheet anatomy): inline-end panel, fade + 2.5rem
|
|
1083
1082
|
nudge rather than a full slide; no section dividers; footer pinned
|
|
1084
|
-
to the bottom with stacked actions */
|
|
1085
|
-
:where(dialog
|
|
1083
|
+
to the bottom with stacked actions. */
|
|
1084
|
+
:where(dialog[data-drawer]) {
|
|
1086
1085
|
margin-inline: auto 0;
|
|
1087
1086
|
margin-block: 0;
|
|
1088
1087
|
/* full height WITHOUT an explicit block-size: the UA gives
|
|
@@ -1102,23 +1101,23 @@
|
|
|
1102
1101
|
scale: 1;
|
|
1103
1102
|
translate: 2.5rem 0;
|
|
1104
1103
|
}
|
|
1105
|
-
:where(dialog
|
|
1104
|
+
:where(dialog[data-drawer][open]) {
|
|
1106
1105
|
translate: 0 0;
|
|
1107
1106
|
}
|
|
1108
1107
|
/* unconditional (survives the exit, like the rest of composition) */
|
|
1109
|
-
:where(dialog
|
|
1108
|
+
:where(dialog[data-drawer]) > :where(:not(header, footer, button.close)) {
|
|
1110
1109
|
font-size: inherit; /* panel runs 12/relaxed throughout */
|
|
1111
1110
|
}
|
|
1112
1111
|
@starting-style {
|
|
1113
|
-
:where(dialog
|
|
1112
|
+
:where(dialog[data-drawer][open]) {
|
|
1114
1113
|
translate: 2.5rem 0;
|
|
1115
1114
|
}
|
|
1116
1115
|
}
|
|
1117
1116
|
|
|
1118
|
-
:where(dialog
|
|
1117
|
+
:where(dialog[data-drawer]) > :where(header) {
|
|
1119
1118
|
border-block-end: 0;
|
|
1120
1119
|
}
|
|
1121
|
-
:where(dialog
|
|
1120
|
+
:where(dialog[data-drawer]) > :where(footer) {
|
|
1122
1121
|
margin-block-start: auto;
|
|
1123
1122
|
flex-direction: column;
|
|
1124
1123
|
align-items: stretch;
|
|
@@ -1133,7 +1132,7 @@
|
|
|
1133
1132
|
nav bars). Explicit 100svh gapped behind the collapsed toolbar and
|
|
1134
1133
|
100dvh lagged in the top layer — don't reintroduce heights here. */
|
|
1135
1134
|
@media (max-width: 40rem) {
|
|
1136
|
-
:where(dialog
|
|
1135
|
+
:where(dialog[data-drawer]) {
|
|
1137
1136
|
inset-block: auto 0; /* release the UA's top anchor… */
|
|
1138
1137
|
margin-block: 0; /* …so height resolves from content */
|
|
1139
1138
|
block-size: auto; /* content height, like vaul */
|
|
@@ -1164,14 +1163,14 @@
|
|
|
1164
1163
|
translate: 0 100%;
|
|
1165
1164
|
opacity: 1;
|
|
1166
1165
|
}
|
|
1167
|
-
:where(dialog
|
|
1166
|
+
:where(dialog[data-drawer][open]) {
|
|
1168
1167
|
translate: 0 0;
|
|
1169
1168
|
box-shadow:
|
|
1170
1169
|
0 4rem 0 0 var(--color-surface-overlay),
|
|
1171
1170
|
0 0 0 100vmax oklch(0 0 0 / 0.4);
|
|
1172
1171
|
}
|
|
1173
1172
|
@starting-style {
|
|
1174
|
-
:where(dialog
|
|
1173
|
+
:where(dialog[data-drawer][open]) {
|
|
1175
1174
|
translate: 0 100%;
|
|
1176
1175
|
box-shadow:
|
|
1177
1176
|
0 4rem 0 0 var(--color-surface-overlay),
|
|
@@ -1180,14 +1179,15 @@
|
|
|
1180
1179
|
}
|
|
1181
1180
|
/* the box-shadow scrim replaces ::backdrop here; keep the native
|
|
1182
1181
|
backdrop clear so they don't double up or flash on the iOS cut. */
|
|
1183
|
-
:where(dialog
|
|
1182
|
+
:where(dialog[data-drawer][open])::backdrop {
|
|
1184
1183
|
background: oklch(0 0 0 / 0);
|
|
1185
1184
|
}
|
|
1186
1185
|
|
|
1187
1186
|
/* grab handle — only when drawer.js is loaded (an affordance
|
|
1188
1187
|
without its behavior would fake interactivity; TIERS.md).
|
|
1189
1188
|
the handle is the first flex item of the open sheet. */
|
|
1190
|
-
:where(:root[data-drawer-gestures])
|
|
1189
|
+
:where(:root[data-drawer-gestures])
|
|
1190
|
+
:where(dialog[data-drawer][open])::before {
|
|
1191
1191
|
content: "";
|
|
1192
1192
|
align-self: center;
|
|
1193
1193
|
flex-shrink: 0;
|
|
@@ -1197,7 +1197,8 @@
|
|
|
1197
1197
|
background: var(--color-border-strong);
|
|
1198
1198
|
border-radius: var(--radius-full);
|
|
1199
1199
|
}
|
|
1200
|
-
:where(:root[data-drawer-gestures])
|
|
1200
|
+
:where(:root[data-drawer-gestures])
|
|
1201
|
+
:where(dialog[data-drawer]) > :where(header) {
|
|
1201
1202
|
touch-action: none; /* the drag surface must own its touches */
|
|
1202
1203
|
}
|
|
1203
1204
|
}
|
|
@@ -1358,7 +1359,7 @@
|
|
|
1358
1359
|
color: var(--color-on-accent);
|
|
1359
1360
|
}
|
|
1360
1361
|
|
|
1361
|
-
|
|
1362
|
+
&:is([data-variant="danger"]) {
|
|
1362
1363
|
color: var(--color-danger-text);
|
|
1363
1364
|
|
|
1364
1365
|
&:hover:not(:disabled, [aria-disabled="true"]),
|
|
@@ -1503,14 +1504,13 @@
|
|
|
1503
1504
|
color: var(--color-text-muted);
|
|
1504
1505
|
}
|
|
1505
1506
|
|
|
1506
|
-
|
|
1507
|
-
&:where(.success) {
|
|
1507
|
+
&:where([data-variant="success"]) {
|
|
1508
1508
|
border-inline-start: 2px solid var(--color-success);
|
|
1509
1509
|
}
|
|
1510
|
-
&:where(
|
|
1510
|
+
&:where([data-variant="danger"]) {
|
|
1511
1511
|
border-inline-start: 2px solid var(--color-danger);
|
|
1512
1512
|
}
|
|
1513
|
-
&:where(
|
|
1513
|
+
&:where([data-variant="warning"]) {
|
|
1514
1514
|
border-inline-start: 2px solid var(--color-warn);
|
|
1515
1515
|
}
|
|
1516
1516
|
}
|
|
@@ -1871,8 +1871,8 @@
|
|
|
1871
1871
|
|
|
1872
1872
|
/* --- m-cover: fill the viewport, center the principal element ---- *
|
|
1873
1873
|
* The principal element (h1 by default, or mark one child with
|
|
1874
|
-
*
|
|
1875
|
-
* top, children after it pin to the bottom.
|
|
1874
|
+
* data-principal) is vertically centered; children before it pin to
|
|
1875
|
+
* the top, children after it pin to the bottom. */
|
|
1876
1876
|
m-cover {
|
|
1877
1877
|
display: flex;
|
|
1878
1878
|
flex-direction: column;
|
|
@@ -1888,9 +1888,9 @@
|
|
|
1888
1888
|
|
|
1889
1889
|
& > * { margin-block: 0; }
|
|
1890
1890
|
|
|
1891
|
-
& > :is(h1,
|
|
1891
|
+
& > :is(h1, [data-principal]) { margin-block: auto; }
|
|
1892
1892
|
|
|
1893
|
-
/*
|
|
1894
|
-
&:has(>
|
|
1893
|
+
/* An explicit marker wins over the h1 default. */
|
|
1894
|
+
&:has(> [data-principal]) > h1:not([data-principal]) { margin-block: 0; }
|
|
1895
1895
|
}
|
|
1896
1896
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akonwi/mica",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Custom elements. Native behavior. Nearly no JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"css",
|
|
@@ -55,4 +55,4 @@
|
|
|
55
55
|
"snapshot:check": "bun tools/snapshot.ts --check",
|
|
56
56
|
"snapshot:setup": "playwright install chromium webkit"
|
|
57
57
|
}
|
|
58
|
-
}
|
|
58
|
+
}
|
package/toast.js
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
*
|
|
7
7
|
* - stacking: open toasts stack upward; JS ships one number per toast
|
|
8
8
|
* (--m-toast-offset), CSS owns the geometry and the reflow motion
|
|
9
|
-
* - auto-dismiss: `duration` attribute in ms (default 5000,
|
|
10
|
-
* paused while hovered
|
|
9
|
+
* - auto-dismiss: `data-duration` attribute in ms (default 5000,
|
|
10
|
+
* "0" = sticky), paused while hovered
|
|
11
11
|
* - toast(title, { description, variant, duration }): spawns the same
|
|
12
|
-
* recipe markup
|
|
12
|
+
* recipe markup (`variant`: success, warning, or danger), shows it,
|
|
13
|
+
* and removes it after dismissal
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
const GAP = 8;
|
|
@@ -30,8 +31,10 @@ function restack() {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
function startTimer(el) {
|
|
33
|
-
const
|
|
34
|
-
|
|
34
|
+
const duration =
|
|
35
|
+
el.getAttribute("data-duration") ?? el.getAttribute("duration");
|
|
36
|
+
const ms = duration !== null
|
|
37
|
+
? Number(duration)
|
|
35
38
|
: DEFAULT_DURATION;
|
|
36
39
|
if (!ms) return;
|
|
37
40
|
timers.set(el, setTimeout(() => el.hidePopover(), ms));
|
|
@@ -74,10 +77,11 @@ document.addEventListener(
|
|
|
74
77
|
export function toast(title, { description = "", variant = "", duration } = {}) {
|
|
75
78
|
const el = document.createElement("div");
|
|
76
79
|
el.popover = "manual";
|
|
77
|
-
el.className =
|
|
80
|
+
el.className = "toast";
|
|
81
|
+
if (variant) el.setAttribute("data-variant", variant);
|
|
78
82
|
el.setAttribute("role", "status");
|
|
79
83
|
el.dataset.ephemeral = "";
|
|
80
|
-
if (duration !== undefined) el.setAttribute("duration", String(duration));
|
|
84
|
+
if (duration !== undefined) el.setAttribute("data-duration", String(duration));
|
|
81
85
|
|
|
82
86
|
const x = document.createElement("button");
|
|
83
87
|
x.className = "close";
|