summaryrefslogtreecommitdiffhomepage
path: root/src/features/chat/ui/LazyCollapse.svelte
blob: 3ce34dd0c85b1e39f85eae9ea782356476ecb4c9 (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
<script lang="ts">
  import type { Snippet } from "svelte";

  /**
   * A daisyUI checkbox collapse whose BODY is rendered lazily: nothing inside
   * `.collapse-content` is created until the user first opens it. A long turn
   * has hundreds of collapsed tool/thinking cards, each holding a full JSON
   * input / tool output / thought — building all of that DOM up front (only to
   * be hidden) dominated first paint on slow devices. Once opened the body
   * stays mounted, so the close animation and any inner scroll state work as
   * before. Same markup + classes as the inline collapses it replaces.
   */
  let {
    label,
    titleClass = "",
    title,
    children,
  }: {
    /** aria-label of the toggle checkbox. */
    label: string;
    /** Extra classes on the `.collapse-title` row. */
    titleClass?: string;
    title: Snippet;
    children: Snippet;
  } = $props();

  let open = $state(false);
  let everOpened = $state(false);
  $effect(() => {
    if (open) everOpened = true;
  });
</script>

<!-- `.inset-well` (app.css): the recessed panel shared by thinking/tool blocks
     and the sidebar views. -->
<div class="collapse inset-well w-full text-sm">
  <input type="checkbox" aria-label={label} bind:checked={open} />
  <div class="collapse-title flex min-h-0 min-w-0 items-center gap-2 py-2 whitespace-nowrap font-medium {titleClass}">
    {@render title()}
  </div>
  <div class="collapse-content">
    {#if open || everOpened}
      {@render children()}
    {/if}
  </div>
</div>