@stnd/ui 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/Accordion.astro +52 -0
- package/AccordionItem.astro +65 -0
- package/Alert.svelte +59 -0
- package/AlertDialog.svelte +146 -0
- package/CHANGELOG.md +25 -0
- package/CfImage.astro +70 -0
- package/Combobox.svelte +458 -0
- package/ContextMenu.svelte +120 -0
- package/ContextMenuItem.svelte +64 -0
- package/ContextMenuItemCheckbox.svelte +105 -0
- package/ContextMenuLabel.svelte +18 -0
- package/ContextMenuSeparator.svelte +1 -0
- package/DialogManager.svelte +32 -0
- package/Dropdown.svelte +107 -0
- package/DropdownItem.svelte +87 -0
- package/DropdownLabel.svelte +11 -0
- package/DropdownSeparator.svelte +1 -0
- package/LauncherHint.astro +35 -0
- package/Pagination.astro +154 -0
- package/Panel.svelte +365 -0
- package/README.md +150 -0
- package/Scroller.astro +11 -0
- package/Scroller.svelte +44 -0
- package/TableOfContents.astro +217 -0
- package/Toast.astro +202 -0
- package/dialog.js +53 -0
- package/package.json +35 -0
- package/transitions.js +78 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* @component TableOfContents
|
|
4
|
+
* @description Subtle Notion-style TOC — small lines fixed to the right side
|
|
5
|
+
* that expand with heading labels on hover and highlight the current section.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import TableOfContents from "@stnd/ui/TableOfContents.astro";
|
|
9
|
+
* <TableOfContents {headings} />
|
|
10
|
+
*
|
|
11
|
+
* Extract headings from rendered HTML:
|
|
12
|
+
* const headings = [];
|
|
13
|
+
* const headingRegex = /<h([23])\s[^>]*id="([^"]*)"[^>]*>(.*?)<\/h\1>/gi;
|
|
14
|
+
* let m;
|
|
15
|
+
* while ((m = headingRegex.exec(html)) !== null) {
|
|
16
|
+
* headings.push({ depth: parseInt(m[1]), text: m[3].replace(/<[^>]+>/g, "").trim(), slug: m[2] });
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
interface Heading {
|
|
21
|
+
depth: number;
|
|
22
|
+
text: string;
|
|
23
|
+
slug: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface Props {
|
|
27
|
+
headings: Heading[];
|
|
28
|
+
/** Minimum headings to show the TOC. Default: 2 */
|
|
29
|
+
minHeadings?: number;
|
|
30
|
+
/** Breakpoint to hide on mobile. Default: 1100 */
|
|
31
|
+
breakpoint?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { headings, minHeadings = 2 } = Astro.props;
|
|
35
|
+
const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
tocHeadings.length >= minHeadings && (
|
|
40
|
+
<nav class="stnd-toc" aria-label="Table des matières">
|
|
41
|
+
{tocHeadings.map((h) => (
|
|
42
|
+
<a
|
|
43
|
+
href={`#${h.slug}`}
|
|
44
|
+
class="toc-item"
|
|
45
|
+
data-slug={h.slug}
|
|
46
|
+
data-depth={h.depth}
|
|
47
|
+
aria-label={h.text}
|
|
48
|
+
>
|
|
49
|
+
<span class="toc-label">{h.text}</span>
|
|
50
|
+
<span class="toc-bar" />
|
|
51
|
+
</a>
|
|
52
|
+
))}
|
|
53
|
+
</nav>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
<style>
|
|
58
|
+
.stnd-toc {
|
|
59
|
+
position: fixed;
|
|
60
|
+
right: var(--space);
|
|
61
|
+
top: 50%;
|
|
62
|
+
transform: translateY(-50%);
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
gap: var(--space-d8);
|
|
66
|
+
z-index: 10;
|
|
67
|
+
padding: var(--space-d2);
|
|
68
|
+
align-items: flex-end;
|
|
69
|
+
opacity: 0;
|
|
70
|
+
transition: all var(--transition);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.stnd-toc[data-visible],
|
|
74
|
+
.stnd-toc:hover {
|
|
75
|
+
opacity: 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.stnd-toc:hover {
|
|
79
|
+
gap: var(--space);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.toc-item {
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: var(--space-d4);
|
|
86
|
+
text-decoration: none;
|
|
87
|
+
cursor: pointer;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.toc-label {
|
|
91
|
+
font-size: var(--size-sm);
|
|
92
|
+
color: var(--color-foreground);
|
|
93
|
+
font-family: var(--font-interface);
|
|
94
|
+
font-weight: var(--font-weight-bold);
|
|
95
|
+
|
|
96
|
+
white-space: nowrap;
|
|
97
|
+
max-width: 0;
|
|
98
|
+
overflow: hidden;
|
|
99
|
+
text-overflow: ellipsis;
|
|
100
|
+
opacity: 0;
|
|
101
|
+
transition: all var(--transition-fast);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.toc-bar {
|
|
105
|
+
display: block;
|
|
106
|
+
height: var(--space-d6);
|
|
107
|
+
border-radius: var(--radius);
|
|
108
|
+
background: var(--color-subtle);
|
|
109
|
+
opacity: 0.35;
|
|
110
|
+
transition: all var(--transition);
|
|
111
|
+
flex-shrink: 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.toc-item[data-depth="2"] .toc-bar {
|
|
115
|
+
width: var(--space);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.toc-item[data-depth="3"] .toc-label {
|
|
119
|
+
margin-right: var(--space-d2);
|
|
120
|
+
}
|
|
121
|
+
.toc-item[data-depth="3"] .toc-bar {
|
|
122
|
+
width: var(--space-d2);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Hover: expand labels, brighten bars */
|
|
126
|
+
.stnd-toc:hover .toc-label {
|
|
127
|
+
max-width: 16em;
|
|
128
|
+
opacity: 0.6;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.stnd-toc:hover .toc-item:hover .toc-label {
|
|
132
|
+
opacity: 1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.stnd-toc:hover .toc-bar {
|
|
136
|
+
opacity: 1;
|
|
137
|
+
background: var(--color-muted);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.toc-item:hover .toc-bar {
|
|
141
|
+
opacity: 1 !important;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Active section */
|
|
145
|
+
.toc-item[data-active] .toc-bar {
|
|
146
|
+
opacity: 1;
|
|
147
|
+
background: var(--color-foreground);
|
|
148
|
+
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.toc-item[data-active] .toc-label {
|
|
152
|
+
opacity: 1 !important;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@media (max-width: 1100px) {
|
|
156
|
+
.stnd-toc {
|
|
157
|
+
display: none;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
</style>
|
|
161
|
+
|
|
162
|
+
<script>
|
|
163
|
+
document.addEventListener("astro:page-load", () => {
|
|
164
|
+
const toc = document.querySelector<HTMLElement>(".stnd-toc");
|
|
165
|
+
const tocLinks = document.querySelectorAll<HTMLElement>(".toc-item[data-slug]");
|
|
166
|
+
if (tocLinks.length === 0) return;
|
|
167
|
+
|
|
168
|
+
// Show on scroll or mouse move, hide after inactivity
|
|
169
|
+
let hideTimer: ReturnType<typeof setTimeout>;
|
|
170
|
+
const show = () => {
|
|
171
|
+
toc?.setAttribute("data-visible", "");
|
|
172
|
+
clearTimeout(hideTimer);
|
|
173
|
+
hideTimer = setTimeout(() => toc?.removeAttribute("data-visible"), 2500);
|
|
174
|
+
};
|
|
175
|
+
window.addEventListener("scroll", show, { passive: true });
|
|
176
|
+
window.addEventListener("mousemove", show, { passive: true });
|
|
177
|
+
toc?.addEventListener("mouseenter", () => {
|
|
178
|
+
clearTimeout(hideTimer);
|
|
179
|
+
toc.setAttribute("data-visible", "");
|
|
180
|
+
});
|
|
181
|
+
toc?.addEventListener("mouseleave", () => {
|
|
182
|
+
hideTimer = setTimeout(() => toc.removeAttribute("data-visible"), 2500);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const ids = [...tocLinks].map((a) => a.dataset.slug!);
|
|
186
|
+
const targets = ids
|
|
187
|
+
.map((id) => document.getElementById(id))
|
|
188
|
+
.filter(Boolean) as HTMLElement[];
|
|
189
|
+
|
|
190
|
+
if (targets.length === 0) return;
|
|
191
|
+
|
|
192
|
+
let current = ids[0];
|
|
193
|
+
|
|
194
|
+
const observer = new IntersectionObserver(
|
|
195
|
+
(entries) => {
|
|
196
|
+
const visible = entries
|
|
197
|
+
.filter((e) => e.isIntersecting)
|
|
198
|
+
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
|
|
199
|
+
|
|
200
|
+
if (visible.length > 0) {
|
|
201
|
+
current = visible[0].target.id;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
tocLinks.forEach((link) => {
|
|
205
|
+
if (link.dataset.slug === current) {
|
|
206
|
+
link.setAttribute("data-active", "");
|
|
207
|
+
} else {
|
|
208
|
+
link.removeAttribute("data-active");
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
{ rootMargin: "0px 0px -65% 0px", threshold: 0 },
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
targets.forEach((el) => observer.observe(el));
|
|
216
|
+
});
|
|
217
|
+
</script>
|
package/Toast.astro
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
// sToastSystem.astro - Zero-dependency notification system
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<div id="toast-container" aria-live="polite" aria-atomic="true"></div>
|
|
6
|
+
|
|
7
|
+
<style>
|
|
8
|
+
#toast-container {
|
|
9
|
+
position: fixed;
|
|
10
|
+
top: var(--space-2);
|
|
11
|
+
left: 50%;
|
|
12
|
+
transform: translateX(-50%);
|
|
13
|
+
z-index: 9999999;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
gap: var(--space-half);
|
|
17
|
+
pointer-events: none;
|
|
18
|
+
margin: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
:global(.toast-item) {
|
|
22
|
+
color: var(--color-foreground);
|
|
23
|
+
border-radius: var(--radius);
|
|
24
|
+
border: var(--border);
|
|
25
|
+
padding: var(--space-half) var(--space-half);
|
|
26
|
+
font-family: var(--font-interface);
|
|
27
|
+
font-size: var(--scale-d3);
|
|
28
|
+
pointer-events: auto;
|
|
29
|
+
box-shadow: var(--shadow-glow);
|
|
30
|
+
backdrop-filter: var(--filter-glass);
|
|
31
|
+
background: var(--color-glass);
|
|
32
|
+
margin: 0 auto;
|
|
33
|
+
|
|
34
|
+
/* Animation setup */
|
|
35
|
+
opacity: 0;
|
|
36
|
+
filter: var(--filter-glass);
|
|
37
|
+
transition: all var(--transition);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
:global(.toast-item.show) {
|
|
41
|
+
opacity: 1;
|
|
42
|
+
filter: blur(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
:global(.toast-item.error) {
|
|
46
|
+
border-color: color-mix(
|
|
47
|
+
in srgb,
|
|
48
|
+
var(--color-border) 75%,
|
|
49
|
+
var(--color-error)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
:global(.toast-item.success) {
|
|
53
|
+
border-color: color-mix(
|
|
54
|
+
in srgb,
|
|
55
|
+
var(--color-border) 75%,
|
|
56
|
+
var(--color-success)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
:global(.toast-item.info) {
|
|
60
|
+
border-color: color-mix(
|
|
61
|
+
in srgb,
|
|
62
|
+
var(--color-border) 75%,
|
|
63
|
+
var(--color-info)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
:global(.toast-item.warning) {
|
|
67
|
+
border-color: color-mix(
|
|
68
|
+
in srgb,
|
|
69
|
+
var(--color-border) 75,
|
|
70
|
+
var(--color-warning)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Mobile adjustments */
|
|
75
|
+
@media (max-width: 640px) {
|
|
76
|
+
#toast-container {
|
|
77
|
+
left: var(--space);
|
|
78
|
+
right: var(--space);
|
|
79
|
+
bottom: var(--space);
|
|
80
|
+
transform: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
:global(.toast-item) {
|
|
84
|
+
text-align: center;
|
|
85
|
+
min-width: auto;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
</style>
|
|
89
|
+
|
|
90
|
+
<script>
|
|
91
|
+
import { navigate } from "astro:transitions/client";
|
|
92
|
+
|
|
93
|
+
function initToastSystem() {
|
|
94
|
+
// Prevent duplicate listeners across re-renders
|
|
95
|
+
if ((window as any)._toastListenerAdded) return;
|
|
96
|
+
|
|
97
|
+
const container = document.getElementById("toast-container");
|
|
98
|
+
if (!container) return;
|
|
99
|
+
|
|
100
|
+
// Listen for custom 'toast' events (only add once)
|
|
101
|
+
window.addEventListener("toast", ((e: CustomEvent) => {
|
|
102
|
+
const { message, type = "default", duration = 4000, action } = e.detail;
|
|
103
|
+
|
|
104
|
+
// Create toast element
|
|
105
|
+
const toast = document.createElement("div");
|
|
106
|
+
toast.className = `toast-item ${type}`;
|
|
107
|
+
|
|
108
|
+
const messageSpan = document.createElement("span");
|
|
109
|
+
messageSpan.className = "toast-message";
|
|
110
|
+
messageSpan.textContent = message;
|
|
111
|
+
toast.appendChild(messageSpan);
|
|
112
|
+
|
|
113
|
+
// If an action was provided, render a button
|
|
114
|
+
if (action && action.label) {
|
|
115
|
+
const btn = document.createElement("button");
|
|
116
|
+
btn.className = "toast-action";
|
|
117
|
+
btn.type = "button";
|
|
118
|
+
btn.textContent = action.label;
|
|
119
|
+
btn.addEventListener("click", (ev) => {
|
|
120
|
+
ev.stopPropagation();
|
|
121
|
+
// Optionally close toast immediately
|
|
122
|
+
toast.classList.remove("show");
|
|
123
|
+
|
|
124
|
+
// Use declarative data-gate attributes so StandardGate handles it
|
|
125
|
+
if (action && action.view) {
|
|
126
|
+
try {
|
|
127
|
+
// If the view looks like a command (starts with ::), use data-gate-trigger
|
|
128
|
+
if (String(action.view).startsWith("::")) {
|
|
129
|
+
const tmp = document.createElement("button");
|
|
130
|
+
tmp.setAttribute("data-gate-trigger", action.view);
|
|
131
|
+
tmp.style.display = "none";
|
|
132
|
+
document.body.appendChild(tmp);
|
|
133
|
+
tmp.click();
|
|
134
|
+
tmp.remove();
|
|
135
|
+
} else {
|
|
136
|
+
// Otherwise treat as a prompt (initial query)
|
|
137
|
+
const tmp = document.createElement("button");
|
|
138
|
+
tmp.setAttribute("data-gate-prompt", action.view);
|
|
139
|
+
tmp.style.display = "none";
|
|
140
|
+
document.body.appendChild(tmp);
|
|
141
|
+
tmp.click();
|
|
142
|
+
tmp.remove();
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.warn("toast action view failed", err);
|
|
146
|
+
}
|
|
147
|
+
} else if (
|
|
148
|
+
action &&
|
|
149
|
+
action.onClick &&
|
|
150
|
+
typeof window[action.onClick] === "function"
|
|
151
|
+
) {
|
|
152
|
+
try {
|
|
153
|
+
window[action.onClick]();
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.warn("toast action failed", err);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
toast.appendChild(btn);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Add to DOM
|
|
163
|
+
container.appendChild(toast);
|
|
164
|
+
|
|
165
|
+
// Trigger animation (next frame)
|
|
166
|
+
requestAnimationFrame(() => {
|
|
167
|
+
toast.classList.add("show");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Remove after duration
|
|
171
|
+
setTimeout(() => {
|
|
172
|
+
toast.classList.remove("show");
|
|
173
|
+
toast.style.opacity = "0";
|
|
174
|
+
|
|
175
|
+
// Wait for CSS transition before removing from DOM
|
|
176
|
+
setTimeout(() => toast.remove(), 1000);
|
|
177
|
+
}, duration);
|
|
178
|
+
}) as EventListener);
|
|
179
|
+
|
|
180
|
+
(window as any)._toastListenerAdded = true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Initialize
|
|
184
|
+
initToastSystem();
|
|
185
|
+
|
|
186
|
+
// Global helpers for non-module scripts
|
|
187
|
+
const win = window as any;
|
|
188
|
+
|
|
189
|
+
win.toast = (
|
|
190
|
+
message: string,
|
|
191
|
+
type: string = "default",
|
|
192
|
+
duration: number = 2000,
|
|
193
|
+
action: { label?: string; view?: string; onClick?: string } | null = null,
|
|
194
|
+
) => {
|
|
195
|
+
const detail = { message, type, duration };
|
|
196
|
+
if (action) detail.action = action;
|
|
197
|
+
window.dispatchEvent(new CustomEvent("toast", { detail }));
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Re-initialize after Astro view transitions (but only add listener once)
|
|
201
|
+
document.addEventListener("astro:page-load", initToastSystem);
|
|
202
|
+
</script>
|
package/dialog.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { writable } from "svelte/store";
|
|
2
|
+
import { provide } from "@stnd/store";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} DialogOptions
|
|
6
|
+
* @property {string} [title]
|
|
7
|
+
* @property {string} [description]
|
|
8
|
+
* @property {string} [confirmLabel]
|
|
9
|
+
* @property {string} [cancelLabel]
|
|
10
|
+
* @property {'neutral' | 'danger'} [intent]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const dialogState = writable({
|
|
14
|
+
open: false,
|
|
15
|
+
title: "",
|
|
16
|
+
description: "",
|
|
17
|
+
confirmLabel: "Continue",
|
|
18
|
+
cancelLabel: "Cancel",
|
|
19
|
+
intent: "danger",
|
|
20
|
+
onconfirm: null,
|
|
21
|
+
oncancel: null,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Open a confirmation dialog
|
|
26
|
+
* @param {DialogOptions} options
|
|
27
|
+
* @returns {Promise<boolean>} Resolves true if confirmed, false if canceled
|
|
28
|
+
*/
|
|
29
|
+
export function confirm(options) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
dialogState.set({
|
|
32
|
+
open: true,
|
|
33
|
+
title: options.title || "Are you sure?",
|
|
34
|
+
description: options.description || "",
|
|
35
|
+
confirmLabel: options.confirmLabel || "Continue",
|
|
36
|
+
cancelLabel: options.cancelLabel || "Cancel",
|
|
37
|
+
intent: options.intent || "neutral",
|
|
38
|
+
onconfirm: () => {
|
|
39
|
+
resolve(true);
|
|
40
|
+
dialogState.update((s) => ({ ...s, open: false }));
|
|
41
|
+
},
|
|
42
|
+
oncancel: () => {
|
|
43
|
+
resolve(false);
|
|
44
|
+
dialogState.update((s) => ({ ...s, open: false }));
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Register the confirm function and dialogState in the runtime store so any
|
|
51
|
+
// module can trigger a dialog via inject("dialog").confirm(...) or
|
|
52
|
+
// subscribe to inject("dialog").state.
|
|
53
|
+
provide("dialog", { confirm, state: dialogState });
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stnd/ui",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"svelte": "./Combobox.svelte",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./transitions.js": "./transitions.js",
|
|
8
|
+
"./ui/*.astro": "./ui/*.astro",
|
|
9
|
+
"./ui/*.svelte": "./ui/*.svelte",
|
|
10
|
+
"./ui/*": {
|
|
11
|
+
"astro": "./ui/*.astro",
|
|
12
|
+
"svelte": "./ui/*.svelte",
|
|
13
|
+
"default": "./ui/*.astro"
|
|
14
|
+
},
|
|
15
|
+
"./*.astro": "./*.astro",
|
|
16
|
+
"./*.svelte": "./*.svelte",
|
|
17
|
+
"./*": {
|
|
18
|
+
"astro": "./*.astro",
|
|
19
|
+
"svelte": "./*.svelte",
|
|
20
|
+
"default": "./*.astro"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"astro": ">=5.0.0"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"wrangler": "^4.105.0",
|
|
31
|
+
"@stnd/icon": "0.2.0",
|
|
32
|
+
"@stnd/store": "0.5.0",
|
|
33
|
+
"@stnd/utils": "0.5.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/transitions.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard Garden Centralized Transitions
|
|
3
|
+
* Combines standard Svelte transitions with custom UI transitions (blur, scale)
|
|
4
|
+
* using unified default durations and easings.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { fade as svelteFade, slide as svelteSlide, fly as svelteFly } from "svelte/transition";
|
|
8
|
+
import { quintOut } from "svelte/easing";
|
|
9
|
+
|
|
10
|
+
// Standard default transition settings matching Standard UI tokens
|
|
11
|
+
const DEFAULT_DURATION = 250;
|
|
12
|
+
const DEFAULT_EASING = quintOut;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Standard Fade
|
|
16
|
+
*/
|
|
17
|
+
export function fade(node, { duration = DEFAULT_DURATION, easing = DEFAULT_EASING, ...opts } = {}) {
|
|
18
|
+
return svelteFade(node, { duration, easing, ...opts });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Standard Slide
|
|
23
|
+
*/
|
|
24
|
+
export function slide(node, { duration = DEFAULT_DURATION, easing = DEFAULT_EASING, ...opts } = {}) {
|
|
25
|
+
return svelteSlide(node, { duration, easing, ...opts });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Standard Fly
|
|
30
|
+
*/
|
|
31
|
+
export function fly(node, { duration = DEFAULT_DURATION, easing = DEFAULT_EASING, ...opts } = {}) {
|
|
32
|
+
return svelteFly(node, { duration, easing, ...opts });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fade + Blur transition
|
|
37
|
+
* Animates opacity and backdrop-filter blur together
|
|
38
|
+
*/
|
|
39
|
+
export function blurFade(node, { duration = DEFAULT_DURATION, maxBlur = 20, easing = DEFAULT_EASING } = {}) {
|
|
40
|
+
return {
|
|
41
|
+
duration,
|
|
42
|
+
easing,
|
|
43
|
+
css: (t) => `
|
|
44
|
+
opacity: ${t};
|
|
45
|
+
backdrop-filter: blur(${t * maxBlur}px);
|
|
46
|
+
`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Scale + Blur + Fade transition
|
|
52
|
+
* Combines scale, blur, and fade for modal-like elements
|
|
53
|
+
*/
|
|
54
|
+
export function scaleBlurFade(
|
|
55
|
+
node,
|
|
56
|
+
{ duration = DEFAULT_DURATION, maxBlur = 20, start = 0.95, easing = DEFAULT_EASING } = {},
|
|
57
|
+
) {
|
|
58
|
+
return {
|
|
59
|
+
duration,
|
|
60
|
+
easing,
|
|
61
|
+
css: (t) => `
|
|
62
|
+
opacity: ${t};
|
|
63
|
+
transform: scale(${start + (1 - start) * t});
|
|
64
|
+
backdrop-filter: blur(${t * maxBlur}px);
|
|
65
|
+
`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Blur transition (backdrop only, no fade)
|
|
71
|
+
*/
|
|
72
|
+
export function blur(node, { duration = DEFAULT_DURATION, maxBlur = 8, easing = DEFAULT_EASING } = {}) {
|
|
73
|
+
return {
|
|
74
|
+
duration,
|
|
75
|
+
easing,
|
|
76
|
+
css: (t) => `backdrop-filter: blur(${t * maxBlur}px);`,
|
|
77
|
+
};
|
|
78
|
+
}
|