migrate dialog consumers to ModalHeader/Body/Footer pattern

This commit is contained in:
Justin Edmund 2025-12-13 14:55:46 -08:00
parent a3c5676c4c
commit 789494e773
4 changed files with 277 additions and 213 deletions

View file

@ -12,6 +12,9 @@
useAddSummonsToCollection
} from '$lib/api/mutations/collection.mutations'
import Dialog from '$lib/components/ui/Dialog.svelte'
import ModalHeader from '$lib/components/ui/ModalHeader.svelte'
import ModalBody from '$lib/components/ui/ModalBody.svelte'
import ModalFooter from '$lib/components/ui/ModalFooter.svelte'
import Button from '$lib/components/ui/Button.svelte'
import Icon from '$lib/components/Icon.svelte'
import CollectionFilters, {
@ -316,8 +319,9 @@
})
</script>
<Dialog bind:open {onOpenChange} title={dialogTitle} size="large">
<Dialog bind:open {onOpenChange} size="large">
{#snippet children()}
<ModalHeader title={dialogTitle} />
<div class="modal-content">
<!-- Search input -->
<div class="search-bar">
@ -441,40 +445,41 @@
{/if}
</div>
</div>
{/snippet}
{#snippet footer()}
<div class="modal-footer">
<div class="footer-left">
{#if selectedCount > 0}
<button
type="button"
class="selected-link"
class:active={showOnlySelected}
onclick={toggleShowSelected}
>
{selectedText}
</button>
{/if}
</div>
<div class="footer-right">
<Button variant="ghost" onclick={() => (open = false)}>
Cancel
</Button>
<Button
variant="primary"
disabled={selectedCount === 0 || currentMutation.isPending}
onclick={handleAdd}
>
{#if currentMutation.isPending}
<Icon name="loader-2" size={16} />
Adding...
{:else}
Add to Collection
{/if}
</Button>
</div>
</div>
<ModalFooter>
{#snippet children()}
<div class="modal-footer">
<div class="footer-left">
{#if selectedCount > 0}
<button
type="button"
class="selected-link"
class:active={showOnlySelected}
onclick={toggleShowSelected}
>
{selectedText}
</button>
{/if}
</div>
<div class="footer-right">
<Button variant="ghost" onclick={() => (open = false)}>
Cancel
</Button>
<Button
variant="primary"
disabled={selectedCount === 0 || currentMutation.isPending}
onclick={handleAdd}
>
{#if currentMutation.isPending}
<Icon name="loader-2" size={16} />
Adding...
{:else}
Add to Collection
{/if}
</Button>
</div>
</div>
{/snippet}
</ModalFooter>
{/snippet}
</Dialog>

View file

@ -18,6 +18,9 @@
-->
<script lang="ts">
import Dialog from '$lib/components/ui/Dialog.svelte'
import ModalHeader from '$lib/components/ui/ModalHeader.svelte'
import ModalBody from '$lib/components/ui/ModalBody.svelte'
import ModalFooter from '$lib/components/ui/ModalFooter.svelte'
import Button from '$lib/components/ui/Button.svelte'
import type { ConflictData } from '$lib/types/api/conflict'
import type { GridCharacter, GridWeapon } from '$lib/types/api/party'
@ -167,70 +170,78 @@
}
</script>
<Dialog bind:open onOpenChange={handleOpenChange} title={m.conflict_title()}>
{#if conflict}
<div class={styles.content}>
<p class={styles.message}>{conflictMessage}</p>
<Dialog bind:open onOpenChange={handleOpenChange}>
{#snippet children()}
<ModalHeader title={m.conflict_title()} />
<ModalBody>
{#snippet children()}
{#if conflict}
<div class={styles.content}>
<p class={styles.message}>{conflictMessage}</p>
<div class={styles.diagram}>
<!-- Conflicting items (left side) -->
<ul class={styles.conflicts}>
{#if conflict.type === 'character'}
{#each conflict.conflicts as gridChar (gridChar.id)}
<li class={styles.item}>
<img
src={getCharacterUrl(gridChar)}
alt={gridChar.character.name[locale]}
/>
<span>{gridChar.character.name[locale]}</span>
</li>
{/each}
{:else}
{#each conflict.conflicts as gridWeapon (gridWeapon.id)}
<li class={styles.item}>
<img
src={getWeaponUrl(gridWeapon)}
alt={gridWeapon.weapon.name[locale]}
/>
<span>{gridWeapon.weapon.name[locale]}</span>
</li>
{/each}
{/if}
</ul>
<div class={styles.diagram}>
<!-- Conflicting items (left side) -->
<ul class={styles.conflicts}>
{#if conflict.type === 'character'}
{#each conflict.conflicts as gridChar (gridChar.id)}
<li class={styles.item}>
<img
src={getCharacterUrl(gridChar)}
alt={gridChar.character.name[locale]}
/>
<span>{gridChar.character.name[locale]}</span>
</li>
{/each}
{:else}
{#each conflict.conflicts as gridWeapon (gridWeapon.id)}
<li class={styles.item}>
<img
src={getWeaponUrl(gridWeapon)}
alt={gridWeapon.weapon.name[locale]}
/>
<span>{gridWeapon.weapon.name[locale]}</span>
</li>
{/each}
{/if}
</ul>
<!-- Arrow -->
<span class={styles.arrow}>&rarr;</span>
<!-- Arrow -->
<span class={styles.arrow}>&rarr;</span>
<!-- Incoming item (right side) -->
<div class={styles.incoming}>
{#if conflict.type === 'character'}
<div class={styles.item}>
<img
src={getIncomingCharacterUrl(conflict.incoming)}
alt={conflict.incoming.name[locale]}
/>
<span>{conflict.incoming.name[locale]}</span>
<!-- Incoming item (right side) -->
<div class={styles.incoming}>
{#if conflict.type === 'character'}
<div class={styles.item}>
<img
src={getIncomingCharacterUrl(conflict.incoming)}
alt={conflict.incoming.name[locale]}
/>
<span>{conflict.incoming.name[locale]}</span>
</div>
{:else}
<div class={styles.item}>
<img
src={getIncomingWeaponUrl(conflict.incoming)}
alt={conflict.incoming.name[locale]}
/>
<span>{conflict.incoming.name[locale]}</span>
</div>
{/if}
</div>
</div>
{:else}
<div class={styles.item}>
<img
src={getIncomingWeaponUrl(conflict.incoming)}
alt={conflict.incoming.name[locale]}
/>
<span>{conflict.incoming.name[locale]}</span>
</div>
{/if}
</div>
</div>
</div>
{/if}
{#snippet footer()}
<Button variant="ghost" onclick={handleCancel} disabled={isLoading}>
{m.conflict_cancel()}
</Button>
<Button variant="primary" onclick={handleResolve} disabled={isLoading}>
{m.conflict_confirm()}
</Button>
</div>
{/if}
{/snippet}
</ModalBody>
<ModalFooter>
{#snippet children()}
<Button variant="ghost" onclick={handleCancel} disabled={isLoading}>
{m.conflict_cancel()}
</Button>
<Button variant="primary" onclick={handleResolve} disabled={isLoading}>
{m.conflict_confirm()}
</Button>
{/snippet}
</ModalFooter>
{/snippet}
</Dialog>

View file

@ -46,6 +46,9 @@
import type { AddItemResult } from '$lib/types/api/search'
import { GridType } from '$lib/types/enums'
import Dialog from '$lib/components/ui/Dialog.svelte'
import ModalHeader from '$lib/components/ui/ModalHeader.svelte'
import ModalBody from '$lib/components/ui/ModalBody.svelte'
import ModalFooter from '$lib/components/ui/ModalFooter.svelte'
import Button from '$lib/components/ui/Button.svelte'
import Icon from '$lib/components/Icon.svelte'
import DescriptionRenderer from '$lib/components/DescriptionRenderer.svelte'
@ -1003,47 +1006,55 @@
</div>
<!-- Edit Dialog -->
<Dialog bind:open={editDialogOpen} title="Edit Party Details">
<Dialog bind:open={editDialogOpen}>
{#snippet children()}
<div class="edit-form">
<label for="party-title">Party Title</label>
<input
id="party-title"
type="text"
bind:value={editingTitle}
placeholder="Enter party title..."
disabled={loading}
/>
</div>
{/snippet}
{#snippet footer()}
<button class="btn-secondary" onclick={() => (editDialogOpen = false)} disabled={loading}>
Cancel
</button>
<button class="btn-primary" onclick={savePartyTitle} disabled={loading || !editingTitle.trim()}>
{loading ? 'Saving...' : 'Save'}
</button>
<ModalHeader title="Edit Party Details" />
<ModalBody>
<div class="edit-form">
<label for="party-title">Party Title</label>
<input
id="party-title"
type="text"
bind:value={editingTitle}
placeholder="Enter party title..."
disabled={loading}
/>
</div>
</ModalBody>
<ModalFooter>
{#snippet children()}
<button class="btn-secondary" onclick={() => (editDialogOpen = false)} disabled={loading}>
Cancel
</button>
<button class="btn-primary" onclick={savePartyTitle} disabled={loading || !editingTitle.trim()}>
{loading ? 'Saving...' : 'Save'}
</button>
{/snippet}
</ModalFooter>
{/snippet}
</Dialog>
<!-- Delete Confirmation Dialog -->
<Dialog bind:open={deleteDialogOpen} title="Delete Party">
<Dialog bind:open={deleteDialogOpen}>
{#snippet children()}
<div class="delete-confirmation">
<p>Are you sure you want to delete this party?</p>
<p><strong>{party.name || 'Unnamed Party'}</strong></p>
<p class="warning">⚠️ This action cannot be undone.</p>
</div>
{/snippet}
{#snippet footer()}
<button class="btn-secondary" onclick={() => (deleteDialogOpen = false)} disabled={deleting}>
Cancel
</button>
<button class="btn-danger" onclick={deleteParty} disabled={deleting}>
{deleting ? 'Deleting...' : 'Delete Party'}
</button>
<ModalHeader title="Delete Party" />
<ModalBody>
<div class="delete-confirmation">
<p>Are you sure you want to delete this party?</p>
<p><strong>{party.name || 'Unnamed Party'}</strong></p>
<p class="warning">⚠️ This action cannot be undone.</p>
</div>
</ModalBody>
<ModalFooter>
{#snippet children()}
<button class="btn-secondary" onclick={() => (deleteDialogOpen = false)} disabled={deleting}>
Cancel
</button>
<button class="btn-danger" onclick={deleteParty} disabled={deleting}>
{deleting ? 'Deleting...' : 'Delete Party'}
</button>
{/snippet}
</ModalFooter>
{/snippet}
</Dialog>

View file

@ -1,6 +1,9 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf'
import Dialog from '$lib/components/ui/Dialog.svelte'
import ModalHeader from '$lib/components/ui/ModalHeader.svelte'
import ModalBody from '$lib/components/ui/ModalBody.svelte'
import ModalFooter from '$lib/components/ui/ModalFooter.svelte'
import Button from '$lib/components/ui/Button.svelte'
const { Story } = defineMeta({
@ -22,9 +25,14 @@
<Story name="Default">
<div>
<Button onclick={() => (defaultOpen = true)}>Open Dialog</Button>
<Dialog bind:open={defaultOpen} title="Dialog Title">
<Dialog bind:open={defaultOpen}>
{#snippet children()}
<p>This is the dialog content. You can put any content here.</p>
<ModalHeader title="Dialog Title" />
<ModalBody>
{#snippet children()}
<p>This is the dialog content. You can put any content here.</p>
{/snippet}
</ModalBody>
{/snippet}
</Dialog>
</div>
@ -34,13 +42,14 @@
<Story name="With Description">
<div>
<Button onclick={() => (withDescOpen = true)}>Open Dialog</Button>
<Dialog
bind:open={withDescOpen}
title="Account Settings"
description="Make changes to your account settings here."
>
<Dialog bind:open={withDescOpen}>
{#snippet children()}
<p>Your account settings form would go here.</p>
<ModalHeader title="Account Settings" description="Make changes to your account settings here." />
<ModalBody>
{#snippet children()}
<p>Your account settings form would go here.</p>
{/snippet}
</ModalBody>
{/snippet}
</Dialog>
</div>
@ -50,13 +59,20 @@
<Story name="With Footer">
<div>
<Button onclick={() => (withFooterOpen = true)}>Open Dialog</Button>
<Dialog bind:open={withFooterOpen} title="Confirm Action">
<Dialog bind:open={withFooterOpen}>
{#snippet children()}
<p>Are you sure you want to proceed with this action?</p>
{/snippet}
{#snippet footer()}
<Button variant="secondary" onclick={() => (withFooterOpen = false)}>Cancel</Button>
<Button variant="primary" onclick={() => (withFooterOpen = false)}>Confirm</Button>
<ModalHeader title="Confirm Action" />
<ModalBody>
{#snippet children()}
<p>Are you sure you want to proceed with this action?</p>
{/snippet}
</ModalBody>
<ModalFooter>
{#snippet children()}
<Button variant="secondary" onclick={() => (withFooterOpen = false)}>Cancel</Button>
<Button variant="primary" onclick={() => (withFooterOpen = false)}>Confirm</Button>
{/snippet}
</ModalFooter>
{/snippet}
</Dialog>
</div>
@ -66,35 +82,42 @@
<Story name="Long Content">
<div>
<Button onclick={() => (longContentOpen = true)}>Open Long Dialog</Button>
<Dialog bind:open={longContentOpen} title="Terms and Conditions">
<Dialog bind:open={longContentOpen}>
{#snippet children()}
<div style="display: flex; flex-direction: column; gap: 16px;">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris.
</p>
<p>
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident.
</p>
<p>
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis.
</p>
<p>
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium
voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint.
</p>
<p>
Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id
quod maxime placeat facere possimus.
</p>
</div>
{/snippet}
{#snippet footer()}
<Button variant="secondary" onclick={() => (longContentOpen = false)}>Decline</Button>
<Button variant="primary" onclick={() => (longContentOpen = false)}>Accept</Button>
<ModalHeader title="Terms and Conditions" />
<ModalBody>
{#snippet children()}
<div style="display: flex; flex-direction: column; gap: 16px;">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris.
</p>
<p>
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident.
</p>
<p>
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis.
</p>
<p>
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium
voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint.
</p>
<p>
Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id
quod maxime placeat facere possimus.
</p>
</div>
{/snippet}
</ModalBody>
<ModalFooter>
{#snippet children()}
<Button variant="secondary" onclick={() => (longContentOpen = false)}>Decline</Button>
<Button variant="primary" onclick={() => (longContentOpen = false)}>Accept</Button>
{/snippet}
</ModalFooter>
{/snippet}
</Dialog>
</div>
@ -104,38 +127,45 @@
<Story name="Form Dialog">
<div>
<Button onclick={() => (formOpen = true)}>Edit Profile</Button>
<Dialog bind:open={formOpen} title="Edit Profile" description="Update your profile information.">
<Dialog bind:open={formOpen}>
{#snippet children()}
<div style="display: flex; flex-direction: column; gap: 16px;">
<div>
<label
style="display: block; font-size: 14px; font-weight: 500; margin-bottom: 4px;"
for="name">Name</label
>
<input
id="name"
type="text"
placeholder="Enter your name"
style="width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px;"
/>
</div>
<div>
<label
style="display: block; font-size: 14px; font-weight: 500; margin-bottom: 4px;"
for="email">Email</label
>
<input
id="email"
type="email"
placeholder="Enter your email"
style="width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px;"
/>
</div>
</div>
{/snippet}
{#snippet footer()}
<Button variant="secondary" onclick={() => (formOpen = false)}>Cancel</Button>
<Button variant="primary" onclick={() => (formOpen = false)}>Save Changes</Button>
<ModalHeader title="Edit Profile" description="Update your profile information." />
<ModalBody>
{#snippet children()}
<div style="display: flex; flex-direction: column; gap: 16px;">
<div>
<label
style="display: block; font-size: 14px; font-weight: 500; margin-bottom: 4px;"
for="name">Name</label
>
<input
id="name"
type="text"
placeholder="Enter your name"
style="width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px;"
/>
</div>
<div>
<label
style="display: block; font-size: 14px; font-weight: 500; margin-bottom: 4px;"
for="email">Email</label
>
<input
id="email"
type="email"
placeholder="Enter your email"
style="width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px;"
/>
</div>
</div>
{/snippet}
</ModalBody>
<ModalFooter>
{#snippet children()}
<Button variant="secondary" onclick={() => (formOpen = false)}>Cancel</Button>
<Button variant="primary" onclick={() => (formOpen = false)}>Save Changes</Button>
{/snippet}
</ModalFooter>
{/snippet}
</Dialog>
</div>
@ -145,16 +175,23 @@
<Story name="Confirmation Dialog">
<div>
<Button variant="destructive" onclick={() => (confirmOpen = true)}>Delete Item</Button>
<Dialog bind:open={confirmOpen} title="Delete Item">
<Dialog bind:open={confirmOpen}>
{#snippet children()}
<p>
Are you sure you want to delete this item? This action cannot be undone and all associated
data will be permanently removed.
</p>
{/snippet}
{#snippet footer()}
<Button variant="secondary" onclick={() => (confirmOpen = false)}>Cancel</Button>
<Button variant="destructive" onclick={() => (confirmOpen = false)}>Delete</Button>
<ModalHeader title="Delete Item" />
<ModalBody>
{#snippet children()}
<p>
Are you sure you want to delete this item? This action cannot be undone and all associated
data will be permanently removed.
</p>
{/snippet}
</ModalBody>
<ModalFooter>
{#snippet children()}
<Button variant="secondary" onclick={() => (confirmOpen = false)}>Cancel</Button>
<Button variant="destructive" onclick={() => (confirmOpen = false)}>Delete</Button>
{/snippet}
</ModalFooter>
{/snippet}
</Dialog>
</div>