feature: Shared UX Primitives
CI / backend (push) Successful in 6m16s
CI / frontend (push) Failing after 1m4s

This commit is contained in:
2026-08-12 03:57:43 +08:00
parent 56e100a495
commit 9811f2a2ed
26 changed files with 1259 additions and 183 deletions
@@ -0,0 +1,72 @@
<template>
<Teleport to="body">
<div
v-if="open"
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
role="dialog"
aria-modal="true"
:aria-labelledby="titleId"
data-testid="confirm-dialog"
@keydown.esc.prevent="$emit('cancel')"
>
<div
class="w-full max-w-md rounded-card border border-line bg-surface p-6 shadow-dialog"
@click.stop
>
<h3 :id="titleId" class="text-lg font-semibold text-ink-strong">
{{ title }}
</h3>
<p v-if="body" class="mt-2 text-sm text-ink">{{ body }}</p>
<div v-if="$slots.default" class="mt-4">
<slot />
</div>
<div class="mt-6 flex flex-col-reverse gap-3 sm:flex-row sm:justify-end">
<button
type="button"
class="px-4 py-2 text-sm text-ink-secondary hover:text-ink-strong"
@click="$emit('cancel')"
>
{{ cancelLabel }}
</button>
<button
type="button"
:class="variant === 'danger' ? 'btn-danger' : 'btn-primary'"
:disabled="confirmDisabled"
@click="$emit('confirm')"
>
{{ confirmLabel }}
</button>
</div>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { useId } from 'vue'
withDefaults(
defineProps<{
open: boolean
title: string
body?: string
confirmLabel?: string
cancelLabel?: string
variant?: 'primary' | 'danger'
confirmDisabled?: boolean
}>(),
{
confirmLabel: 'Confirm',
cancelLabel: 'Cancel',
variant: 'primary',
confirmDisabled: false,
}
)
defineEmits<{
(e: 'confirm'): void
(e: 'cancel'): void
}>()
const titleId = useId()
</script>