mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-29 01:17:36 -05:00
ui : animate the family rows and hand the sections their own state
Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
let { children, open }: Props = $props();
|
||||
|
||||
// rows stay mounted through the collapse transition so it can play
|
||||
const EXPAND_TRANSITION_MS = 200;
|
||||
// the initial state only: the effect below owns it afterwards
|
||||
// svelte-ignore state_referenced_locally
|
||||
let contentMounted = $state(open);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
contentMounted = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => (contentMounted = false), EXPAND_TRANSITION_MS);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="collapsible-region" data-expanded={open}>
|
||||
{#if contentMounted}
|
||||
<div class="collapsible-region-content">
|
||||
{@render children()}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/*
|
||||
* The region animates between height 0 and height auto. `interpolate-size`
|
||||
* lets the auto keyword take part in the interpolation, so the content needs
|
||||
* no measured height to stay in sync with. Browsers without it fall back to
|
||||
* an interpolating grid row, which snaps only in the oldest engines.
|
||||
*/
|
||||
.collapsible-region {
|
||||
height: 0;
|
||||
/* clip, not hidden: hidden would make the region a scrollport, and the
|
||||
sticky rows inside it would then never leave their opening position */
|
||||
overflow: clip;
|
||||
visibility: hidden;
|
||||
interpolate-size: allow-keywords;
|
||||
transition:
|
||||
height 200ms cubic-bezier(0.23, 1, 0.32, 1),
|
||||
visibility 200ms;
|
||||
}
|
||||
|
||||
.collapsible-region[data-expanded='true'] {
|
||||
height: auto;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@supports not (interpolate-size: allow-keywords) {
|
||||
.collapsible-region {
|
||||
display: grid;
|
||||
/* minmax(0, 1fr) pins the column to the region width: a plain auto
|
||||
column would size to the content and push wide rows out of the list */
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: 0fr;
|
||||
/* the row owns the height here: leaving height: 0 in place would snap
|
||||
the region shut before the row could interpolate */
|
||||
height: auto;
|
||||
transition:
|
||||
grid-template-rows 200ms cubic-bezier(0.23, 1, 0.32, 1),
|
||||
visibility 200ms;
|
||||
}
|
||||
|
||||
.collapsible-region[data-expanded='true'] {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
.collapsible-region-content {
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: clip;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import CollapsibleRegion from './CollapsibleRegion.svelte';
|
||||
import { ChevronDown, ChevronUp } from '@lucide/svelte';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
@@ -11,7 +12,10 @@
|
||||
trigger: Snippet;
|
||||
/** Render the trigger as a dropdown menu item, so menu keyboard navigation reaches it. */
|
||||
inMenu?: boolean;
|
||||
/** Start expanded. */
|
||||
/** Start expanded. The section owns the state, so a parent rebuild cannot
|
||||
* snap it back open. Bind `open` instead to control it from outside. */
|
||||
defaultOpen?: boolean;
|
||||
/** Controlled open state, for a caller that binds it. */
|
||||
open?: boolean;
|
||||
/** Hide the chevron while expanded, until the trigger is hovered. */
|
||||
revealChevronOnHover?: boolean;
|
||||
@@ -23,33 +27,15 @@
|
||||
|
||||
let {
|
||||
children,
|
||||
defaultOpen = true,
|
||||
inMenu = false,
|
||||
open = $bindable(true),
|
||||
open = $bindable(defaultOpen),
|
||||
revealChevronOnHover = false,
|
||||
trigger,
|
||||
triggerClass = '',
|
||||
triggerPosition = 'top',
|
||||
triggerStyle = ''
|
||||
}: Props = $props();
|
||||
|
||||
// rows stay mounted through the collapse transition so it can play; after it
|
||||
// they unmount - mounted rows are menu items and would pollute the arrow-key
|
||||
// navigation
|
||||
// the region animates between height 0 and auto; see the component styles
|
||||
const EXPAND_TRANSITION_MS = 200;
|
||||
let contentMounted = $state(open);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
contentMounted = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => (contentMounted = false), EXPAND_TRANSITION_MS);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
});
|
||||
</script>
|
||||
|
||||
{#snippet chevron()}
|
||||
@@ -107,13 +93,11 @@
|
||||
{#snippet region()}
|
||||
<!-- Custom expand region instead of bits-ui Collapsible (whose conditional
|
||||
rendering kills the transition). -->
|
||||
<div class="collapsible-region" data-expanded={open}>
|
||||
{#if contentMounted}
|
||||
<div class="collapsible-region-content">
|
||||
{@render children()}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#snippet body()}
|
||||
{@render children()}
|
||||
{/snippet}
|
||||
|
||||
<CollapsibleRegion {open}>{@render body()}</CollapsibleRegion>
|
||||
{/snippet}
|
||||
|
||||
{#if triggerPosition === 'top'}
|
||||
@@ -125,54 +109,3 @@
|
||||
|
||||
{@render triggerButton()}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
/*
|
||||
* The region animates between height 0 and height auto. `interpolate-size`
|
||||
* lets the auto keyword take part in the interpolation, so the content needs
|
||||
* no measured height to stay in sync with. Browsers without it fall back to
|
||||
* an interpolating grid row, which snaps only in the oldest engines.
|
||||
*/
|
||||
.collapsible-region {
|
||||
height: 0;
|
||||
/* clip, not hidden: hidden would make the region a scrollport, and the
|
||||
sticky rows inside it would then never leave their opening position */
|
||||
overflow: clip;
|
||||
visibility: hidden;
|
||||
interpolate-size: allow-keywords;
|
||||
transition:
|
||||
height 200ms cubic-bezier(0.23, 1, 0.32, 1),
|
||||
visibility 200ms;
|
||||
}
|
||||
|
||||
.collapsible-region[data-expanded='true'] {
|
||||
height: auto;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@supports not (interpolate-size: allow-keywords) {
|
||||
.collapsible-region {
|
||||
display: grid;
|
||||
/* minmax(0, 1fr) pins the column to the region width: a plain auto
|
||||
column would size to the content and push wide rows out of the list */
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: 0fr;
|
||||
/* the row owns the height here: leaving height: 0 in place would snap
|
||||
the region shut before the row could interpolate */
|
||||
height: auto;
|
||||
transition:
|
||||
grid-template-rows 200ms cubic-bezier(0.23, 1, 0.32, 1),
|
||||
visibility 200ms;
|
||||
}
|
||||
|
||||
.collapsible-region[data-expanded='true'] {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
.collapsible-region-content {
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: clip;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
</script>
|
||||
|
||||
<script generics="G, E" lang="ts">
|
||||
import CollapsibleRegion from './CollapsibleRegion.svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
||||
|
||||
@@ -120,7 +121,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if expanded}
|
||||
<CollapsibleRegion open={expanded}>
|
||||
{#each entry.rows as row (keyOf(row))}
|
||||
{@render item({ depth: 1, entry: row })}
|
||||
{/each}
|
||||
@@ -132,7 +133,7 @@
|
||||
unit: 'entries'
|
||||
})}
|
||||
{/if}
|
||||
{/if}
|
||||
</CollapsibleRegion>
|
||||
{/each}
|
||||
|
||||
{#if windowed.length < (groups?.length ?? 0) && more}
|
||||
|
||||
+1
-1
@@ -548,9 +548,9 @@
|
||||
<ModelsSection
|
||||
backendId={group.kind === 'provider' ? (group.backendId ?? undefined) : undefined}
|
||||
count={group.items.length}
|
||||
defaultOpen={group.kind !== 'hidden'}
|
||||
icon={group.kind === 'provider' ? undefined : groupIcon}
|
||||
label={group.label}
|
||||
open={group.kind !== 'hidden'}
|
||||
sticky
|
||||
stickyClass="sticky z-10 bg-muted/90 backdrop-blur-lg"
|
||||
>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
/** Renders the back control, for a drilled-in provider. */
|
||||
onBack?: () => void;
|
||||
/** Start expanded; the manager collapses its hidden block. */
|
||||
open?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
revealChevronOnHover?: boolean;
|
||||
sectionHeaderClass?: string;
|
||||
/** Sticky class of the header, so a surface can shade it differently. */
|
||||
@@ -33,12 +33,12 @@
|
||||
backendId,
|
||||
children,
|
||||
count,
|
||||
defaultOpen = true,
|
||||
error = false,
|
||||
icon,
|
||||
label,
|
||||
loading = false,
|
||||
onBack,
|
||||
open = true,
|
||||
revealChevronOnHover = false,
|
||||
sectionHeaderClass = 'm-0 px-2 py-2 text-[13px] font-semibold text-muted-foreground select-none',
|
||||
sticky = false,
|
||||
@@ -52,7 +52,7 @@
|
||||
let triggerStyle = $derived(sticky ? 'top: var(--dropdown-sticky-height, 0px)' : '');
|
||||
</script>
|
||||
|
||||
<CollapsibleSection {open} {revealChevronOnHover} {triggerClass} {triggerStyle}>
|
||||
<CollapsibleSection {defaultOpen} {revealChevronOnHover} {triggerClass} {triggerStyle}>
|
||||
{#snippet trigger()}
|
||||
{#if onBack}
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user