mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 04:46:42 +02:00
feat: add form and layout components to shared-ui (Tier 6b)
New molecules: - ModalFooter: Standardized modal footer with button layout - DataCard: Generic card for displaying data items (memos, decks, etc.) - PageHeader: Standardized page header with title, description, actions New organisms: - FormModal: Modal with built-in form handling, validation, loading states All components use Svelte 5 snippets for flexible slot patterns. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
3c457f9c18
commit
5045d70bf7
7 changed files with 505 additions and 1 deletions
117
packages/shared-ui/src/organisms/FormModal.svelte
Normal file
117
packages/shared-ui/src/organisms/FormModal.svelte
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* FormModal - Modal with built-in form handling
|
||||
*
|
||||
* Extends Modal with form submission, validation error display,
|
||||
* and standardized footer buttons.
|
||||
*
|
||||
* @example Basic form modal
|
||||
* ```svelte
|
||||
* <FormModal
|
||||
* visible={showModal}
|
||||
* onClose={() => showModal = false}
|
||||
* onSubmit={handleSubmit}
|
||||
* title="Create Item"
|
||||
* submitLabel="Create"
|
||||
* >
|
||||
* <Input label="Name" bind:value={name} />
|
||||
* <Textarea label="Description" bind:value={description} />
|
||||
* </FormModal>
|
||||
* ```
|
||||
*/
|
||||
|
||||
import type { Snippet } from 'svelte';
|
||||
import Modal from './Modal.svelte';
|
||||
import { Button, Text } from '../atoms';
|
||||
|
||||
type SubmitVariant = 'primary' | 'danger' | 'success';
|
||||
|
||||
interface Props {
|
||||
/** Whether the modal is visible */
|
||||
visible: boolean;
|
||||
/** Called when modal is closed */
|
||||
onClose: () => void;
|
||||
/** Called when form is submitted */
|
||||
onSubmit: () => void | Promise<void>;
|
||||
/** Modal title */
|
||||
title: string;
|
||||
/** Form content */
|
||||
children: Snippet;
|
||||
/** Icon snippet for header */
|
||||
icon?: Snippet;
|
||||
/** Submit button label */
|
||||
submitLabel?: string;
|
||||
/** Cancel button label */
|
||||
cancelLabel?: string;
|
||||
/** Submit button variant */
|
||||
submitVariant?: SubmitVariant;
|
||||
/** Whether submission is in progress */
|
||||
loading?: boolean;
|
||||
/** Error message to display */
|
||||
error?: string | null;
|
||||
/** Max width of modal */
|
||||
maxWidth?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
/** Whether submit button is disabled */
|
||||
submitDisabled?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
visible,
|
||||
onClose,
|
||||
onSubmit,
|
||||
title,
|
||||
children,
|
||||
icon,
|
||||
submitLabel = 'Submit',
|
||||
cancelLabel = 'Cancel',
|
||||
submitVariant = 'primary',
|
||||
loading = false,
|
||||
error = null,
|
||||
maxWidth = 'md',
|
||||
submitDisabled = false
|
||||
}: Props = $props();
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
await onSubmit();
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter' && e.ctrlKey && !loading && !submitDisabled) {
|
||||
handleSubmit(e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal {visible} {onClose} {title} {icon} {maxWidth}>
|
||||
<form onsubmit={handleSubmit} onkeydown={handleKeydown} class="space-y-4">
|
||||
<!-- Error message -->
|
||||
{#if error}
|
||||
<div class="rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 p-3">
|
||||
<Text variant="small" class="text-red-600 dark:text-red-400">
|
||||
{error}
|
||||
</Text>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Form content -->
|
||||
{@render children()}
|
||||
</form>
|
||||
|
||||
{#snippet footer()}
|
||||
<div class="flex gap-3 justify-end">
|
||||
<Button variant="ghost" onclick={onClose} disabled={loading}>
|
||||
{cancelLabel}
|
||||
</Button>
|
||||
<Button
|
||||
variant={submitVariant}
|
||||
type="submit"
|
||||
onclick={handleSubmit}
|
||||
{loading}
|
||||
disabled={submitDisabled}
|
||||
>
|
||||
{submitLabel}
|
||||
</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Modal>
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
export { default as Modal } from './Modal.svelte';
|
||||
export { default as ConfirmationModal } from './ConfirmationModal.svelte';
|
||||
export { default as FormModal } from './FormModal.svelte';
|
||||
export { default as AppSlider } from './AppSlider.svelte';
|
||||
export type { AppItem } from './AppSlider.types';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue