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

View file

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

View file

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

View file

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