Skip to content

Commit c4fb681

Browse files
committed
refactor: Extract TableActionMenu and ConfirmDeleteDialog
1 parent 93209fb commit c4fb681

16 files changed

Lines changed: 200 additions & 269 deletions
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<script lang="ts">
2+
import { Button } from '$lib/components/ui/button';
3+
import * as Dialog from '$lib/components/ui/dialog';
4+
import type { Snippet } from 'svelte';
5+
6+
interface Props {
7+
open: boolean;
8+
title: string;
9+
description: Snippet;
10+
/** Button label. Defaults to "Delete". */
11+
actionLabel?: string;
12+
onConfirm: () => void | Promise<void>;
13+
/** Optional extra content between the header and the action button. */
14+
children?: Snippet;
15+
}
16+
17+
let {
18+
open = $bindable(),
19+
title,
20+
description,
21+
actionLabel = 'Delete',
22+
onConfirm,
23+
children,
24+
}: Props = $props();
25+
</script>
26+
27+
<Dialog.Root bind:open>
28+
<Dialog.Content>
29+
<Dialog.Header>
30+
<Dialog.Title>{title}</Dialog.Title>
31+
<Dialog.Description>{@render description()}</Dialog.Description>
32+
</Dialog.Header>
33+
{@render children?.()}
34+
<Button variant="destructive" onclick={onConfirm}>{actionLabel}</Button>
35+
</Dialog.Content>
36+
</Dialog.Root>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<script lang="ts">
2+
import Ellipsis from '@lucide/svelte/icons/ellipsis';
3+
import { Button } from '$lib/components/ui/button';
4+
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
5+
import type { Snippet } from 'svelte';
6+
7+
interface Props {
8+
children?: Snippet;
9+
}
10+
11+
let { children }: Props = $props();
12+
</script>
13+
14+
<DropdownMenu.Root>
15+
<DropdownMenu.Trigger>
16+
{#snippet child({ props })}
17+
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
18+
<span class="sr-only">Open menu</span>
19+
<Ellipsis class="size-4" />
20+
</Button>
21+
{/snippet}
22+
</DropdownMenu.Trigger>
23+
<DropdownMenu.Content>
24+
{@render children?.()}
25+
</DropdownMenu.Content>
26+
</DropdownMenu.Root>
Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<script lang="ts">
2-
import Ellipsis from '@lucide/svelte/icons/ellipsis';
32
import { type ConfigurationItemDto } from '$lib/api/internal/v1';
4-
import { Button } from '$lib/components/ui/button';
3+
import TableActionMenu from '$lib/components/TableActionMenu.svelte';
54
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
65
import ItemDeleteDialog from './dialog-item-delete.svelte';
76
import ItemEditDialog from './dialog-item-edit.svelte';
@@ -20,17 +19,7 @@
2019
<ItemEditDialog bind:open={editDialogOpen} {item} onEdited={onChange} />
2120
<ItemDeleteDialog bind:open={deleteDialogOpen} {item} onDeleted={onChange} />
2221

23-
<DropdownMenu.Root>
24-
<DropdownMenu.Trigger>
25-
{#snippet child({ props })}
26-
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
27-
<span class="sr-only">Open menu</span>
28-
<Ellipsis class="size-4" />
29-
</Button>
30-
{/snippet}
31-
</DropdownMenu.Trigger>
32-
<DropdownMenu.Content>
33-
<DropdownMenu.Item onclick={() => (editDialogOpen = true)}>Edit</DropdownMenu.Item>
34-
<DropdownMenu.Item onclick={() => (deleteDialogOpen = true)}>Delete</DropdownMenu.Item>
35-
</DropdownMenu.Content>
36-
</DropdownMenu.Root>
22+
<TableActionMenu>
23+
<DropdownMenu.Item onclick={() => (editDialogOpen = true)}>Edit</DropdownMenu.Item>
24+
<DropdownMenu.Item onclick={() => (deleteDialogOpen = true)}>Delete</DropdownMenu.Item>
25+
</TableActionMenu>
Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
<script lang="ts">
22
import { adminApi } from '$lib/api';
33
import type { ConfigurationItemDto } from '$lib/api/internal/v1';
4-
import Button from '$lib/components/ui/button/button.svelte';
5-
import * as Dialog from '$lib/components/ui/dialog';
4+
import ConfirmDeleteDialog from '$lib/components/ConfirmDeleteDialog.svelte';
65
import { handleApiError } from '$lib/errorhandling/apiErrorHandling';
76
import { toast } from 'svelte-sonner';
87
@@ -20,22 +19,15 @@
2019
.then(() => {
2120
onDeleted();
2221
toast.success('Removed item');
23-
open = false;
2422
})
2523
.catch(handleApiError)
2624
.finally(() => (open = false));
2725
}
2826
</script>
2927

30-
<Dialog.Root bind:open={() => open, (o) => (open = o)}>
31-
<Dialog.Content>
32-
<Dialog.Header>
33-
<Dialog.Title>Delete configuration item</Dialog.Title>
34-
<Dialog.Description>
35-
Are you sure you want to delete <strong>{item.name}</strong>?<br />
36-
<strong>This action is irreversible.</strong>
37-
</Dialog.Description>
38-
</Dialog.Header>
39-
<Button variant="destructive" onclick={onSubmit}>Delete</Button>
40-
</Dialog.Content>
41-
</Dialog.Root>
28+
<ConfirmDeleteDialog bind:open title="Delete configuration item" onConfirm={onSubmit}>
29+
{#snippet description()}
30+
Are you sure you want to delete <strong>{item.name}</strong>?<br />
31+
<strong>This action is irreversible.</strong>
32+
{/snippet}
33+
</ConfirmDeleteDialog>
Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
<script lang="ts">
2-
import Ellipsis from '@lucide/svelte/icons/ellipsis';
32
import { goto } from '$app/navigation';
4-
import { Button } from '$lib/components/ui/button';
3+
import { resolve } from '$app/paths';
4+
import TableActionMenu from '$lib/components/TableActionMenu.svelte';
55
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
66
import { copyToClipboard } from '$lib/utils/clipboard.svelte';
77
import type { OnlineHub } from './columns';
8-
import { resolve } from '$app/paths';
98
109
interface Props {
1110
hub: OnlineHub;
@@ -17,22 +16,12 @@
1716
const copyUserId = () => copyToClipboard(hub.owner.id, 'User ID copied to clipboard');
1817
</script>
1918

20-
<DropdownMenu.Root>
21-
<DropdownMenu.Trigger>
22-
{#snippet child({ props })}
23-
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
24-
<span class="sr-only">Open menu</span>
25-
<Ellipsis class="size-4" />
26-
</Button>
27-
{/snippet}
28-
</DropdownMenu.Trigger>
29-
<DropdownMenu.Content>
30-
<DropdownMenu.Item onclick={copyId}>Copy ID</DropdownMenu.Item>
31-
<DropdownMenu.Item onclick={copyUserId}>Copy User ID</DropdownMenu.Item>
32-
<DropdownMenu.Item onclick={() => goto(resolve(`/admin/users/${hub.owner.id}`))}>
33-
View User
34-
</DropdownMenu.Item>
35-
<DropdownMenu.Item>Edit</DropdownMenu.Item>
36-
<DropdownMenu.Item>Delete</DropdownMenu.Item>
37-
</DropdownMenu.Content>
38-
</DropdownMenu.Root>
19+
<TableActionMenu>
20+
<DropdownMenu.Item onclick={copyId}>Copy ID</DropdownMenu.Item>
21+
<DropdownMenu.Item onclick={copyUserId}>Copy User ID</DropdownMenu.Item>
22+
<DropdownMenu.Item onclick={() => goto(resolve(`/admin/users/${hub.owner.id}`))}>
23+
View User
24+
</DropdownMenu.Item>
25+
<DropdownMenu.Item>Edit</DropdownMenu.Item>
26+
<DropdownMenu.Item>Delete</DropdownMenu.Item>
27+
</TableActionMenu>
Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<script lang="ts">
2-
import Ellipsis from '@lucide/svelte/icons/ellipsis';
32
import { type AdminUsersView, RoleType } from '$lib/api/internal/v1';
4-
import { Button } from '$lib/components/ui/button';
3+
import TableActionMenu from '$lib/components/TableActionMenu.svelte';
54
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
65
import { copyToClipboard } from '$lib/utils/clipboard.svelte';
76
import UserDeleteDialog from './dialog-user-delete.svelte';
@@ -25,26 +24,16 @@
2524
<UserEditDialog bind:open={editDialogOpen} {user} />
2625
<UserDeleteDialog bind:open={deleteDialogOpen} {user} />
2726

28-
<DropdownMenu.Root>
29-
<DropdownMenu.Trigger>
30-
{#snippet child({ props })}
31-
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
32-
<span class="sr-only">Open menu</span>
33-
<Ellipsis class="size-4" />
34-
</Button>
35-
{/snippet}
36-
</DropdownMenu.Trigger>
37-
<DropdownMenu.Content>
38-
<DropdownMenu.Item onclick={copyId}>Copy ID</DropdownMenu.Item>
39-
<DropdownMenu.Item onclick={() => (editDialogOpen = true)}>Edit</DropdownMenu.Item>
40-
<DropdownMenu.Item>Promote</DropdownMenu.Item>
41-
<DropdownMenu.Item>Reset password</DropdownMenu.Item>
42-
<DropdownMenu.Item
43-
onclick={() => (deleteDialogOpen = true)}
44-
disabled={isPrivileged}
45-
class={isPrivileged ? undefined : 'text-red-500'}
46-
>
47-
Delete
48-
</DropdownMenu.Item>
49-
</DropdownMenu.Content>
50-
</DropdownMenu.Root>
27+
<TableActionMenu>
28+
<DropdownMenu.Item onclick={copyId}>Copy ID</DropdownMenu.Item>
29+
<DropdownMenu.Item onclick={() => (editDialogOpen = true)}>Edit</DropdownMenu.Item>
30+
<DropdownMenu.Item>Promote</DropdownMenu.Item>
31+
<DropdownMenu.Item>Reset password</DropdownMenu.Item>
32+
<DropdownMenu.Item
33+
onclick={() => (deleteDialogOpen = true)}
34+
disabled={isPrivileged}
35+
class={isPrivileged ? undefined : 'text-red-500'}
36+
>
37+
Delete
38+
</DropdownMenu.Item>
39+
</TableActionMenu>
Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<script lang="ts">
22
import type { AdminUsersView } from '$lib/api/internal/v1';
3-
import { Button } from '$lib/components/ui/button';
4-
import * as Dialog from '$lib/components/ui/dialog';
3+
import ConfirmDeleteDialog from '$lib/components/ConfirmDeleteDialog.svelte';
54
65
interface Props {
76
open: boolean;
@@ -11,16 +10,10 @@
1110
let { open = $bindable<boolean>(), user }: Props = $props();
1211
</script>
1312

14-
<Dialog.Root bind:open={() => open, (o) => (open = o)}>
15-
<Dialog.Content>
16-
<Dialog.Header>
17-
<Dialog.Title>Delete user</Dialog.Title>
18-
<Dialog.Description>
19-
Are you sure you want to delete <strong>{user.name}</strong>?<br />
20-
All data associated with this user will be permanently deleted.<br />
21-
<strong>This action is irreversible.</strong>
22-
</Dialog.Description>
23-
</Dialog.Header>
24-
<Button variant="destructive" onclick={() => {}}>Delete</Button>
25-
</Dialog.Content>
26-
</Dialog.Root>
13+
<ConfirmDeleteDialog bind:open title="Delete user" onConfirm={() => {}}>
14+
{#snippet description()}
15+
Are you sure you want to delete <strong>{user.name}</strong>?<br />
16+
All data associated with this user will be permanently deleted.<br />
17+
<strong>This action is irreversible.</strong>
18+
{/snippet}
19+
</ConfirmDeleteDialog>
Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<script lang="ts">
2-
import Ellipsis from '@lucide/svelte/icons/ellipsis';
32
import { type WebhookDto } from '$lib/api/internal/v1';
4-
import { Button } from '$lib/components/ui/button';
3+
import TableActionMenu from '$lib/components/TableActionMenu.svelte';
54
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
65
import { copyToClipboard } from '$lib/utils/clipboard.svelte';
76
import WebhookDeleteDialog from './dialog-webhook-delete.svelte';
@@ -19,18 +18,8 @@
1918

2019
<WebhookDeleteDialog bind:open={deleteDialogOpen} {webhook} />
2120

22-
<DropdownMenu.Root>
23-
<DropdownMenu.Trigger>
24-
{#snippet child({ props })}
25-
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
26-
<span class="sr-only">Open menu</span>
27-
<Ellipsis class="size-4" />
28-
</Button>
29-
{/snippet}
30-
</DropdownMenu.Trigger>
31-
<DropdownMenu.Content>
32-
<DropdownMenu.Item onclick={copyId}>Copy ID</DropdownMenu.Item>
33-
<DropdownMenu.Item>Edit</DropdownMenu.Item>
34-
<DropdownMenu.Item onclick={() => (deleteDialogOpen = true)}>Delete</DropdownMenu.Item>
35-
</DropdownMenu.Content>
36-
</DropdownMenu.Root>
21+
<TableActionMenu>
22+
<DropdownMenu.Item onclick={copyId}>Copy ID</DropdownMenu.Item>
23+
<DropdownMenu.Item>Edit</DropdownMenu.Item>
24+
<DropdownMenu.Item onclick={() => (deleteDialogOpen = true)}>Delete</DropdownMenu.Item>
25+
</TableActionMenu>
Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
<script lang="ts">
22
import { adminApi } from '$lib/api';
33
import type { WebhookDto } from '$lib/api/internal/v1';
4-
import Button from '$lib/components/ui/button/button.svelte';
5-
import * as Dialog from '$lib/components/ui/dialog';
4+
import ConfirmDeleteDialog from '$lib/components/ConfirmDeleteDialog.svelte';
65
import { handleApiError } from '$lib/errorhandling/apiErrorHandling';
76
import { toast } from 'svelte-sonner';
87
@@ -18,22 +17,15 @@
1817
.adminRemoveWebhook(webhook.id)
1918
.then(() => {
2019
toast.success('Removed webhook');
21-
open = false;
2220
})
2321
.catch(handleApiError)
2422
.finally(() => (open = false));
2523
}
2624
</script>
2725

28-
<Dialog.Root bind:open={() => open, (o) => (open = o)}>
29-
<Dialog.Content>
30-
<Dialog.Header>
31-
<Dialog.Title>Delete webhook</Dialog.Title>
32-
<Dialog.Description>
33-
Are you sure you want to delete <strong>{webhook.name}</strong>?<br />
34-
<strong>This action is irreversible.</strong>
35-
</Dialog.Description>
36-
</Dialog.Header>
37-
<Button variant="destructive" onclick={onDeleteClicked}>Delete</Button>
38-
</Dialog.Content>
39-
</Dialog.Root>
26+
<ConfirmDeleteDialog bind:open title="Delete webhook" onConfirm={onDeleteClicked}>
27+
{#snippet description()}
28+
Are you sure you want to delete <strong>{webhook.name}</strong>?<br />
29+
<strong>This action is irreversible.</strong>
30+
{/snippet}
31+
</ConfirmDeleteDialog>

0 commit comments

Comments
 (0)