feat(primitives): media-editor components, category reorg, perf + type cleanup

Reorganize components into category folders (forms/canvas/overlays/etc.); add the
media-editor headless family (timeline, curve-editor, waveform, crop, color
picker, etc.); apply perf fixes (O(1) collection lookups, plain-object drag
state, gesture-leak teardown, shallowRef color state, rect caching) and replace
source `any` with proper types.
This commit is contained in:
2026-06-15 16:54:29 +07:00
parent 661a55719e
commit eefd7abf83
1029 changed files with 65815 additions and 9449 deletions
+110
View File
@@ -0,0 +1,110 @@
<script setup lang="ts">
import { ref } from 'vue';
import {
ToastAction,
ToastClose,
ToastDescription,
ToastProvider,
ToastRoot,
ToastTitle,
ToastViewport,
} from '@robonen/primitives';
interface ToastItem {
id: number;
title: string;
description: string;
open: boolean;
}
const toasts = ref<ToastItem[]>([]);
let nextId = 0;
function notify() {
const id = nextId++;
toasts.value.push({
id,
title: 'Message archived',
description: 'Moved "Weekly digest" to your archive.',
open: true,
});
}
function undo(id: number) {
const toast = toasts.value.find((t) => t.id === id);
if (toast) toast.open = false;
}
// Drop a toast from the list once it has fully closed.
function remove(id: number) {
toasts.value = toasts.value.filter((t) => t.id !== id);
}
</script>
<template>
<ToastProvider :duration="4000" swipe-direction="right">
<div class="flex flex-col items-start gap-3 text-fg">
<button
type="button"
class="inline-flex items-center rounded-md bg-accent px-3 py-1.5 text-sm font-medium text-accent-fg transition-colors hover:bg-accent-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
@click="notify"
>
Archive message
</button>
<p class="text-xs text-fg-muted">
Toasts auto-dismiss after 4s. Hover the stack to pause the timer.
</p>
</div>
<ToastRoot
v-for="toast in toasts"
:key="toast.id"
v-model:open="toast.open"
class="flex items-start gap-3 rounded-lg border border-border bg-bg-elevated p-3 shadow-lg data-[state=closed]:opacity-0 data-[state=open]:opacity-100"
@update:open="(open) => !open && remove(toast.id)"
>
<div class="mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-emerald-500/15 text-emerald-600 dark:text-emerald-400">
<svg
class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
>
<path d="M20 6 9 17l-5-5" />
</svg>
</div>
<div class="min-w-0 flex-1">
<ToastTitle class="text-sm font-semibold text-fg">
{{ toast.title }}
</ToastTitle>
<ToastDescription class="mt-0.5 text-sm text-fg-muted">
{{ toast.description }}
</ToastDescription>
</div>
<div class="flex shrink-0 items-center gap-1">
<ToastAction
alt-text="Undo archiving this message"
class="rounded-md border border-border bg-bg px-2 py-1 text-xs font-medium text-fg transition-colors hover:bg-bg-subtle focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
@click="undo(toast.id)"
>
Undo
</ToastAction>
<ToastClose
aria-label="Dismiss"
class="inline-flex size-6 items-center justify-center rounded-md text-fg-muted transition-colors hover:bg-bg-subtle hover:text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<svg
class="size-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"
>
<path d="M18 6 6 18M6 6l12 12" />
</svg>
</ToastClose>
</div>
</ToastRoot>
<ToastViewport
class="fixed bottom-4 right-4 z-50 flex w-[min(92vw,22rem)] flex-col gap-2 outline-none"
/>
</ToastProvider>
</template>