summaryrefslogtreecommitdiffhomepage
path: root/src/features/views/ui/ViewSidebar.svelte
blob: e4a3ee6583bad864aab8bc1ce1e5f3e8dba10bf9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<script lang="ts">
	import { type Snippet, untrack } from "svelte";
	import {
		addPanel,
		initialPanels,
		type PanelsState,
		removePanel,
		selectKind,
	} from "../logic/panels";

	interface ViewKind {
		readonly id: string;
		readonly label: string;
	}

	let {
		kinds,
		content,
		initial,
		onChange,
	}: {
		/** The view kinds offered in every panel's dropdown. */
		kinds: readonly ViewKind[];
		/** Renders a panel body for the given (non-null) view-kind id. */
		content: Snippet<[string]>;
		/** Optional seed of panel kinds; defaults to one panel of the first kind. */
		initial?: readonly (string | null)[];
		/** Called whenever the panel layout changes (add/remove/select). */
		onChange?: (kinds: readonly (string | null)[]) => void;
	} = $props();

	// Local UI composition state, owned by this unit and folded through the pure
	// reducer — never reached from elsewhere (no ambient store). Seeded ONCE from
	// the props (untrack makes that one-time read explicit, not reactive).
	let state = $state<PanelsState>(
		untrack(() => initialPanels(initial ?? [kinds[0]?.id ?? null])),
	);

	function notify(): void {
		onChange?.(state.panels.map((p) => p.kind));
	}
</script>

<div class="flex min-h-0 flex-col gap-2">
	{#each state.panels as panel, idx (panel.id)}
		<div class="flex flex-col rounded-lg bg-base-200 p-3">
			<div class="flex items-center gap-1">
				<select
					class="select select-bordered select-sm flex-1"
					aria-label="Select a view"
					value={panel.kind ?? ""}
					onchange={(e) => {
						const v = e.currentTarget.value;
						state = selectKind(state, panel.id, v === "" ? null : v);
						notify();
					}}
				>
					<option value="" disabled>Select a view</option>
					{#each kinds as kind (kind.id)}
						<option value={kind.id}>{kind.label}</option>
					{/each}
				</select>
				{#if idx > 0}
					<button
						type="button"
						class="btn btn-square btn-ghost btn-sm shrink-0"
						aria-label="Remove view"
						onclick={() => {
							state = removePanel(state, panel.id);
							notify();
						}}
					>
						✕
					</button>
				{/if}
			</div>

			{#if panel.kind !== null}
				<div class="mt-2">
					{@render content(panel.kind)}
				</div>
			{/if}
		</div>
	{/each}

	<button
		type="button"
		class="btn w-full border-none bg-base-200 text-lg hover:bg-base-300"
		aria-label="Add view"
		onclick={() => {
			state = addPanel(state);
			notify();
		}}
	>
		+
	</button>
</div>