summaryrefslogtreecommitdiffhomepage
path: root/src/features/views/ui/ViewSidebar.svelte
blob: e9e8682273ae67b336a6e64b0af44c2ec16edfd6 (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
<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>