refactor(mana/web): extract QuickActionsList molecule shared by both widget variants

Two QuickActionsWidget files lived in parallel under different widget
systems — `dashboard/widgets/` (the user-customizable dashboard, i18n
keys, 3 actions: credits/feedback/profile) and `core/widgets/` (the
mana home screen, hardcoded German strings, 5 actions: todo/calendar/
contacts/context/times). The two rendered the same shape character-
for-character: optional emoji-prefixed title + a list of rounded-card
links each with icon + label + description. Only the data and a
slightly different padding/icon sizing differed.

Extract <QuickActionsList> in $lib/components that takes the actions
array directly (consumers resolve i18n before passing in). Both widget
files become thin wrappers — the dashboard one resolves $_(...) keys
and passes the result, the core one passes its hardcoded data with
`compact` set.

LOC: 110 → 102 across the 3 files (-8 net, plus the shared 70-LOC
molecule). Small numerically, but the bigger win is that future
changes to the link layout (hover state, padding, icon style) happen
once instead of twice — and the two widget files no longer accidentally
drift in sizing/spacing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-08 23:02:09 +02:00
parent 8772a9b9e1
commit 96023394b5
3 changed files with 97 additions and 42 deletions

View file

@ -0,0 +1,75 @@
<!--
QuickActionsList — shared shell for "list of clickable shortcut links" widgets.
Both `dashboard/widgets/QuickActionsWidget` and `core/widgets/QuickActionsWidget`
rendered the same shape: optional title (with emoji prefix) + a list of
rounded-card links, each with an emoji/icon, a label and a description.
They differed only in the data and a slightly different padding/icon size
(the dashboard one is roomier; the core one is compact).
This molecule takes the actions array directly. Consumers that need i18n
resolve the keys before passing the data in.
@example
```svelte
<QuickActionsList
title="Schnellzugriff"
actions={[
{ href: '/todo', icon: '✅', label: 'Neue Aufgabe', description: 'Aufgabe erstellen' },
...
]}
compact
/>
```
-->
<script lang="ts">
export interface QuickAction {
href: string;
/** Emoji glyph or HTML entity (rendered with {@html}). Use a Phosphor icon snippet via the consumer's wrapper if you need a real component. */
icon: string;
label: string;
description: string;
}
interface Props {
actions: QuickAction[];
/** Compact mode: smaller padding, smaller icon, smaller text. Used by core/widgets. */
compact?: boolean;
/** Optional heading rendered above the list. */
title?: string;
/** Optional emoji glyph rendered before the title. */
titleIcon?: string;
}
let { actions, compact = false, title, titleIcon }: Props = $props();
</script>
<div>
{#if title}
<div class="mb-3">
<h3 class="flex items-center gap-2 text-lg font-semibold">
{#if titleIcon}<span>{@html titleIcon}</span>{/if}
{title}
</h3>
</div>
{/if}
<div class={compact ? 'space-y-1' : 'space-y-2'}>
{#each actions as action}
<a
href={action.href}
class="flex items-center gap-3 rounded-lg transition-colors hover:bg-surface-hover {compact
? 'p-2.5'
: 'p-3'}"
>
<span class={compact ? 'text-xl' : 'text-2xl'}>{@html action.icon}</span>
<div class="min-w-0">
<p class="font-medium {compact ? 'text-sm' : ''}">{action.label}</p>
<p class="text-muted-foreground {compact ? 'text-xs' : 'text-sm'}">
{action.description}
</p>
</div>
</a>
{/each}
</div>
</div>

View file

@ -1,9 +1,12 @@
<script lang="ts">
/**
* QuickActionsWidget - Quick action links
* QuickActionsWidget - Quick action links (dashboard variant, i18n).
*
* Thin wrapper around <QuickActionsList>. Resolves i18n keys to literal
* strings before passing the actions array down.
*/
import { _ } from 'svelte-i18n';
import QuickActionsList, { type QuickAction } from '$lib/components/QuickActionsList.svelte';
const actions = [
{
@ -25,25 +28,19 @@
descKey: 'dashboard.widgets.quick_actions.profile_desc',
},
];
let resolved = $derived<QuickAction[]>(
actions.map((a) => ({
href: a.href,
icon: a.icon,
label: $_(a.labelKey),
description: $_(a.descKey),
}))
);
</script>
<div>
<h3 class="mb-3 flex items-center gap-2 text-lg font-semibold">
<span></span>
{$_('dashboard.widgets.quick_actions.title')}
</h3>
<div class="space-y-2">
{#each actions as action}
<a href={action.href} class="block rounded-lg p-3 transition-colors hover:bg-surface-hover">
<div class="flex items-center">
<span class="text-2xl">{action.icon}</span>
<div class="ml-3">
<p class="font-medium">{$_(action.labelKey)}</p>
<p class="text-sm text-muted-foreground">{$_(action.descKey)}</p>
</div>
</div>
</a>
{/each}
</div>
</div>
<QuickActionsList
title={$_('dashboard.widgets.quick_actions.title')}
titleIcon="⚡"
actions={resolved}
/>

View file

@ -3,7 +3,9 @@
* QuickActionsWidget — Schnellzugriff-Buttons für häufige Aktionen.
*
* Navigiert direkt zu den jeweiligen App-Routen innerhalb der Unified App.
* Thin wrapper around <QuickActionsList> in compact mode.
*/
import QuickActionsList from '$lib/components/QuickActionsList.svelte';
const actions = [
{
@ -39,23 +41,4 @@
];
</script>
<div>
<div class="mb-3">
<h3 class="flex items-center gap-2 text-lg font-semibold">Schnellzugriff</h3>
</div>
<div class="space-y-1">
{#each actions as action}
<a
href={action.href}
class="flex items-center gap-3 rounded-lg p-2.5 transition-colors hover:bg-surface-hover"
>
<span class="text-xl">{@html action.icon}</span>
<div>
<p class="text-sm font-medium">{action.label}</p>
<p class="text-xs text-muted-foreground">{action.description}</p>
</div>
</a>
{/each}
</div>
</div>
<QuickActionsList title="Schnellzugriff" {actions} compact />