partial toolbar implementation

This commit is contained in:
Elijah Duffy
2025-07-08 18:02:01 -07:00
parent 9d17fef232
commit e953c362cf
2 changed files with 117 additions and 0 deletions

94
src/lib/Toolbar.svelte Normal file
View File

@@ -0,0 +1,94 @@
<script lang="ts" module>
type ToolbarRenderable<P extends unknown[] = any[]> =
| { type: 'component'; component: Component; props?: P }
| { type: 'snippet'; snippet: Snippet<P>; props?: P }
| { type: 'string'; value: string };
interface ToolbarButton<P extends unknown[] = any[]> {
type: 'toggle' | 'button';
title: string;
render: ToolbarRenderable<P>;
}
interface ToolbarDivider {
type: 'divider';
}
interface ToolbarGroup {
type: 'group';
items: ToolbarItem[];
}
export type ToolbarItem = ToolbarButton<any> | ToolbarDivider | ToolbarGroup;
type SnippetArgs<T> = T extends Snippet<infer Args extends unknown[]> ? Args : never;
// For snippet-based buttons
export function toolbarItem<S extends Snippet<any>>(item: {
type: 'button' | 'toggle';
title: string;
render: {
type: 'snippet';
snippet: S;
props: SnippetArgs<S>;
};
}): typeof item;
// For component-based buttons (optional, for completeness)
export function toolbarItem<C extends Component>(item: {
type: 'button' | 'toggle';
title: string;
render: {
type: 'component';
component: C;
props: ComponentProps<C>;
};
}): typeof item;
// For other items (divider, group, etc)
export function toolbarItem<T extends ToolbarDivider | ToolbarGroup>(item: T): T;
// Implementation
export function toolbarItem(item: any): any {
return item;
}
</script>
<script lang="ts">
import { type Component, type Snippet, type ComponentProps } from 'svelte';
interface Props {
items: ToolbarItem[];
}
let { items }: Props = $props();
</script>
<div>
{#each items as toolbarItem}
{@render item(toolbarItem)}
{/each}
</div>
{#snippet item(i: ToolbarItem)}
{#if i.type === 'button' || i.type === 'toggle'}
<button class="toolbar-button">
{#if i.render.type === 'component'}
<item.render.component {...i.render.props} />
{:else if i.render.type === 'snippet'}
{@render i.render.snippet(i.render.props)}
{:else if i.render.type === 'string'}
<span>{i.render.value}</span>
{/if}
<span>{i.title}</span>
</button>
{:else if i.type === 'divider'}
<hr class="toolbar-divider" />
{:else if i.type === 'group'}
<div class="toolbar-group">
{#each i.items as groupItem}
{@render item(groupItem)}
{/each}
</div>
{/if}
{/snippet}

View File

@@ -17,6 +17,9 @@
import TimezoneInput from '$lib/TimezoneInput.svelte';
import ToggleGroup from '$lib/ToggleGroup.svelte';
import ToggleSelect from '$lib/ToggleSelect.svelte';
import Toolbar, { toolbarItem } from '$lib/Toolbar.svelte';
import { BowlFood } from 'phosphor-svelte';
let dateInputValue = $state<Date | undefined>(undefined);
let checkboxValue = $state<CheckboxState>('indeterminate');
@@ -202,6 +205,26 @@
<ToggleSelect name="example-toggle-select">Toggle Me!</ToggleSelect>
</div>
<div class="component">
<p class="title">Toolbar</p>
<Toolbar
items={[
toolbarItem({
type: 'button',
title: 'Button Action',
render: {
type: 'component',
component: BowlFood,
props: {
size: '1.5em'
}
}
})
]}
/>
</div>
<Dialog
bind:open={dialogOpen}
title="Dialog Title"