feat: add layout components to shared-ui (Tier 6)

Button enhancements:
- Add 'outline' and 'success' variants
- Add 'xl' size option

New skeleton/loading components:
- SkeletonBox: Base skeleton with shimmer animation (theme-aware)
- SkeletonText: Multi-line text skeleton

New feedback components:
- EmptyState: Standardized empty state display with icon, title,
  message, and optional action buttons

New confirmation components:
- ConfirmationModal: Pre-styled confirmation dialog for delete/warning
  actions with 'danger', 'warning', and 'info' variants

These components reduce duplication across apps and provide
consistent UX patterns.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-11-25 00:28:52 +01:00
parent c87641f91b
commit 3c457f9c18
10 changed files with 413 additions and 7 deletions

View file

@ -0,0 +1,126 @@
<script lang="ts">
/**
* ConfirmationModal - Pre-styled confirmation dialog
*
* Used for delete confirmations, destructive actions, or any action
* that requires user confirmation before proceeding.
*
* @example Delete confirmation
* ```svelte
* <ConfirmationModal
* visible={showDeleteModal}
* onClose={() => showDeleteModal = false}
* onConfirm={handleDelete}
* variant="danger"
* title="Delete memo?"
* message="This action cannot be undone."
* confirmLabel="Delete"
* />
* ```
*
* @example Warning confirmation
* ```svelte
* <ConfirmationModal
* visible={showWarningModal}
* onClose={() => showWarningModal = false}
* onConfirm={handleProceed}
* variant="warning"
* title="Are you sure?"
* message="You have unsaved changes that will be lost."
* />
* ```
*/
import { Icon } from '@manacore/shared-icons';
import Modal from './Modal.svelte';
import { Text, Button } from '../atoms';
type ConfirmationVariant = 'danger' | 'warning' | 'info';
interface Props {
/** Whether the modal is visible */
visible: boolean;
/** Called when modal is closed (cancel or backdrop click) */
onClose: () => void;
/** Called when user confirms the action */
onConfirm: () => void | Promise<void>;
/** Visual variant */
variant?: ConfirmationVariant;
/** Modal title */
title: string;
/** Confirmation message */
message?: string;
/** Confirm button label */
confirmLabel?: string;
/** Cancel button label */
cancelLabel?: string;
/** Whether confirm action is in progress */
loading?: boolean;
}
let {
visible,
onClose,
onConfirm,
variant = 'danger',
title,
message,
confirmLabel = 'Confirm',
cancelLabel = 'Cancel',
loading = false
}: Props = $props();
const variantConfig: Record<
ConfirmationVariant,
{ iconName: string; iconColor: string; buttonVariant: 'danger' | 'primary' }
> = {
danger: {
iconName: 'alert-triangle',
iconColor: 'text-red-500',
buttonVariant: 'danger'
},
warning: {
iconName: 'alert-circle',
iconColor: 'text-yellow-500',
buttonVariant: 'primary'
},
info: {
iconName: 'info',
iconColor: 'text-blue-500',
buttonVariant: 'primary'
}
};
const config = $derived(variantConfig[variant]);
async function handleConfirm() {
await onConfirm();
}
</script>
<Modal {visible} {onClose} {title} maxWidth="sm">
{#snippet icon()}
<div class="p-2 rounded-full bg-menu-hover">
<Icon name={config.iconName} size={20} class={config.iconColor} />
</div>
{/snippet}
<div class="text-center py-2">
{#if message}
<Text variant="muted" class="leading-relaxed">
{message}
</Text>
{/if}
</div>
{#snippet footer()}
<div class="flex gap-3 justify-end">
<Button variant="ghost" onclick={onClose} disabled={loading}>
{cancelLabel}
</Button>
<Button variant={config.buttonVariant} onclick={handleConfirm} {loading}>
{confirmLabel}
</Button>
</div>
{/snippet}
</Modal>

View file

@ -1,3 +1,4 @@
export { default as Modal } from './Modal.svelte';
export { default as ConfirmationModal } from './ConfirmationModal.svelte';
export { default as AppSlider } from './AppSlider.svelte';
export type { AppItem } from './AppSlider.svelte';
export type { AppItem } from './AppSlider.types';