feat(fend): adds record cards to each record section

This commit is contained in:
Lewis Wynne 2026-03-23 18:57:09 +00:00
parent 973541e24f
commit 85b87f8140
3 changed files with 81 additions and 24 deletions

View file

@ -0,0 +1,58 @@
<script lang="ts">
import { ChevronDown } from 'lucide-svelte';
import type { RecordDef } from '$lib/types';
import DynamicField from './fields/DynamicField.svelte';
import { slugify } from '$lib/utils/slugify';
let { record, data, onFieldChange }: {
record: RecordDef;
data: Record<string, unknown>;
onFieldChange: (key: string, value: any) => void;
} = $props();
let expanded = $state(false);
let filled = $derived(
record.fields.filter((f) => {
const v = data[slugify(f.label)];
if (v === undefined || v === null || v === '' || v === 0) return false;
if (Array.isArray(v) && v.length === 0) return false;
return true;
}).length
);
</script>
<section class="rounded border" style="border-color: var(--border); background: var(--bg-card);">
<button
onclick={() => { expanded = !expanded; }}
class="flex items-center w-full px-4 py-3 text-left gap-3"
>
<ChevronDown
size={16}
class="transition-transform shrink-0"
style="transform: rotate({expanded ? '0' : '-90'}deg); color: var(--text-muted);"
/>
<div class="flex-1 min-w-0">
<span class="font-medium capitalize">{record.type}</span>
{#if record.preamble}
<span class="block text-xs truncate" style="color: var(--text-muted);">{record.preamble}</span>
{/if}
</div>
<span class="text-sm tabular-nums" style="color: var(--text-muted);">
{filled}/{record.fields.length}
</span>
</button>
{#if expanded}
<div class="px-4 pb-4 flex flex-col gap-4">
{#each record.fields as field}
<DynamicField
{field}
value={data[slugify(field.label)]}
{data}
onChange={(v) => onFieldChange(slugify(field.label), v)}
/>
{/each}
</div>
{/if}
</section>

View file

@ -0,0 +1,20 @@
<script lang="ts">
import type { Character } from '$lib/types';
import { roster } from '$lib/state.svelte';
import RecordCard from './RecordCard.svelte';
let { character }: { character: Character } = $props();
</script>
<div class="flex flex-col gap-4">
{#each character.template.records as record}
<RecordCard
{record}
data={character.data}
onFieldChange={(key, value) => {
character.data[key] = value;
roster.scheduleSave(character);
}}
/>
{/each}
</div>

View file

@ -1,9 +1,8 @@
<script lang="ts"> <script lang="ts">
import Header from '$lib/components/Header.svelte'; import Header from '$lib/components/Header.svelte';
import DynamicField from '$lib/components/fields/DynamicField.svelte'; import SchemaForm from '$lib/components/SchemaForm.svelte';
import { roster } from '$lib/state.svelte'; import { roster } from '$lib/state.svelte';
import { presets } from '$lib/presets'; import { presets } from '$lib/presets';
import { slugify } from '$lib/utils/slugify';
</script> </script>
<div class="min-h-screen flex flex-col"> <div class="min-h-screen flex flex-col">
@ -11,28 +10,8 @@
<main class="flex-1 p-4"> <main class="flex-1 p-4">
{#if roster.active} {#if roster.active}
{@const char = roster.active} <div class="max-w-xl mx-auto">
<div class="max-w-xl mx-auto flex flex-col gap-6"> <SchemaForm character={roster.active} />
{#each char.template.records as record}
<section>
<h2 class="text-sm font-semibold uppercase tracking-wide mb-3" style="color: var(--text-muted);">
{record.type}
</h2>
<div class="flex flex-col gap-4">
{#each record.fields as field}
<DynamicField
{field}
value={char.data[slugify(field.label)]}
data={char.data}
onChange={(v) => {
char.data[slugify(field.label)] = v;
roster.scheduleSave(char);
}}
/>
{/each}
</div>
</section>
{/each}
</div> </div>
{:else} {:else}
<div class="flex flex-col items-center justify-center gap-4 h-full min-h-[60vh]"> <div class="flex flex-col items-center justify-center gap-4 h-full min-h-[60vh]">