staffa 0.5.0 → 0.6.1
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 +6 -0
- package/dist/components/button.js +1 -2
- package/dist/components/checkbox.js +1 -1
- package/dist/components/field.js +0 -1
- package/dist/components/tabs.js +0 -1
- package/dist/components/toast.d.ts +2 -2
- package/dist/components/toast.js +42 -16
- package/dist/staffa.esm.js +1 -1
- package/dist/theme.js +10 -3
- package/package.json +5 -4
- package/skill/Attributes.md +10 -0
- package/skill/AutocompleteOptions.md +37 -0
- package/skill/BoxOptions.md +33 -0
- package/skill/ButtonChooserOptions.md +37 -0
- package/skill/ButtonGroupOptions.md +23 -0
- package/skill/ButtonOptions.md +57 -0
- package/skill/CheckboxOptions.md +27 -0
- package/skill/ContentOptions.md +16 -0
- package/skill/DialogOptions.md +70 -0
- package/skill/FieldOptions.md +63 -0
- package/skill/FloatingMenuOptions.md +21 -0
- package/skill/FormOptions.md +31 -0
- package/skill/MainOptions.md +91 -0
- package/skill/MenuItem.md +52 -0
- package/skill/MenuOptions.md +25 -0
- package/skill/SKILL.md +548 -0
- package/skill/SelectOptions.md +21 -0
- package/skill/Slot.md +13 -0
- package/skill/Tab.md +33 -0
- package/skill/TabsOptions.md +28 -0
- package/skill/TextareaOptions.md +51 -0
- package/skill/TextlineOptions.md +45 -0
- package/skill/TextlineType.md +18 -0
- package/skill/ToastOptions.md +40 -0
- package/skill/TooltipOptions.md +22 -0
- package/skill/addTooltip.md +25 -0
- package/skill/alert.md +17 -0
- package/skill/autocomplete.md +22 -0
- package/skill/box.md +25 -0
- package/skill/button.md +29 -0
- package/skill/buttonChooser.md +23 -0
- package/skill/buttonGroup.md +20 -0
- package/skill/checkbox.md +17 -0
- package/skill/confirm.md +17 -0
- package/skill/dialog.md +28 -0
- package/skill/form.md +28 -0
- package/skill/getDarkMode.md +12 -0
- package/skill/main.md +33 -0
- package/skill/menuButton.md +27 -0
- package/skill/prompt.md +19 -0
- package/skill/select.md +17 -0
- package/skill/showFloatingMenu.md +22 -0
- package/skill/tabs.md +19 -0
- package/skill/textarea.md +16 -0
- package/skill/textline.md +19 -0
- package/skill/toast.md +20 -0
- package/src/components/button.ts +1 -2
- package/src/components/checkbox.ts +1 -1
- package/src/components/field.ts +1 -2
- package/src/components/tabs.ts +1 -2
- package/src/components/toast.ts +48 -18
- package/src/theme.ts +10 -0
package/src/components/toast.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface ToastOptions {
|
|
|
13
13
|
*/
|
|
14
14
|
type?: "success" | "danger" | "warning" | "neutral"
|
|
15
15
|
/**
|
|
16
|
-
* Auto-dismiss delay in milliseconds. Defaults to `
|
|
16
|
+
* Auto-dismiss delay in milliseconds. Defaults to `6000`.
|
|
17
17
|
* Pass `0` to make the toast persistent until dismissed manually.
|
|
18
18
|
*/
|
|
19
19
|
duration?: number;
|
|
@@ -37,7 +37,7 @@ A.insertGlobalCss({
|
|
|
37
37
|
"&":
|
|
38
38
|
"display:flex align-items:flex-start gap:$2 " +
|
|
39
39
|
"padding: $3; border: 1px solid $s-border; r:$s-radius box-shadow:$s-shadow " +
|
|
40
|
-
"pointer-events:auto",
|
|
40
|
+
"pointer-events:auto position:relative overflow:hidden",
|
|
41
41
|
".s-toast-body": "display:flex flex-direction:column gap:$1 flex:1 min-width:0",
|
|
42
42
|
".s-toast-title": "font-weight:700 line-height:1.3",
|
|
43
43
|
".s-toast-msg": "font-size:0.9em fg:$s-fg-muted line-height:1.4",
|
|
@@ -46,6 +46,7 @@ A.insertGlobalCss({
|
|
|
46
46
|
"padding: 0 0.15em; r:4px flex-shrink:0 align-self:flex-start",
|
|
47
47
|
".s-toast-close:hover": "fg:$s-fg",
|
|
48
48
|
".s-toast-close:focus-visible": "outline:none box-shadow: 0 0 0 3px $s-focus; fg:$s-fg",
|
|
49
|
+
".s-toast-progress": "position:absolute bottom:0 left:0 right:0 height:2px background:$s-accent width:100%",
|
|
49
50
|
},
|
|
50
51
|
});
|
|
51
52
|
|
|
@@ -54,15 +55,47 @@ let toastCount = 0;
|
|
|
54
55
|
const toasts = A.proxy({} as Record<number, ToastEntry>);
|
|
55
56
|
|
|
56
57
|
mountPortal(() => {
|
|
57
|
-
//
|
|
58
|
-
|
|
58
|
+
// In initial peek is here to NOT subscribe to changes once `toasts` first becomes non empty,
|
|
59
|
+
// leaving the container in the DOM forever after. (So as not to cut of hide animations.)
|
|
60
|
+
if (A.peek(() => A.isEmpty(toasts)) && A.isEmpty(toasts)) return;
|
|
59
61
|
A("div.s-toasts aria-live=polite aria-atomic=false", () => {
|
|
60
62
|
A.onEach(toasts, (entry) => {
|
|
61
|
-
const { opts } = entry;
|
|
63
|
+
const { opts, id } = entry;
|
|
62
64
|
const role = opts.type === "danger" || opts.type === "warning" ? "alert" : "status";
|
|
63
65
|
const surface = opts.type ?? "neutral";
|
|
66
|
+
const duration = opts.duration ?? 6000;
|
|
67
|
+
|
|
68
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
69
|
+
let progressEl: HTMLElement | null = null;
|
|
70
|
+
|
|
71
|
+
const startCountdown = () => {
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
if (progressEl) {
|
|
74
|
+
progressEl.style.transition = "none";
|
|
75
|
+
progressEl.style.width = "100%";
|
|
76
|
+
progressEl.offsetWidth; // force reflow so transition triggers
|
|
77
|
+
progressEl.style.transition = `width ${duration}ms linear`;
|
|
78
|
+
progressEl.style.width = "0%";
|
|
79
|
+
}
|
|
80
|
+
timer = setTimeout(() => dismiss(id), duration);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const stopCountdown = () => {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
timer = undefined;
|
|
86
|
+
if (progressEl) {
|
|
87
|
+
progressEl.style.transition = "none";
|
|
88
|
+
progressEl.style.width = "100%";
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
A.clean(() => clearTimeout(timer));
|
|
64
93
|
|
|
65
94
|
A(`div.s-toast.s-s.${surface} role=${role}`, "create=", grow, "destroy=", shrink, opts.attrs, () => {
|
|
95
|
+
if (duration > 0) {
|
|
96
|
+
A("mouseenter=", stopCountdown);
|
|
97
|
+
A("mouseleave=", startCountdown);
|
|
98
|
+
}
|
|
66
99
|
A("div.s-toast-body", () => {
|
|
67
100
|
A(() => {
|
|
68
101
|
if (opts.title != null) A("div.s-toast-title", () => drawSlot(opts.title));
|
|
@@ -73,10 +106,17 @@ mountPortal(() => {
|
|
|
73
106
|
if (opts.dismissible === false) return;
|
|
74
107
|
A("button.s-toast-close type=button aria-label=Dismiss", () => {
|
|
75
108
|
A("#×");
|
|
76
|
-
A("click=", () => dismiss(
|
|
109
|
+
A("click=", () => dismiss(id));
|
|
77
110
|
});
|
|
78
111
|
});
|
|
112
|
+
if (duration > 0) {
|
|
113
|
+
progressEl = A("div.s-toast-progress") as HTMLElement;
|
|
114
|
+
}
|
|
79
115
|
});
|
|
116
|
+
|
|
117
|
+
if (duration > 0) {
|
|
118
|
+
requestAnimationFrame(startCountdown);
|
|
119
|
+
}
|
|
80
120
|
});
|
|
81
121
|
});
|
|
82
122
|
});
|
|
@@ -87,7 +127,7 @@ function dismiss(id: number): void {
|
|
|
87
127
|
|
|
88
128
|
/**
|
|
89
129
|
* Show a toast notification. Returns a `dismiss()` function to remove it
|
|
90
|
-
* programmatically. Auto-dismisses after `duration` ms (default
|
|
130
|
+
* programmatically. Auto-dismisses after `duration` ms (default 6 000).
|
|
91
131
|
*
|
|
92
132
|
* @example
|
|
93
133
|
* ```ts
|
|
@@ -100,15 +140,5 @@ function dismiss(id: number): void {
|
|
|
100
140
|
export function toast(opts: ToastOptions): () => void {
|
|
101
141
|
const id = ++toastCount;
|
|
102
142
|
toasts[id] = { id, opts };
|
|
103
|
-
|
|
104
|
-
const duration = opts.duration ?? 4000;
|
|
105
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
106
|
-
if (duration > 0) {
|
|
107
|
-
timer = setTimeout(() => dismiss(id), duration);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return () => {
|
|
111
|
-
if (timer != null) clearTimeout(timer);
|
|
112
|
-
dismiss(id);
|
|
113
|
-
};
|
|
143
|
+
return () => dismiss(id);
|
|
114
144
|
}
|
package/src/theme.ts
CHANGED
|
@@ -332,6 +332,16 @@ if (typeof document !== "undefined" && typeof requestAnimationFrame === "functio
|
|
|
332
332
|
requestAnimationFrame(() => requestAnimationFrame(() => root.classList.remove("s-preload")));
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
+
// ── Disabled region ───────────────────────────────────────────────────────────
|
|
336
|
+
// aria-disabled="true" on any container dims it and blocks pointer events on
|
|
337
|
+
// it and all descendants, matching the per-element disabled look. Keyboard
|
|
338
|
+
// access to focusable descendants is unaffected — add the `inert` attribute too
|
|
339
|
+
// if you need that.
|
|
340
|
+
A.insertGlobalCss({
|
|
341
|
+
":disabled, [aria-disabled=true]": "opacity:0.45 filter:saturate(0.6) user-select:none",
|
|
342
|
+
":disabled, [aria-disabled=true], :disabled *, [aria-disabled=true] *": "pointer-events:none cursor:not-allowed",
|
|
343
|
+
});
|
|
344
|
+
|
|
335
345
|
// ── Flow content: vertical rhythm & light typography ─────────────────────────
|
|
336
346
|
// Sensible block defaults for *any* content — your own UI just as much as
|
|
337
347
|
// markdown-rendered HTML. The rhythm: strip the browser's block margins, then
|