feat(frontend): add primitive UI components (Icon, StatusPill, UnderlineStroke, StatCard, Tally, Field)

This commit is contained in:
2026-04-28 15:08:50 +02:00
parent 97a3c2196b
commit 04155c182a
6 changed files with 157 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
<script lang="ts">
const { label, value = '', mono = false, suffix, readonly = false } = $props<{
label: string;
value?: string;
mono?: boolean;
suffix?: string;
readonly?: boolean;
}>();
</script>
<div style="display:flex;flex-direction:column;gap:3px">
<span class="tiny" style="color:var(--ink-3)">{label}</span>
<div style="display:flex;align-items:center;gap:6px">
<input
class="input"
style={mono ? 'font-family:var(--mono);font-size:12px' : ''}
value={value}
readonly={readonly}
/>
{#if suffix}
<span class="tiny" style="color:var(--ink-4)">{suffix}</span>
{/if}
</div>
</div>

View File

@@ -0,0 +1,52 @@
<script lang="ts">
const { name, size = 14 } = $props<{
name: 'check'|'x'|'lock'|'open'|'copy'|'edit'|'download'|'arrow'|'search'|'plus';
size?: number;
}>();
</script>
{#if name === 'check'}
<svg width={size} height={size} viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<path d="M2.5 7.5 L5.5 10.5 L11.5 3.5"/>
</svg>
{:else if name === 'x'}
<svg width={size} height={size} viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round">
<path d="M3 3 L9 9 M9 3 L3 9"/>
</svg>
{:else if name === 'lock'}
<svg width={size} height={size} viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.3">
<rect x="2.5" y="6" width="8" height="5.5" rx="1"/>
<path d="M4.5 6 V4.5 a2 2 0 0 1 4 0 V6"/>
</svg>
{:else if name === 'open'}
<svg width={size} height={size} viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.3">
<rect x="2.5" y="6" width="8" height="5.5" rx="1"/>
<path d="M4.5 6 V4.5 a2 2 0 0 1 4 0"/>
</svg>
{:else if name === 'copy'}
<svg width={size} height={size} viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.2">
<rect x="2" y="2" width="7" height="7" rx="1"/>
<rect x="4.5" y="4.5" width="7" height="7" rx="1"/>
</svg>
{:else if name === 'edit'}
<svg width={size} height={size} viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round">
<path d="M2 11 L2 9 L9 2 L11 4 L4 11 Z"/>
</svg>
{:else if name === 'download'}
<svg width={size} height={size} viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
<path d="M7 2 V9 M3.5 6 L7 9 L10.5 6 M2.5 11.5 H11.5"/>
</svg>
{:else if name === 'arrow'}
<svg width={size} height={size} viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6 H9 M6.5 3 L9 6 L6.5 9"/>
</svg>
{:else if name === 'search'}
<svg width={size} height={size} viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.3">
<circle cx="5.5" cy="5.5" r="3.5"/>
<path d="M8 8 L11 11" stroke-linecap="round"/>
</svg>
{:else if name === 'plus'}
<svg width={size} height={size} viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round">
<path d="M6 2 V10 M2 6 H10"/>
</svg>
{/if}

View File

@@ -0,0 +1,24 @@
<script lang="ts">
const { label, value, suffix, hint, accent } = $props<{
label: string;
value: string | number;
suffix?: string;
hint?: string;
accent?: string;
}>();
</script>
<div class="card" style="padding: 16px 20px">
<span class="eyebrow">{label}</span>
<div style="display:flex;align-items:baseline;gap:6px;margin-top:8px">
<span style="font-family:var(--serif);font-size:32px;font-weight:500;line-height:1;color:{accent || 'var(--ink)'}">
{value}
</span>
{#if suffix}
<span class="tiny" style="color:var(--ink-4)">{suffix}</span>
{/if}
</div>
{#if hint}
<div class="tiny" style="color:var(--ink-4);margin-top:4px">{hint}</div>
{/if}
</div>

View File

@@ -0,0 +1,15 @@
<script lang="ts">
const { status } = $props<{
status: 'open' | 'closed' | 'locked' | 'present' | 'absent';
}>();
const labels: Record<string, string> = {
open: 'OFFEN',
closed: 'GESCHL.',
locked: 'GESPERRT',
present: 'ANWESEND',
absent: 'FEHLT',
};
</script>
<span class="pill {status}"><span class="dot"></span>{labels[status]}</span>

View File

@@ -0,0 +1,21 @@
<script lang="ts">
const { label, value, total, suffix, accent } = $props<{
label: string;
value: string | number;
total?: number;
suffix?: string;
accent?: string;
}>();
</script>
<div style="display:flex;flex-direction:column;gap:2px">
<span class="eyebrow">{label}</span>
<span style="font-family:var(--serif);font-size:22px;font-weight:500;color:{accent || 'var(--ink)'}">
{value}
{#if total !== undefined}
<span class="tiny" style="color:var(--ink-4)"> / {total}</span>
{:else if suffix}
<span class="tiny" style="color:var(--ink-4)"> {suffix}</span>
{/if}
</span>
</div>

View File

@@ -0,0 +1,21 @@
<script lang="ts">
const { width = 110, color = 'var(--accent)' } = $props<{
width?: number;
color?: string;
}>();
</script>
<svg
width={width}
height="8"
viewBox="0 0 {width} 8"
fill="none"
style="display:block;margin-top:2px"
>
<path
d="M 2 5 Q {width * 0.25} 1 {width * 0.5} 4 T {width - 2} 3"
stroke={color}
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>