refactor(toolkit): type source any with proper types
Genuinely type composable any usages (useStepper/useStorage/useForm/ createEventHook/useSorted/etc.) as proper generics/unknown; keep idiomatic any-function and overload-impl signatures with comments; skipped test -> .todo.
This commit is contained in:
@@ -40,7 +40,7 @@ const stateColor = computed(() => {
|
||||
case 'running': return 'bg-emerald-500';
|
||||
case 'paused': return 'bg-amber-500';
|
||||
case 'finished': return 'bg-sky-500';
|
||||
default: return 'bg-(--border-strong)';
|
||||
default: return 'bg-border-strong';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ const rates = [0.5, 1, 2] as const;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div
|
||||
v-if="!isSupported"
|
||||
class="rounded-xl border border-amber-500/30 bg-amber-500/10 p-4 text-sm text-amber-600 dark:text-amber-400"
|
||||
@@ -57,28 +57,28 @@ const rates = [0.5, 1, 2] as const;
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="flex h-28 items-center justify-center overflow-hidden rounded-xl border border-(--border) bg-(--bg-inset)">
|
||||
<div class="flex h-28 items-center justify-center overflow-hidden rounded-xl border border-border bg-bg-inset">
|
||||
<div
|
||||
ref="target"
|
||||
class="size-12 bg-(--accent) shadow-lg"
|
||||
class="size-12 bg-accent shadow-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3">
|
||||
<div class="demo-label">
|
||||
State
|
||||
</div>
|
||||
<div class="mt-1 flex items-center gap-2">
|
||||
<span class="inline-block size-2 rounded-full transition" :class="stateColor" />
|
||||
<span class="font-mono text-sm text-(--fg)">{{ playState }}</span>
|
||||
<span class="font-mono text-sm text-fg">{{ playState }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3">
|
||||
<div class="demo-label">
|
||||
Current time
|
||||
</div>
|
||||
<div class="mt-1 font-mono text-sm tabular-nums text-(--fg)">
|
||||
<div class="mt-1 font-mono text-sm tabular-nums text-fg">
|
||||
{{ elapsed }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,31 +86,31 @@ const rates = [0.5, 1, 2] as const;
|
||||
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<button
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn-primary"
|
||||
@click="play"
|
||||
>
|
||||
Play
|
||||
</button>
|
||||
<button
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn"
|
||||
@click="pause"
|
||||
>
|
||||
Pause
|
||||
</button>
|
||||
<button
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn"
|
||||
@click="reverse"
|
||||
>
|
||||
Reverse
|
||||
</button>
|
||||
<button
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn"
|
||||
@click="finish"
|
||||
>
|
||||
Finish
|
||||
</button>
|
||||
<button
|
||||
class="col-span-2 inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn col-span-2"
|
||||
@click="cancel"
|
||||
>
|
||||
Cancel
|
||||
@@ -118,7 +118,7 @@ const rates = [0.5, 1, 2] as const;
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-label">
|
||||
Playback rate
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@@ -127,8 +127,8 @@ const rates = [0.5, 1, 2] as const;
|
||||
:key="rate"
|
||||
class="flex-1 rounded-lg border px-3 py-1.5 text-sm font-medium tabular-nums transition active:scale-[0.98] cursor-pointer"
|
||||
:class="playbackRate === rate
|
||||
? 'border-transparent bg-(--accent) text-(--accent-fg)'
|
||||
: 'border-(--border) bg-(--bg-elevated) text-(--fg) hover:bg-(--bg-inset) hover:border-(--border-strong)'"
|
||||
? 'border-transparent bg-accent text-accent-fg'
|
||||
: 'border-border bg-bg-elevated text-fg hover:bg-bg-inset hover:border-border-strong'"
|
||||
@click="playbackRate = rate"
|
||||
>
|
||||
{{ rate }}×
|
||||
|
||||
@@ -37,9 +37,9 @@ function toggle() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-5 text-center">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card p-5 text-center">
|
||||
<div class="demo-label">
|
||||
Time remaining
|
||||
</div>
|
||||
<div
|
||||
@@ -48,22 +48,22 @@ function toggle() {
|
||||
? 'text-emerald-600 dark:text-emerald-400'
|
||||
: remaining <= 10 && remaining > 0
|
||||
? 'text-amber-600 dark:text-amber-400'
|
||||
: 'text-(--fg)'"
|
||||
: 'text-fg'"
|
||||
>
|
||||
{{ minutes }}:{{ seconds }}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 h-1.5 w-full overflow-hidden rounded-full bg-(--bg-inset)">
|
||||
<div class="mt-4 h-1.5 w-full overflow-hidden rounded-full bg-bg-inset">
|
||||
<div
|
||||
class="h-full rounded-full bg-(--accent) transition-[width] duration-300 ease-linear"
|
||||
class="h-full rounded-full bg-accent transition-[width] duration-300 ease-linear"
|
||||
:style="{ width: `${progress * 100}%` }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center justify-center gap-2 text-xs text-(--fg-subtle)">
|
||||
<div class="mt-3 flex items-center justify-center gap-2 text-xs text-fg-subtle">
|
||||
<span
|
||||
class="inline-block size-2 rounded-full transition"
|
||||
:class="isActive ? 'bg-emerald-500' : justFinished ? 'bg-sky-500' : 'bg-(--border-strong)'"
|
||||
:class="isActive ? 'bg-emerald-500' : justFinished ? 'bg-sky-500' : 'bg-border-strong'"
|
||||
/>
|
||||
{{ justFinished ? 'Completed' : isActive ? 'Counting down' : 'Paused' }}
|
||||
</div>
|
||||
@@ -73,7 +73,7 @@ function toggle() {
|
||||
<button
|
||||
v-for="preset in presets"
|
||||
:key="preset"
|
||||
class="rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium tabular-nums text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="rounded-lg border border-border bg-bg-elevated px-3 py-1.5 text-sm font-medium tabular-nums text-fg transition hover:bg-bg-inset hover:border-border-strong active:scale-[0.98] cursor-pointer"
|
||||
@click="setPreset(preset)"
|
||||
>
|
||||
{{ preset < 60 ? `${preset}s` : `${preset / 60}m` }}
|
||||
@@ -82,20 +82,20 @@ function toggle() {
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="flex-1 inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
class="demo-btn-primary flex-1 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
:disabled="remaining === 0 && isActive"
|
||||
@click="toggle"
|
||||
>
|
||||
{{ isActive ? 'Pause' : 'Resume' }}
|
||||
</button>
|
||||
<button
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn"
|
||||
@click="start()"
|
||||
>
|
||||
Restart
|
||||
</button>
|
||||
<button
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn"
|
||||
@click="stop"
|
||||
>
|
||||
Stop
|
||||
|
||||
@@ -27,46 +27,46 @@ const isValid = computed(() => formatted.value !== 'Invalid Date');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-md flex-col gap-4">
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-stack max-w-md">
|
||||
<div class="demo-card p-4">
|
||||
<div class="demo-label">
|
||||
Formatted output
|
||||
</div>
|
||||
<div
|
||||
class="mt-2 font-mono text-lg font-semibold tabular-nums"
|
||||
:class="isValid ? 'text-(--fg)' : 'text-red-600 dark:text-red-400'"
|
||||
:class="isValid ? 'text-fg' : 'text-red-600 dark:text-red-400'"
|
||||
>
|
||||
{{ formatted }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<label class="demo-label">
|
||||
Date input
|
||||
</label>
|
||||
<input
|
||||
v-model="date"
|
||||
type="datetime-local"
|
||||
class="w-full rounded-lg border border-(--border) bg-(--bg) px-3 py-2 text-sm text-(--fg) placeholder:text-(--fg-subtle) transition focus:border-(--accent) focus:outline-none focus:ring-2 focus:ring-(--ring)"
|
||||
class="demo-input"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<label class="demo-label">
|
||||
Format token string
|
||||
</label>
|
||||
<input
|
||||
v-model="format"
|
||||
type="text"
|
||||
spellcheck="false"
|
||||
class="w-full rounded-lg border border-(--border) bg-(--bg) px-3 py-2 font-mono text-sm text-(--fg) placeholder:text-(--fg-subtle) transition focus:border-(--accent) focus:outline-none focus:ring-2 focus:ring-(--ring)"
|
||||
class="demo-input font-mono"
|
||||
>
|
||||
<div class="flex flex-wrap gap-1.5 pt-1">
|
||||
<button
|
||||
v-for="f in formats"
|
||||
:key="f"
|
||||
class="rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 font-mono text-xs text-(--fg-muted) transition hover:bg-(--bg-elevated) hover:text-(--fg) active:scale-[0.98] cursor-pointer"
|
||||
:class="{ 'border-(--accent) text-(--accent-text)': format === f }"
|
||||
class="rounded-md border border-border bg-bg-inset px-2 py-0.5 font-mono text-xs text-fg-muted transition hover:bg-bg-elevated hover:text-fg active:scale-[0.98] cursor-pointer"
|
||||
:class="{ 'border-accent text-accent-text': format === f }"
|
||||
@click="format = f"
|
||||
>
|
||||
{{ f }}
|
||||
@@ -75,7 +75,7 @@ const isValid = computed(() => formatted.value !== 'Invalid Date');
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<label class="demo-label">
|
||||
Locale
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@@ -84,8 +84,8 @@ const isValid = computed(() => formatted.value !== 'Invalid Date');
|
||||
:key="loc.value"
|
||||
class="rounded-lg border px-3 py-1.5 text-sm font-medium transition active:scale-[0.98] cursor-pointer"
|
||||
:class="locale === loc.value
|
||||
? 'border-transparent bg-(--accent) text-(--accent-fg)'
|
||||
: 'border-(--border) bg-(--bg-elevated) text-(--fg) hover:bg-(--bg-inset) hover:border-(--border-strong)'"
|
||||
? 'border-transparent bg-accent text-accent-fg'
|
||||
: 'border-border bg-bg-elevated text-fg hover:bg-bg-inset hover:border-border-strong'"
|
||||
@click="locale = loc.value"
|
||||
>
|
||||
{{ loc.label }}
|
||||
|
||||
@@ -52,6 +52,7 @@ const REGEX_FORMAT
|
||||
// `20240101`); JS lacks possessive quantifiers to disambiguate it.
|
||||
// eslint-disable-next-line regexp/no-misleading-capturing-group
|
||||
const REGEX_PARSE = /* #__PURE__ */ /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[T\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/i;
|
||||
const REGEX_ISO_SUFFIX = /* #__PURE__ */ /z$/i;
|
||||
|
||||
const ORDINAL_SUFFIXES = ['th', 'st', 'nd', 'rd'] as const;
|
||||
|
||||
@@ -82,7 +83,7 @@ function formatOrdinal(num: number): string {
|
||||
export function normalizeDate(date: DateLike): Date {
|
||||
if (date === null || date === undefined) return new Date();
|
||||
if (isDate(date)) return new Date(date.getTime());
|
||||
if (isString(date) && !/z$/i.test(date)) {
|
||||
if (isString(date) && !REGEX_ISO_SUFFIX.test(date)) {
|
||||
const d = REGEX_PARSE.exec(date);
|
||||
if (d) {
|
||||
const month = d[2] ? Number(d[2]) - 1 : 0;
|
||||
|
||||
@@ -27,12 +27,12 @@ function toggle() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-5 text-center">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card p-5 text-center">
|
||||
<div class="demo-label">
|
||||
Ticks elapsed
|
||||
</div>
|
||||
<div class="mt-2 font-mono text-5xl font-bold tabular-nums text-(--fg)">
|
||||
<div class="demo-stat mt-2 text-5xl">
|
||||
{{ counter }}
|
||||
</div>
|
||||
|
||||
@@ -41,21 +41,21 @@ function toggle() {
|
||||
v-for="(on, i) in beats"
|
||||
:key="i"
|
||||
class="size-2.5 rounded-full transition-colors duration-200"
|
||||
:class="on ? 'bg-(--accent)' : 'bg-(--bg-inset)'"
|
||||
:class="on ? 'bg-accent' : 'bg-bg-inset'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex items-center justify-center gap-2 text-xs text-(--fg-subtle)">
|
||||
<div class="mt-4 flex items-center justify-center gap-2 text-xs text-fg-subtle">
|
||||
<span
|
||||
class="inline-block size-2 rounded-full transition"
|
||||
:class="isActive ? 'bg-emerald-500' : 'bg-(--border-strong)'"
|
||||
:class="isActive ? 'bg-emerald-500' : 'bg-border-strong'"
|
||||
/>
|
||||
{{ isActive ? `Ticking every ${interval}ms` : 'Paused' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-label">
|
||||
Interval speed
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@@ -64,8 +64,8 @@ function toggle() {
|
||||
:key="speed.value"
|
||||
class="flex-1 rounded-lg border px-3 py-1.5 text-sm font-medium transition active:scale-[0.98] cursor-pointer"
|
||||
:class="interval === speed.value
|
||||
? 'border-transparent bg-(--accent) text-(--accent-fg)'
|
||||
: 'border-(--border) bg-(--bg-elevated) text-(--fg) hover:bg-(--bg-inset) hover:border-(--border-strong)'"
|
||||
? 'border-transparent bg-accent text-accent-fg'
|
||||
: 'border-border bg-bg-elevated text-fg hover:bg-bg-inset hover:border-border-strong'"
|
||||
@click="interval = speed.value"
|
||||
>
|
||||
{{ speed.label }}
|
||||
@@ -75,13 +75,13 @@ function toggle() {
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="flex-1 inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn-primary flex-1"
|
||||
@click="toggle"
|
||||
>
|
||||
{{ isActive ? 'Pause' : 'Resume' }}
|
||||
</button>
|
||||
<button
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
class="demo-btn disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
:disabled="counter === 0"
|
||||
@click="reset"
|
||||
>
|
||||
|
||||
@@ -31,22 +31,22 @@ function clear() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<div class="flex items-center justify-between rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card flex items-center justify-between p-4">
|
||||
<div>
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-label">
|
||||
Interval callback
|
||||
</div>
|
||||
<div class="mt-1 flex items-center gap-2 text-sm text-(--fg-muted)">
|
||||
<div class="mt-1 flex items-center gap-2 text-sm text-fg-muted">
|
||||
<span
|
||||
class="inline-block size-2 rounded-full transition"
|
||||
:class="isActive ? 'bg-emerald-500' : 'bg-(--border-strong)'"
|
||||
:class="isActive ? 'bg-emerald-500' : 'bg-border-strong'"
|
||||
/>
|
||||
{{ isActive ? `Firing every ${interval}ms` : 'Stopped' }}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn-primary"
|
||||
@click="toggle"
|
||||
>
|
||||
{{ isActive ? 'Pause' : 'Start' }}
|
||||
@@ -54,7 +54,7 @@ function clear() {
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-label">
|
||||
Interval
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@@ -63,24 +63,24 @@ function clear() {
|
||||
:key="speed.value"
|
||||
class="flex-1 rounded-lg border px-3 py-1.5 text-sm font-medium transition active:scale-[0.98] cursor-pointer"
|
||||
:class="interval === speed.value
|
||||
? 'border-transparent bg-(--accent) text-(--accent-fg)'
|
||||
: 'border-(--border) bg-(--bg-elevated) text-(--fg) hover:bg-(--bg-inset) hover:border-(--border-strong)'"
|
||||
? 'border-transparent bg-accent text-accent-fg'
|
||||
: 'border-border bg-bg-elevated text-fg hover:bg-bg-inset hover:border-border-strong'"
|
||||
@click="interval = speed.value"
|
||||
>
|
||||
{{ speed.label }}
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-(--fg-subtle)">
|
||||
<p class="text-xs text-fg-subtle">
|
||||
Changing the interval while running restarts the timer with the new duration.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-label">
|
||||
Tick log
|
||||
</div>
|
||||
<button
|
||||
class="text-xs text-(--accent-text) transition hover:underline disabled:cursor-not-allowed disabled:opacity-40 cursor-pointer"
|
||||
class="text-xs text-accent-text transition hover:underline disabled:cursor-not-allowed disabled:opacity-40 cursor-pointer"
|
||||
:disabled="logs.length === 0"
|
||||
@click="clear"
|
||||
>
|
||||
@@ -88,17 +88,17 @@ function clear() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="min-h-32 rounded-lg border border-(--border) bg-(--bg-inset) p-3">
|
||||
<p v-if="logs.length === 0" class="py-6 text-center text-sm text-(--fg-subtle)">
|
||||
<div class="min-h-32 rounded-lg border border-border bg-bg-inset p-3">
|
||||
<p v-if="logs.length === 0" class="py-6 text-center text-sm text-fg-subtle">
|
||||
No ticks yet — press Start.
|
||||
</p>
|
||||
<ul v-else class="flex flex-col gap-1.5">
|
||||
<li
|
||||
v-for="log in logs"
|
||||
:key="log.id"
|
||||
class="flex items-center gap-2 font-mono text-sm tabular-nums text-(--fg)"
|
||||
class="flex items-center gap-2 font-mono text-sm tabular-nums text-fg"
|
||||
>
|
||||
<span class="inline-block size-1.5 rounded-full bg-(--accent)" />
|
||||
<span class="inline-block size-1.5 rounded-full bg-accent" />
|
||||
{{ log.time }}
|
||||
</li>
|
||||
</ul>
|
||||
@@ -106,14 +106,14 @@ function clear() {
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="flex-1 inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
class="demo-btn flex-1 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
:disabled="isActive"
|
||||
@click="resume"
|
||||
>
|
||||
Resume
|
||||
</button>
|
||||
<button
|
||||
class="flex-1 inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
class="demo-btn flex-1 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
:disabled="!isActive"
|
||||
@click="pause"
|
||||
>
|
||||
|
||||
@@ -20,23 +20,23 @@ const secondAngle = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full max-w-sm flex flex-col gap-4">
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 flex flex-col items-center gap-3">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Reactive now</div>
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card p-4 flex flex-col items-center gap-3">
|
||||
<div class="demo-label">Reactive now</div>
|
||||
|
||||
<div class="flex items-baseline gap-1">
|
||||
<span class="font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ time }}</span>
|
||||
<span class="font-mono text-lg font-semibold tabular-nums text-(--fg-subtle)">.{{ millis }}</span>
|
||||
<span class="demo-stat text-3xl">{{ time }}</span>
|
||||
<span class="font-mono text-lg font-semibold tabular-nums text-fg-subtle">.{{ millis }}</span>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-(--fg-muted)">{{ date }}</div>
|
||||
<div class="text-sm text-fg-muted">{{ date }}</div>
|
||||
|
||||
<div class="relative mt-1 size-24 rounded-full border-2 border-(--border-strong) bg-(--bg-inset)">
|
||||
<div class="relative mt-1 size-24 rounded-full border-2 border-border-strong bg-bg-inset">
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<div class="size-1.5 rounded-full bg-(--accent)" />
|
||||
<div class="size-1.5 rounded-full bg-accent" />
|
||||
</div>
|
||||
<div
|
||||
class="absolute bottom-1/2 left-1/2 h-9 w-0.5 origin-bottom rounded-full bg-(--accent)"
|
||||
class="absolute bottom-1/2 left-1/2 h-9 w-0.5 origin-bottom rounded-full bg-accent"
|
||||
:style="{ transform: `translateX(-50%) rotate(${secondAngle}deg)` }"
|
||||
/>
|
||||
</div>
|
||||
@@ -44,11 +44,11 @@ const secondAngle = computed(() => {
|
||||
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)"
|
||||
class="demo-badge"
|
||||
>
|
||||
<span
|
||||
class="size-1.5 rounded-full transition"
|
||||
:class="isActive ? 'bg-emerald-500' : 'bg-(--fg-subtle)'"
|
||||
:class="isActive ? 'bg-emerald-500' : 'bg-fg-subtle'"
|
||||
/>
|
||||
{{ isActive ? 'Ticking (RAF)' : 'Paused' }}
|
||||
</span>
|
||||
@@ -56,7 +56,7 @@ const secondAngle = computed(() => {
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn"
|
||||
@click="toggle"
|
||||
>
|
||||
{{ isActive ? 'Pause' : 'Resume' }}
|
||||
@@ -64,7 +64,7 @@ const secondAngle = computed(() => {
|
||||
<button
|
||||
type="button"
|
||||
:disabled="isActive"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
class="demo-btn-primary disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
@click="resume"
|
||||
>
|
||||
Resume
|
||||
@@ -72,7 +72,7 @@ const secondAngle = computed(() => {
|
||||
<button
|
||||
type="button"
|
||||
:disabled="!isActive"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
class="demo-btn disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
@click="pause"
|
||||
>
|
||||
Pause
|
||||
|
||||
@@ -35,46 +35,46 @@ const limitLabel = computed(() => (fpsLimit.value === 0 ? 'Unlimited' : `${fpsLi
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full max-w-sm flex flex-col gap-4">
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 flex flex-col gap-4">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card p-4 flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">requestAnimationFrame</span>
|
||||
<span class="demo-label">requestAnimationFrame</span>
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)"
|
||||
class="demo-badge"
|
||||
>
|
||||
<span class="size-1.5 rounded-full transition" :class="isActive ? 'bg-emerald-500' : 'bg-(--fg-subtle)'" />
|
||||
<span class="size-1.5 rounded-full transition" :class="isActive ? 'bg-emerald-500' : 'bg-fg-subtle'" />
|
||||
{{ isActive ? 'Running' : 'Paused' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- The animated track: marker position is updated every frame -->
|
||||
<div class="relative mx-2.5 h-8 rounded-lg border border-(--border) bg-(--bg-inset)">
|
||||
<div class="relative mx-2.5 h-8 rounded-lg border border-border bg-bg-inset">
|
||||
<div
|
||||
class="absolute top-1/2 size-5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-(--accent) shadow"
|
||||
class="absolute top-1/2 size-5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-accent shadow"
|
||||
:style="{ left: `${position}%` }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-2 text-center">
|
||||
<div class="font-mono text-lg font-bold tabular-nums text-(--fg)">{{ fps }}</div>
|
||||
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">fps</div>
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-2 text-center">
|
||||
<div class="demo-stat text-lg">{{ fps }}</div>
|
||||
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">fps</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-2 text-center">
|
||||
<div class="font-mono text-lg font-bold tabular-nums text-(--fg)">{{ delta.toFixed(1) }}</div>
|
||||
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">delta ms</div>
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-2 text-center">
|
||||
<div class="demo-stat text-lg">{{ delta.toFixed(1) }}</div>
|
||||
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">delta ms</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-2 text-center">
|
||||
<div class="font-mono text-lg font-bold tabular-nums text-(--fg)">{{ frames }}</div>
|
||||
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">frames</div>
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-2 text-center">
|
||||
<div class="demo-stat text-lg">{{ frames }}</div>
|
||||
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">frames</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)" for="fps-limit">FPS limit</label>
|
||||
<span class="font-mono text-xs tabular-nums text-(--fg-muted)">{{ limitLabel }}</span>
|
||||
<label class="demo-label" for="fps-limit">FPS limit</label>
|
||||
<span class="font-mono text-xs tabular-nums text-fg-muted">{{ limitLabel }}</span>
|
||||
</div>
|
||||
<input
|
||||
id="fps-limit"
|
||||
@@ -83,14 +83,14 @@ const limitLabel = computed(() => (fpsLimit.value === 0 ? 'Unlimited' : `${fpsLi
|
||||
min="0"
|
||||
max="60"
|
||||
step="5"
|
||||
class="w-full accent-(--accent) cursor-pointer"
|
||||
class="w-full accent-accent cursor-pointer"
|
||||
>
|
||||
<p class="text-xs text-(--fg-subtle)">Changing the limit takes effect on the next mount; toggle below to see it live.</p>
|
||||
<p class="text-xs text-fg-subtle">Changing the limit takes effect on the next mount; toggle below to see it live.</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn-primary"
|
||||
@click="toggle"
|
||||
>
|
||||
{{ isActive ? 'Pause loop' : 'Resume loop' }}
|
||||
|
||||
@@ -42,15 +42,15 @@ const absolute = computed(() =>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full max-w-sm flex flex-col gap-4">
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 flex flex-col items-center gap-2">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Relative time</span>
|
||||
<span class="font-mono text-3xl font-bold tabular-nums text-(--fg) text-center">{{ timeAgo }}</span>
|
||||
<span class="text-xs text-(--fg-muted)">{{ absolute }}</span>
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card p-4 flex flex-col items-center gap-2">
|
||||
<span class="demo-label">Relative time</span>
|
||||
<span class="demo-stat text-3xl text-center">{{ timeAgo }}</span>
|
||||
<span class="text-xs text-fg-muted">{{ absolute }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Pick an instant</span>
|
||||
<span class="demo-label">Pick an instant</span>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
v-for="preset in presets"
|
||||
@@ -58,8 +58,8 @@ const absolute = computed(() =>
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm font-medium transition active:scale-[0.98] cursor-pointer"
|
||||
:class="offset === preset.offset
|
||||
? 'border-transparent bg-(--accent) text-(--accent-fg) hover:bg-(--accent-hover)'
|
||||
: 'border-(--border) bg-(--bg-elevated) text-(--fg) hover:bg-(--bg-inset) hover:border-(--border-strong)'"
|
||||
? 'border-transparent bg-accent text-accent-fg hover:bg-accent-hover'
|
||||
: 'border-border bg-bg-elevated text-fg hover:bg-bg-inset hover:border-border-strong'"
|
||||
@click="offset = preset.offset"
|
||||
>
|
||||
{{ preset.label }}
|
||||
@@ -69,14 +69,14 @@ const absolute = computed(() =>
|
||||
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)"
|
||||
class="demo-badge"
|
||||
>
|
||||
<span class="size-1.5 rounded-full transition" :class="isActive ? 'bg-emerald-500' : 'bg-(--fg-subtle)'" />
|
||||
<span class="size-1.5 rounded-full transition" :class="isActive ? 'bg-emerald-500' : 'bg-fg-subtle'" />
|
||||
{{ isActive ? 'Updating every 1s' : 'Updates paused' }}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn"
|
||||
@click="toggle"
|
||||
>
|
||||
{{ isActive ? 'Pause' : 'Resume' }}
|
||||
|
||||
@@ -165,10 +165,12 @@ const DEFAULT_UNITS: Array<UseTimeAgoUnit<UseTimeAgoUnitName>> = [
|
||||
{ max: Number.POSITIVE_INFINITY, value: 31536000000, name: 'year' },
|
||||
];
|
||||
|
||||
const REGEX_DIGIT = /* #__PURE__ */ /\d/;
|
||||
|
||||
const DEFAULT_MESSAGES: UseTimeAgoMessages<UseTimeAgoUnitName> = {
|
||||
justNow: 'just now',
|
||||
past: n => /\d/.test(n) ? `${n} ago` : n,
|
||||
future: n => /\d/.test(n) ? `in ${n}` : n,
|
||||
past: n => REGEX_DIGIT.test(n) ? `${n} ago` : n,
|
||||
future: n => REGEX_DIGIT.test(n) ? `in ${n}` : n,
|
||||
month: (n, past) => n === 1 ? (past ? 'last month' : 'next month') : `${n} month${n > 1 ? 's' : ''}`,
|
||||
year: (n, past) => n === 1 ? (past ? 'last year' : 'next year') : `${n} year${n > 1 ? 's' : ''}`,
|
||||
day: (n, past) => n === 1 ? (past ? 'yesterday' : 'tomorrow') : `${n} day${n > 1 ? 's' : ''}`,
|
||||
|
||||
@@ -24,21 +24,21 @@ function cancel() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full max-w-sm flex flex-col gap-4">
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 flex flex-col items-center gap-3">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Status</span>
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card p-4 flex flex-col items-center gap-3">
|
||||
<span class="demo-label">Status</span>
|
||||
|
||||
<div
|
||||
class="flex size-20 items-center justify-center rounded-full border-2 transition"
|
||||
:class="ready
|
||||
? 'border-emerald-500/40 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
|
||||
: 'border-(--accent) bg-(--accent-subtle) text-(--accent-text)'"
|
||||
: 'border-accent bg-accent-subtle text-accent-text'"
|
||||
>
|
||||
<span class="text-sm font-semibold">{{ ready ? 'Ready' : 'Pending' }}</span>
|
||||
</div>
|
||||
|
||||
<p class="text-center text-sm text-(--fg-muted)">
|
||||
<template v-if="ready && firedAt">Fired at <span class="font-mono tabular-nums text-(--fg)">{{ firedAt }}</span></template>
|
||||
<p class="text-center text-sm text-fg-muted">
|
||||
<template v-if="ready && firedAt">Fired at <span class="font-mono tabular-nums text-fg">{{ firedAt }}</span></template>
|
||||
<template v-else-if="ready">Idle — start the timer below</template>
|
||||
<template v-else>Counting down… stays pending until the delay elapses</template>
|
||||
</p>
|
||||
@@ -46,8 +46,8 @@ function cancel() {
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)" for="delay">Delay</label>
|
||||
<span class="font-mono text-xs tabular-nums text-(--fg-muted)">{{ (delay / 1000).toFixed(1) }}s</span>
|
||||
<label class="demo-label" for="delay">Delay</label>
|
||||
<span class="font-mono text-xs tabular-nums text-fg-muted">{{ (delay / 1000).toFixed(1) }}s</span>
|
||||
</div>
|
||||
<input
|
||||
id="delay"
|
||||
@@ -56,14 +56,14 @@ function cancel() {
|
||||
min="500"
|
||||
max="5000"
|
||||
step="500"
|
||||
class="w-full accent-(--accent) cursor-pointer"
|
||||
class="w-full accent-accent cursor-pointer"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn-primary flex-1"
|
||||
@click="restart"
|
||||
>
|
||||
{{ ready ? 'Start' : 'Restart' }}
|
||||
@@ -71,7 +71,7 @@ function cancel() {
|
||||
<button
|
||||
type="button"
|
||||
:disabled="ready"
|
||||
class="flex-1 inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
class="demo-btn flex-1 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
@click="cancel"
|
||||
>
|
||||
Cancel
|
||||
|
||||
@@ -42,23 +42,23 @@ function undo() {
|
||||
|
||||
<template>
|
||||
<div class="w-full max-w-sm flex flex-col gap-3">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Inbox · undo with grace period</span>
|
||||
<span class="demo-label">Inbox · undo with grace period</span>
|
||||
|
||||
<ul v-if="inbox.length" class="flex flex-col gap-2">
|
||||
<li
|
||||
v-for="mail in inbox"
|
||||
:key="mail.id"
|
||||
class="flex items-center justify-between gap-3 rounded-xl border border-(--border) bg-(--bg-elevated) p-3 transition"
|
||||
class="demo-card flex items-center justify-between gap-3 p-3 transition"
|
||||
:class="{ 'opacity-40': pendingDelete?.id === mail.id }"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<div class="truncate text-sm font-medium text-(--fg)">{{ mail.subject }}</div>
|
||||
<div class="truncate text-xs text-(--fg-muted)">{{ mail.from }}</div>
|
||||
<div class="truncate text-sm font-medium text-fg">{{ mail.subject }}</div>
|
||||
<div class="truncate text-xs text-fg-muted">{{ mail.from }}</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
:disabled="isPending"
|
||||
class="shrink-0 inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
class="demo-btn shrink-0 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
@click="archive(mail)"
|
||||
>
|
||||
Archive
|
||||
@@ -66,7 +66,7 @@ function undo() {
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div v-else class="rounded-xl border border-dashed border-(--border) bg-(--bg-inset) p-6 text-center text-sm text-(--fg-subtle)">
|
||||
<div v-else class="rounded-xl border border-dashed border-border bg-bg-inset p-6 text-center text-sm text-fg-subtle">
|
||||
Inbox zero — everything archived.
|
||||
</div>
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export interface UseTimeoutFnOptions {
|
||||
immediateCallback?: boolean;
|
||||
}
|
||||
|
||||
export interface UseTimeoutFnReturn<Args extends any[]> {
|
||||
export interface UseTimeoutFnReturn<Args extends unknown[]> {
|
||||
/**
|
||||
* Whether the timeout is currently pending
|
||||
*/
|
||||
|
||||
@@ -45,33 +45,33 @@ function resetOffset() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 text-center">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card p-4 text-center">
|
||||
<div class="demo-label">
|
||||
Reactive timestamp
|
||||
</div>
|
||||
<div class="mt-2 font-mono text-3xl font-bold tabular-nums text-(--fg)">
|
||||
<div class="demo-stat mt-2 text-3xl">
|
||||
{{ clockTime }}
|
||||
</div>
|
||||
<div class="mt-1 text-sm text-(--fg-muted)">
|
||||
<div class="mt-1 text-sm text-fg-muted">
|
||||
{{ clockDate }}
|
||||
</div>
|
||||
<div class="mt-3 flex items-center justify-center gap-2 text-xs text-(--fg-subtle)">
|
||||
<div class="mt-3 flex items-center justify-center gap-2 text-xs text-fg-subtle">
|
||||
<span
|
||||
class="inline-block size-2 rounded-full transition"
|
||||
:class="isActive ? 'bg-emerald-500' : 'bg-(--border-strong)'"
|
||||
:class="isActive ? 'bg-emerald-500' : 'bg-border-strong'"
|
||||
/>
|
||||
{{ isActive ? 'Updating every second' : 'Paused' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 font-mono text-sm text-(--fg) tabular-nums">
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3 font-mono text-sm text-fg tabular-nums">
|
||||
{{ Math.round(timestamp) }} ms
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<button
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn-primary"
|
||||
@click="toggle"
|
||||
>
|
||||
{{ isActive ? 'Pause' : 'Resume' }}
|
||||
@@ -79,13 +79,13 @@ function resetOffset() {
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn"
|
||||
@click="shift(-3600_000)"
|
||||
>
|
||||
-1h
|
||||
</button>
|
||||
<button
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn"
|
||||
@click="shift(3600_000)"
|
||||
>
|
||||
+1h
|
||||
@@ -93,13 +93,13 @@ function resetOffset() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between text-sm text-(--fg-muted)">
|
||||
<div class="flex items-center justify-between text-sm text-fg-muted">
|
||||
<span>
|
||||
Offset:
|
||||
<span class="font-mono text-(--fg) tabular-nums">{{ (offset / 3600_000).toFixed(0) }}h</span>
|
||||
<span class="font-mono text-fg tabular-nums">{{ (offset / 3600_000).toFixed(0) }}h</span>
|
||||
</span>
|
||||
<button
|
||||
class="text-(--accent-text) transition hover:underline disabled:cursor-not-allowed disabled:opacity-40 cursor-pointer"
|
||||
class="text-accent-text transition hover:underline disabled:cursor-not-allowed disabled:opacity-40 cursor-pointer"
|
||||
:disabled="offset === 0"
|
||||
@click="resetOffset"
|
||||
>
|
||||
|
||||
@@ -40,19 +40,19 @@ function randomize() {
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-md flex-col gap-5">
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<div class="demo-card p-4">
|
||||
<div class="flex items-baseline justify-between">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<span class="demo-label">
|
||||
Eased value
|
||||
</span>
|
||||
<span class="font-mono text-3xl font-bold tabular-nums text-(--fg)">
|
||||
<span class="demo-stat text-3xl">
|
||||
{{ value.toFixed(1) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 h-2.5 w-full overflow-hidden rounded-full bg-(--bg-inset)">
|
||||
<div class="mt-3 h-2.5 w-full overflow-hidden rounded-full bg-bg-inset">
|
||||
<div
|
||||
class="h-full rounded-full bg-(--accent)"
|
||||
class="h-full rounded-full bg-accent"
|
||||
:style="{ width: `${Math.max(0, Math.min(100, value))}%` }"
|
||||
/>
|
||||
</div>
|
||||
@@ -63,10 +63,10 @@ function randomize() {
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
class="h-1.5 flex-1 cursor-pointer accent-(--accent)"
|
||||
class="h-1.5 flex-1 cursor-pointer accent-accent"
|
||||
>
|
||||
<button
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn"
|
||||
@click="randomize"
|
||||
>
|
||||
Random
|
||||
@@ -75,21 +75,21 @@ function randomize() {
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<label class="demo-label">
|
||||
Easing preset
|
||||
</label>
|
||||
<select
|
||||
v-model="preset"
|
||||
class="w-full rounded-lg border border-(--border) bg-(--bg) px-3 py-2 text-sm text-(--fg) transition focus:border-(--accent) focus:outline-none focus:ring-2 focus:ring-(--ring)"
|
||||
class="w-full rounded-lg border border-border bg-bg px-3 py-2 text-sm text-fg transition focus:border-accent focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
>
|
||||
<option v-for="name in presetNames" :key="name" :value="name">
|
||||
{{ name }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<label class="mt-1 flex items-center justify-between text-sm text-(--fg-muted)">
|
||||
<label class="mt-1 flex items-center justify-between text-sm text-fg-muted">
|
||||
<span>Duration</span>
|
||||
<span class="font-mono text-(--fg) tabular-nums">{{ duration }}ms</span>
|
||||
<span class="font-mono text-fg tabular-nums">{{ duration }}ms</span>
|
||||
</label>
|
||||
<input
|
||||
v-model.number="duration"
|
||||
@@ -97,21 +97,21 @@ function randomize() {
|
||||
min="100"
|
||||
max="2000"
|
||||
step="100"
|
||||
class="h-1.5 w-full cursor-pointer accent-(--accent)"
|
||||
class="h-1.5 w-full cursor-pointer accent-accent"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<div class="demo-card p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="size-12 shrink-0 rounded-lg border border-(--border)"
|
||||
class="size-12 shrink-0 rounded-lg border border-border"
|
||||
:style="{ backgroundColor: colorCss }"
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-label">
|
||||
Animated tuple
|
||||
</div>
|
||||
<div class="font-mono text-sm text-(--fg) tabular-nums">
|
||||
<div class="font-mono text-sm text-fg tabular-nums">
|
||||
{{ colorCss }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -121,7 +121,7 @@ function randomize() {
|
||||
<button
|
||||
v-for="[label, rgb] in swatches"
|
||||
:key="label"
|
||||
class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted) transition hover:border-(--border-strong) cursor-pointer"
|
||||
class="demo-badge transition hover:border-border-strong cursor-pointer"
|
||||
@click="colorTarget = [...rgb]"
|
||||
>
|
||||
<span class="size-2.5 rounded-full" :style="{ backgroundColor: `rgb(${rgb.join(',')})` }" />
|
||||
|
||||
@@ -4,6 +4,7 @@ import { clamp, isFunction, isNumber, lerp, noop } from '@robonen/stdlib';
|
||||
import { defaultWindow } from '@/types';
|
||||
import type { ConfigurableWindow } from '@/types';
|
||||
import { useRafFn } from '@/composables/animation/useRafFn';
|
||||
import { tryOnScopeDispose } from '@/composables/lifecycle/tryOnScopeDispose';
|
||||
|
||||
/**
|
||||
* Cubic bezier control points `[x1, y1, x2, y2]` (the implied endpoints are
|
||||
@@ -356,5 +357,10 @@ export function useTransition<T extends TransitionValue>(
|
||||
},
|
||||
);
|
||||
|
||||
// The RAF loop is torn down by useRafFn on scope dispose, but a pending start
|
||||
// delay (window.setTimeout) is not — clear it so the timer can't fire into a
|
||||
// disposed scope.
|
||||
tryOnScopeDispose(clearDelay);
|
||||
|
||||
return computed(() => outputRef.value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user