@signal9/era-ui 2.0.0 → 2.2.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/dist/ai/stick-to-bottom.svelte.d.ts +38 -0
- package/dist/ai/stick-to-bottom.svelte.js +133 -0
- package/dist/era-ui.css +1 -1
- package/dist/generated-docs/llms-full.txt +60 -48
- package/dist/generated-docs/llms.txt +1 -1
- package/dist/generated-docs/manifest.json +20 -8
- package/dist/generated-docs/spacing.md +3 -0
- package/dist/generated-docs/step.md +51 -0
- package/dist/os/motion.d.ts +1 -19
- package/dist/os/motion.js +3 -44
- package/dist/os/taskbar.svelte +6 -12
- package/dist/styles/index.css +26 -0
- package/dist/ui/command-bar/command-bar.svelte +10 -5
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/step/context.d.ts +8 -0
- package/dist/ui/step/context.js +8 -0
- package/dist/ui/step/index.d.ts +5 -0
- package/dist/ui/step/index.js +4 -0
- package/dist/ui/step/step-content.svelte +55 -0
- package/dist/ui/step/step-content.svelte.d.ts +5 -0
- package/dist/ui/step/step-root.svelte +31 -0
- package/dist/ui/step/step-root.svelte.d.ts +9 -0
- package/dist/ui/step/step-section.svelte +23 -0
- package/dist/ui/step/step-section.svelte.d.ts +10 -0
- package/dist/ui/step/step-summary.svelte +55 -0
- package/dist/ui/step/step-summary.svelte.d.ts +5 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/motion.d.ts +19 -0
- package/dist/utils/motion.js +44 -0
- package/package.json +1 -1
- package/dist/generated-docs/quickstart.md +0 -47
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"entries": [
|
|
3
3
|
{
|
|
4
|
-
"slug": "
|
|
5
|
-
"title": "
|
|
6
|
-
"summary": "
|
|
7
|
-
"tokenEstimate":
|
|
8
|
-
"sections": [
|
|
9
|
-
|
|
10
|
-
],
|
|
11
|
-
"file": "quickstart.md"
|
|
4
|
+
"slug": "spacing",
|
|
5
|
+
"title": "Spacing",
|
|
6
|
+
"summary": "The sp-derived tier ladder — heights, insets, radii, concentricity.",
|
|
7
|
+
"tokenEstimate": 20,
|
|
8
|
+
"sections": [],
|
|
9
|
+
"file": "spacing.md"
|
|
12
10
|
},
|
|
13
11
|
{
|
|
14
12
|
"slug": "surfaces",
|
|
@@ -345,6 +343,20 @@
|
|
|
345
343
|
"sections": [],
|
|
346
344
|
"file": "command-bar.md"
|
|
347
345
|
},
|
|
346
|
+
{
|
|
347
|
+
"slug": "step",
|
|
348
|
+
"title": "Step",
|
|
349
|
+
"summary": "Expandable inline step — tool calls, thinking, agent actions.",
|
|
350
|
+
"tokenEstimate": 313,
|
|
351
|
+
"sections": [
|
|
352
|
+
"Import",
|
|
353
|
+
"Step.Root",
|
|
354
|
+
"Step.Summary",
|
|
355
|
+
"Step.Content",
|
|
356
|
+
"Step.Section"
|
|
357
|
+
],
|
|
358
|
+
"file": "step.md"
|
|
359
|
+
},
|
|
348
360
|
{
|
|
349
361
|
"slug": "context-menu",
|
|
350
362
|
"title": "Context Menu",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Step
|
|
2
|
+
|
|
3
|
+
Expandable inline step — tool calls, thinking, agent actions.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Step } from "$lib/ui/step";
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Composed from sub-components — compose explicitly at the call site:
|
|
12
|
+
|
|
13
|
+
- `Step.Root`
|
|
14
|
+
- `Step.Summary`
|
|
15
|
+
- `Step.Content`
|
|
16
|
+
- `Step.Section`
|
|
17
|
+
|
|
18
|
+
## Step.Root
|
|
19
|
+
|
|
20
|
+
Inherits all props from `Collapsible.RootProps`.
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Notes |
|
|
23
|
+
|------|------|---------|-------|
|
|
24
|
+
| `status?` | `StepStatus` | `'done'` | Drives the summary's icon and shimmer; exposed as data-status. |
|
|
25
|
+
| `ref?` | `(forwarded)` | `null` | bindable |
|
|
26
|
+
| `open?` | `(forwarded)` | `false` | bindable |
|
|
27
|
+
|
|
28
|
+
## Step.Summary
|
|
29
|
+
|
|
30
|
+
Inherits all props from `PartProps<Collapsible.TriggerProps>`.
|
|
31
|
+
|
|
32
|
+
| Prop | Type | Default | Notes |
|
|
33
|
+
|------|------|---------|-------|
|
|
34
|
+
| `ref?` | `(forwarded)` | `null` | bindable |
|
|
35
|
+
|
|
36
|
+
## Step.Content
|
|
37
|
+
|
|
38
|
+
Inherits all props from `PartProps<Collapsible.ContentProps>`.
|
|
39
|
+
|
|
40
|
+
| Prop | Type | Default | Notes |
|
|
41
|
+
|------|------|---------|-------|
|
|
42
|
+
| `ref?` | `(forwarded)` | `null` | bindable |
|
|
43
|
+
|
|
44
|
+
## Step.Section
|
|
45
|
+
|
|
46
|
+
Inherits all props from `HTMLAttributes<HTMLDivElement>`.
|
|
47
|
+
|
|
48
|
+
| Prop | Type | Default | Notes |
|
|
49
|
+
|------|------|---------|-------|
|
|
50
|
+
| `label?` | `string` | — | Tiny uppercase section header — INPUT, OUTPUT, … |
|
|
51
|
+
| `children?` | `Snippet` | — | — |
|
package/dist/os/motion.d.ts
CHANGED
|
@@ -1,19 +1 @@
|
|
|
1
|
-
|
|
2
|
-
type EraFlyParams = {
|
|
3
|
-
x?: number | string;
|
|
4
|
-
y?: number | string;
|
|
5
|
-
opacity?: number;
|
|
6
|
-
delay?: number;
|
|
7
|
-
duration?: number;
|
|
8
|
-
easing?: EasingFunction;
|
|
9
|
-
};
|
|
10
|
-
/**
|
|
11
|
-
* Fly transition that reads Era's motion tokens from the rendered element.
|
|
12
|
-
*
|
|
13
|
-
* Svelte transition durations are JavaScript parameters, so hard-coded numbers
|
|
14
|
-
* bypass the runtime `data-motion` axis. Reading `--era-duration` here keeps OS
|
|
15
|
-
* overlays in sync with docs settings and library consumers that set motion on
|
|
16
|
-
* an ancestor.
|
|
17
|
-
*/
|
|
18
|
-
export declare function eraFly(node: Element, { x, y, opacity, delay, duration, easing }?: EraFlyParams): TransitionConfig;
|
|
19
|
-
export {};
|
|
1
|
+
export { eraFly } from '../utils/motion.js';
|
package/dist/os/motion.js
CHANGED
|
@@ -1,44 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
function parseDuration(value) {
|
|
6
|
-
const match = value.trim().match(/^(-?\d*\.?\d+)(ms|s)$/i);
|
|
7
|
-
if (!match)
|
|
8
|
-
return undefined;
|
|
9
|
-
return Math.max(0, Number(match[1]) * durationUnits[match[2].toLowerCase()]);
|
|
10
|
-
}
|
|
11
|
-
function eraDuration(node, fallback) {
|
|
12
|
-
const token = getComputedStyle(node).getPropertyValue('--era-duration');
|
|
13
|
-
return parseDuration(token) ?? fallback;
|
|
14
|
-
}
|
|
15
|
-
function offset(value, progress) {
|
|
16
|
-
return typeof value === 'number' ? `${value * progress}px` : `calc(${value} * ${progress})`;
|
|
17
|
-
}
|
|
18
|
-
function eraEase(node) {
|
|
19
|
-
return getComputedStyle(node).getPropertyValue('--era-ease').trim() || undefined;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Fly transition that reads Era's motion tokens from the rendered element.
|
|
23
|
-
*
|
|
24
|
-
* Svelte transition durations are JavaScript parameters, so hard-coded numbers
|
|
25
|
-
* bypass the runtime `data-motion` axis. Reading `--era-duration` here keeps OS
|
|
26
|
-
* overlays in sync with docs settings and library consumers that set motion on
|
|
27
|
-
* an ancestor.
|
|
28
|
-
*/
|
|
29
|
-
export function eraFly(node, { x = 0, y = 0, opacity = 0, delay = 0, duration = 150, easing } = {}) {
|
|
30
|
-
const style = getComputedStyle(node);
|
|
31
|
-
const targetOpacity = Number(style.opacity);
|
|
32
|
-
const transform = style.transform === 'none' ? '' : style.transform;
|
|
33
|
-
const cssEase = eraEase(node);
|
|
34
|
-
return {
|
|
35
|
-
delay,
|
|
36
|
-
duration: eraDuration(node, duration),
|
|
37
|
-
easing,
|
|
38
|
-
css: (t, u) => `
|
|
39
|
-
transform: ${transform} translate(${offset(x, u)}, ${offset(y, u)});
|
|
40
|
-
opacity: ${targetOpacity - (targetOpacity - opacity) * u};
|
|
41
|
-
${cssEase ? `animation-timing-function: ${cssEase};` : ''}
|
|
42
|
-
`
|
|
43
|
-
};
|
|
44
|
-
}
|
|
1
|
+
// Motion helpers moved to utils (they're layer-agnostic — the ui/ layer needs
|
|
2
|
+
// them too). Re-exported here so os-internal imports keep working.
|
|
3
|
+
export { eraFly } from '../utils/motion.js';
|
package/dist/os/taskbar.svelte
CHANGED
|
@@ -76,20 +76,14 @@
|
|
|
76
76
|
};
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
// Live clock (minute resolution is plenty; tick often enough to feel current).
|
|
80
|
-
let now = $state(new Date());
|
|
81
|
-
$effect(() => {
|
|
82
|
-
const t = setInterval(() => (now = new Date()), 10_000);
|
|
83
|
-
return () => clearInterval(t);
|
|
84
|
-
});
|
|
85
|
-
const time = $derived(now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
|
|
86
|
-
|
|
87
79
|
// The command bar owns everything the old spotlight overlay did, and more:
|
|
88
80
|
// every registered app launches from the root view, `windows` drills into
|
|
89
81
|
// the open windows (focus/restore — the recovery path when the window tabs
|
|
90
82
|
// are hidden), and `workspace` drills into numbered workspaces + `new`.
|
|
91
|
-
// Consumer `commands` append after the built-ins
|
|
92
|
-
|
|
83
|
+
// Consumer `commands` append after the built-ins (merged in a thin outer
|
|
84
|
+
// derived, so a consumer-commands change doesn't rebuild the wm closures
|
|
85
|
+
// and vice-versa).
|
|
86
|
+
const builtinItems = $derived.by((): CommandBarItem[] => {
|
|
93
87
|
const apps: CommandBarItem[] = Object.values(wm.apps).map((app) => ({
|
|
94
88
|
value: `app:${app.id}`,
|
|
95
89
|
label: app.title,
|
|
@@ -133,8 +127,9 @@
|
|
|
133
127
|
}
|
|
134
128
|
]
|
|
135
129
|
};
|
|
136
|
-
return [...apps, windows, workspaces
|
|
130
|
+
return [...apps, windows, workspaces];
|
|
137
131
|
});
|
|
132
|
+
const commandItems = $derived([...builtinItems, ...commands]);
|
|
138
133
|
</script>
|
|
139
134
|
|
|
140
135
|
<!-- Flush: three edges touch the shell; the exposed OUTER corners are the two
|
|
@@ -221,7 +216,6 @@
|
|
|
221
216
|
<!-- System tray -->
|
|
222
217
|
<div class="flex shrink-0 items-center gap-(--era-gap)">
|
|
223
218
|
{@render end?.()}
|
|
224
|
-
<span class="px-(--era-inset-sm) text-body text-muted tabular-nums">{time}</span>
|
|
225
219
|
<div class="relative">
|
|
226
220
|
<Button icon size="default" aria-label="Notifications" onclick={onNotifications}>
|
|
227
221
|
<Bell class="size-(--era-h-xs)" />
|
package/dist/styles/index.css
CHANGED
|
@@ -56,6 +56,32 @@
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/* In-progress text shimmer — a bright sweep through muted text (the standard
|
|
60
|
+
* "working…" label treatment in streaming UIs). Timing derives from the motion
|
|
61
|
+
* axis (10× --era-duration ≈ 1.5s at normal, 2.6s at extra); at instant the
|
|
62
|
+
* duration is 0s and the sweep freezes on its base frame — plain muted text —
|
|
63
|
+
* so reduced-motion and data-motion="instant" both kill it for free. */
|
|
64
|
+
@utility era-shimmer {
|
|
65
|
+
background: linear-gradient(
|
|
66
|
+
110deg,
|
|
67
|
+
var(--color-muted) 0% 43%,
|
|
68
|
+
var(--color-bright) 50%,
|
|
69
|
+
var(--color-muted) 57% 100%
|
|
70
|
+
);
|
|
71
|
+
background-size: 200% 100%;
|
|
72
|
+
background-clip: text;
|
|
73
|
+
-webkit-text-fill-color: transparent;
|
|
74
|
+
animation: era-shimmer calc(var(--era-duration) * 10) cubic-bezier(0.7, 0, 1, 0.4) infinite;
|
|
75
|
+
}
|
|
76
|
+
@keyframes era-shimmer {
|
|
77
|
+
0% {
|
|
78
|
+
background-position: 100% 0;
|
|
79
|
+
}
|
|
80
|
+
100% {
|
|
81
|
+
background-position: -100% 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
59
85
|
/* Register color tokens as Tailwind theme values for autocomplete and bare utility usage */
|
|
60
86
|
@theme inline {
|
|
61
87
|
/* Density-derived body size: `text-body` replaces text-[length:var(--era-text)] */
|
|
@@ -61,6 +61,12 @@
|
|
|
61
61
|
});
|
|
62
62
|
const selected = $derived(filtered[selectedIndex]);
|
|
63
63
|
const open = $derived(focused || value !== '');
|
|
64
|
+
const hotkeyList = $derived(hotkey ? (Array.isArray(hotkey) ? hotkey : [hotkey]) : []);
|
|
65
|
+
// The focus hotkeys double as close keys while the bar is focused (a bound
|
|
66
|
+
// key is never part of a command — it toggles). Only single-character
|
|
67
|
+
// bindings qualify; chords/mod combos can't collide with typing. Escape
|
|
68
|
+
// always closes.
|
|
69
|
+
const closeKeys = $derived(new Set(['Escape', ...hotkeyList.filter((b) => b.length === 1)]));
|
|
64
70
|
|
|
65
71
|
// zsh-style ghost completion: the selected item's remaining characters laid
|
|
66
72
|
// under the caret (or the view placeholder while empty).
|
|
@@ -118,7 +124,8 @@
|
|
|
118
124
|
e.preventDefault();
|
|
119
125
|
complete(selected);
|
|
120
126
|
}
|
|
121
|
-
} else if (e.key
|
|
127
|
+
} else if (closeKeys.has(e.key)) {
|
|
128
|
+
e.preventDefault();
|
|
122
129
|
e.stopPropagation();
|
|
123
130
|
reset();
|
|
124
131
|
input?.blur();
|
|
@@ -136,10 +143,8 @@
|
|
|
136
143
|
// acts (it blurs the field); every other binding is ignored so typing a
|
|
137
144
|
// backtick in a text input never steals the caret.
|
|
138
145
|
const bindings = $derived.by((): KeyBindingMap => {
|
|
139
|
-
if (!hotkey) return {};
|
|
140
|
-
const list = Array.isArray(hotkey) ? hotkey : [hotkey];
|
|
141
146
|
return Object.fromEntries(
|
|
142
|
-
|
|
147
|
+
hotkeyList.map((b) => [
|
|
143
148
|
b,
|
|
144
149
|
(e: KeyboardEvent) => {
|
|
145
150
|
if (isTextEntryFocused()) {
|
|
@@ -164,7 +169,7 @@
|
|
|
164
169
|
dropdown is a flat sheet anchored under it — no border, shadow or radius,
|
|
165
170
|
just the elevated surface, exactly like the shell it mirrors. -->
|
|
166
171
|
<div class={cn('relative flex h-(--era-h-md) w-full min-w-0 items-center font-mono', className)}>
|
|
167
|
-
<ChevronRight class="mx-(--era-gap) size-(--era-h-xs) shrink-0 text-
|
|
172
|
+
<ChevronRight class="mx-(--era-gap) size-(--era-h-xs) shrink-0 text-fg" />
|
|
168
173
|
|
|
169
174
|
<!-- Completed segments: the locked prefix. Backspace (empty input) pops. -->
|
|
170
175
|
{#each stack as seg (seg.value)}
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * as Combobox from './combobox/index.js';
|
|
|
11
11
|
export * as Collapsible from './collapsible/index.js';
|
|
12
12
|
export * as Command from './command/index.js';
|
|
13
13
|
export * from './command-bar/index.js';
|
|
14
|
+
export * as Step from './step/index.js';
|
|
14
15
|
export * as ContextMenu from './context-menu/index.js';
|
|
15
16
|
export * as DateField from './date-field/index.js';
|
|
16
17
|
export * as DatePicker from './date-picker/index.js';
|
package/dist/ui/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export * as Combobox from './combobox/index.js';
|
|
|
11
11
|
export * as Collapsible from './collapsible/index.js';
|
|
12
12
|
export * as Command from './command/index.js';
|
|
13
13
|
export * from './command-bar/index.js';
|
|
14
|
+
export * as Step from './step/index.js';
|
|
14
15
|
export * as ContextMenu from './context-menu/index.js';
|
|
15
16
|
export * as DateField from './date-field/index.js';
|
|
16
17
|
export * as DatePicker from './date-picker/index.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The lifecycle of a step: not started, working, finished, failed. */
|
|
2
|
+
export type StepStatus = 'pending' | 'running' | 'done' | 'error';
|
|
3
|
+
type StepContext = {
|
|
4
|
+
readonly status: StepStatus;
|
|
5
|
+
};
|
|
6
|
+
export declare function setStepContext(ctx: StepContext): void;
|
|
7
|
+
export declare function getStepContext(): StepContext;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as Root } from './step-root.svelte';
|
|
2
|
+
export { default as Summary } from './step-summary.svelte';
|
|
3
|
+
export { default as Content } from './step-content.svelte';
|
|
4
|
+
export { default as Section } from './step-section.svelte';
|
|
5
|
+
export type { StepStatus } from './context.js';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Collapsible } from 'bits-ui';
|
|
3
|
+
import { cn, type PartProps } from '../../utils/index.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
children,
|
|
8
|
+
class: className,
|
|
9
|
+
...restProps
|
|
10
|
+
}: PartProps<Collapsible.ContentProps> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<!-- Height animates on the outer (padding-free) element via bits' measured
|
|
14
|
+
content-height var — padding lives on the inner well so the animation
|
|
15
|
+
never fights box metrics. The well is recessed (shadow-well): payload the
|
|
16
|
+
step produced, not raised chrome. -->
|
|
17
|
+
<Collapsible.Content bind:ref class="era-step-content overflow-hidden" {...restProps}>
|
|
18
|
+
<div
|
|
19
|
+
class={cn(
|
|
20
|
+
'mt-(--era-gap) mb-(--era-gap) flex min-w-0 flex-col gap-(--era-pad-sm) rounded-(--era-rd-md) bg-(--era-surface-bg) p-(--era-pad-sm) shadow-(--era-shadow-well)',
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
>
|
|
24
|
+
{@render children?.()}
|
|
25
|
+
</div>
|
|
26
|
+
</Collapsible.Content>
|
|
27
|
+
|
|
28
|
+
<style>
|
|
29
|
+
:global(.era-step-content[data-state='open']) {
|
|
30
|
+
animation: era-step-expand var(--era-duration) var(--era-ease);
|
|
31
|
+
}
|
|
32
|
+
:global(.era-step-content[data-state='closed']) {
|
|
33
|
+
animation: era-step-collapse var(--era-duration) var(--era-ease);
|
|
34
|
+
}
|
|
35
|
+
@keyframes -global-era-step-expand {
|
|
36
|
+
from {
|
|
37
|
+
height: 0;
|
|
38
|
+
opacity: 0;
|
|
39
|
+
}
|
|
40
|
+
to {
|
|
41
|
+
height: var(--bits-collapsible-content-height);
|
|
42
|
+
opacity: 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
@keyframes -global-era-step-collapse {
|
|
46
|
+
from {
|
|
47
|
+
height: var(--bits-collapsible-content-height);
|
|
48
|
+
opacity: 1;
|
|
49
|
+
}
|
|
50
|
+
to {
|
|
51
|
+
height: 0;
|
|
52
|
+
opacity: 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
</style>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Collapsible } from 'bits-ui';
|
|
2
|
+
import { type PartProps } from '../../utils/index.js';
|
|
3
|
+
declare const StepContent: import("svelte").Component<PartProps<Collapsible.ContentProps>, {}, "ref">;
|
|
4
|
+
type StepContent = ReturnType<typeof StepContent>;
|
|
5
|
+
export default StepContent;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Collapsible } from 'bits-ui';
|
|
3
|
+
import { cn } from '../../utils/index.js';
|
|
4
|
+
import { setStepContext, type StepStatus } from './context.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
ref = $bindable(null),
|
|
8
|
+
open = $bindable(false),
|
|
9
|
+
status = 'done',
|
|
10
|
+
class: className,
|
|
11
|
+
...restProps
|
|
12
|
+
}: Collapsible.RootProps & {
|
|
13
|
+
/** Drives the summary's icon and shimmer; exposed as data-status. */
|
|
14
|
+
status?: StepStatus;
|
|
15
|
+
} = $props();
|
|
16
|
+
|
|
17
|
+
// Shared with Summary via context (a getter, so status stays reactive).
|
|
18
|
+
setStepContext({
|
|
19
|
+
get status() {
|
|
20
|
+
return status;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<Collapsible.Root
|
|
26
|
+
bind:ref
|
|
27
|
+
bind:open
|
|
28
|
+
data-status={status}
|
|
29
|
+
class={cn('group/step flex w-full min-w-0 flex-col', className)}
|
|
30
|
+
{...restProps}
|
|
31
|
+
/>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Collapsible } from 'bits-ui';
|
|
2
|
+
import { type StepStatus } from './context.js';
|
|
3
|
+
type $$ComponentProps = Collapsible.RootProps & {
|
|
4
|
+
/** Drives the summary's icon and shimmer; exposed as data-status. */
|
|
5
|
+
status?: StepStatus;
|
|
6
|
+
};
|
|
7
|
+
declare const StepRoot: import("svelte").Component<$$ComponentProps, {}, "ref" | "open">;
|
|
8
|
+
type StepRoot = ReturnType<typeof StepRoot>;
|
|
9
|
+
export default StepRoot;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { cn } from '../../utils/index.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
label,
|
|
8
|
+
children,
|
|
9
|
+
class: className,
|
|
10
|
+
...restProps
|
|
11
|
+
}: HTMLAttributes<HTMLDivElement> & {
|
|
12
|
+
/** Tiny uppercase section header — INPUT, OUTPUT, … */
|
|
13
|
+
label?: string;
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
} = $props();
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<div class={cn('flex min-w-0 flex-col gap-(--era-gap)', className)} {...restProps}>
|
|
19
|
+
{#if label}
|
|
20
|
+
<span class="text-[0.625rem] leading-none tracking-[0.15em] text-muted uppercase">{label}</span>
|
|
21
|
+
{/if}
|
|
22
|
+
{@render children?.()}
|
|
23
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
|
|
4
|
+
/** Tiny uppercase section header — INPUT, OUTPUT, … */
|
|
5
|
+
label?: string;
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
};
|
|
8
|
+
declare const StepSection: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type StepSection = ReturnType<typeof StepSection>;
|
|
10
|
+
export default StepSection;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Collapsible } from 'bits-ui';
|
|
3
|
+
import LoaderCircle from '@lucide/svelte/icons/loader-circle';
|
|
4
|
+
import CircleCheck from '@lucide/svelte/icons/circle-check';
|
|
5
|
+
import CircleX from '@lucide/svelte/icons/circle-x';
|
|
6
|
+
import CircleDashed from '@lucide/svelte/icons/circle-dashed';
|
|
7
|
+
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
|
8
|
+
import { cn, type PartProps } from '../../utils/index.js';
|
|
9
|
+
import { getStepContext } from './context.js';
|
|
10
|
+
|
|
11
|
+
let {
|
|
12
|
+
ref = $bindable(null),
|
|
13
|
+
children,
|
|
14
|
+
class: className,
|
|
15
|
+
...restProps
|
|
16
|
+
}: PartProps<Collapsible.TriggerProps> = $props();
|
|
17
|
+
|
|
18
|
+
const step = getStepContext();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<!-- The collapsed line: status icon, label (shimmering while running — the
|
|
22
|
+
shimmer freezes to plain muted text at instant motion), rotating chevron.
|
|
23
|
+
Quiet by design — a step reads as an aside in the conversation, not a
|
|
24
|
+
control; it brightens on hover to reveal it's expandable. The spinner is
|
|
25
|
+
a progress indicator, not decoration, so it spins at every motion level. -->
|
|
26
|
+
<Collapsible.Trigger
|
|
27
|
+
bind:ref
|
|
28
|
+
class={cn(
|
|
29
|
+
'flex h-(--era-h-md) w-full min-w-0 cursor-pointer era-interactive items-center gap-(--era-gap) rounded-(--era-rd-md) px-(--era-inset-md) text-body text-muted transition-colors duration-(--era-duration) ease-(--era-ease) hover:text-fg',
|
|
30
|
+
className
|
|
31
|
+
)}
|
|
32
|
+
{...restProps}
|
|
33
|
+
>
|
|
34
|
+
{#if step.status === 'running'}
|
|
35
|
+
<LoaderCircle class="size-(--era-h-xs) shrink-0 animate-spin" aria-hidden="true" />
|
|
36
|
+
{:else if step.status === 'done'}
|
|
37
|
+
<CircleCheck class="size-(--era-h-xs) shrink-0 text-success" aria-hidden="true" />
|
|
38
|
+
{:else if step.status === 'error'}
|
|
39
|
+
<CircleX class="size-(--era-h-xs) shrink-0 text-destructive" aria-hidden="true" />
|
|
40
|
+
{:else}
|
|
41
|
+
<CircleDashed class="size-(--era-h-xs) shrink-0" aria-hidden="true" />
|
|
42
|
+
{/if}
|
|
43
|
+
<span
|
|
44
|
+
class={cn(
|
|
45
|
+
'flex min-w-0 flex-1 items-center gap-(--era-gap) truncate text-left',
|
|
46
|
+
step.status === 'running' && 'era-shimmer'
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
{@render children?.()}
|
|
50
|
+
</span>
|
|
51
|
+
<ChevronDown
|
|
52
|
+
class="size-(--era-h-xs) shrink-0 transition-transform duration-(--era-duration) ease-(--era-ease) group-data-[state=open]/step:rotate-180"
|
|
53
|
+
aria-hidden="true"
|
|
54
|
+
/>
|
|
55
|
+
</Collapsible.Trigger>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Collapsible } from 'bits-ui';
|
|
2
|
+
import { type PartProps } from '../../utils/index.js';
|
|
3
|
+
declare const StepSummary: import("svelte").Component<PartProps<Collapsible.TriggerProps>, {}, "ref">;
|
|
4
|
+
type StepSummary = ReturnType<typeof StepSummary>;
|
|
5
|
+
export default StepSummary;
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -14,3 +14,4 @@ export type PartProps<T> = WithoutChildrenOrChild<T> & {
|
|
|
14
14
|
children?: Snippet;
|
|
15
15
|
};
|
|
16
16
|
export { hotkeys, keys, parseKeybinding, matchKeyBindingPress, createKeybindingsHandler, formatKeybinding, isApplePlatform, modKey, type KeyBindingMap, type KeyBindingPress, type KeyBindingOptions } from './hotkeys.js';
|
|
17
|
+
export { eraFly } from './motion.js';
|
package/dist/utils/index.js
CHANGED
|
@@ -23,3 +23,4 @@ export function cn(...inputs) {
|
|
|
23
23
|
/** `tv` preconfigured with this library's tailwind-merge settings. */
|
|
24
24
|
export const tv = createTV({ twMergeConfig });
|
|
25
25
|
export { hotkeys, keys, parseKeybinding, matchKeyBindingPress, createKeybindingsHandler, formatKeybinding, isApplePlatform, modKey } from './hotkeys.js';
|
|
26
|
+
export { eraFly } from './motion.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { EasingFunction, TransitionConfig } from 'svelte/transition';
|
|
2
|
+
type EraFlyParams = {
|
|
3
|
+
x?: number | string;
|
|
4
|
+
y?: number | string;
|
|
5
|
+
opacity?: number;
|
|
6
|
+
delay?: number;
|
|
7
|
+
duration?: number;
|
|
8
|
+
easing?: EasingFunction;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Fly transition that reads Era's motion tokens from the rendered element.
|
|
12
|
+
*
|
|
13
|
+
* Svelte transition durations are JavaScript parameters, so hard-coded numbers
|
|
14
|
+
* bypass the runtime `data-motion` axis. Reading `--era-duration` here keeps OS
|
|
15
|
+
* overlays in sync with docs settings and library consumers that set motion on
|
|
16
|
+
* an ancestor.
|
|
17
|
+
*/
|
|
18
|
+
export declare function eraFly(node: Element, { x, y, opacity, delay, duration, easing }?: EraFlyParams): TransitionConfig;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const durationUnits = {
|
|
2
|
+
ms: 1,
|
|
3
|
+
s: 1000
|
|
4
|
+
};
|
|
5
|
+
function parseDuration(value) {
|
|
6
|
+
const match = value.trim().match(/^(-?\d*\.?\d+)(ms|s)$/i);
|
|
7
|
+
if (!match)
|
|
8
|
+
return undefined;
|
|
9
|
+
return Math.max(0, Number(match[1]) * durationUnits[match[2].toLowerCase()]);
|
|
10
|
+
}
|
|
11
|
+
function eraDuration(node, fallback) {
|
|
12
|
+
const token = getComputedStyle(node).getPropertyValue('--era-duration');
|
|
13
|
+
return parseDuration(token) ?? fallback;
|
|
14
|
+
}
|
|
15
|
+
function offset(value, progress) {
|
|
16
|
+
return typeof value === 'number' ? `${value * progress}px` : `calc(${value} * ${progress})`;
|
|
17
|
+
}
|
|
18
|
+
function eraEase(node) {
|
|
19
|
+
return getComputedStyle(node).getPropertyValue('--era-ease').trim() || undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Fly transition that reads Era's motion tokens from the rendered element.
|
|
23
|
+
*
|
|
24
|
+
* Svelte transition durations are JavaScript parameters, so hard-coded numbers
|
|
25
|
+
* bypass the runtime `data-motion` axis. Reading `--era-duration` here keeps OS
|
|
26
|
+
* overlays in sync with docs settings and library consumers that set motion on
|
|
27
|
+
* an ancestor.
|
|
28
|
+
*/
|
|
29
|
+
export function eraFly(node, { x = 0, y = 0, opacity = 0, delay = 0, duration = 150, easing } = {}) {
|
|
30
|
+
const style = getComputedStyle(node);
|
|
31
|
+
const targetOpacity = Number(style.opacity);
|
|
32
|
+
const transform = style.transform === 'none' ? '' : style.transform;
|
|
33
|
+
const cssEase = eraEase(node);
|
|
34
|
+
return {
|
|
35
|
+
delay,
|
|
36
|
+
duration: eraDuration(node, duration),
|
|
37
|
+
easing,
|
|
38
|
+
css: (t, u) => `
|
|
39
|
+
transform: ${transform} translate(${offset(x, u)}, ${offset(y, u)});
|
|
40
|
+
opacity: ${targetOpacity - (targetOpacity - opacity) * u};
|
|
41
|
+
${cssEase ? `animation-timing-function: ${cssEase};` : ''}
|
|
42
|
+
`
|
|
43
|
+
};
|
|
44
|
+
}
|