feat: widgets and ai chat

This commit is contained in:
Rotimi Best
2026-04-27 11:40:38 +01:00
parent ae1801f85f
commit 5717dd8004
391 changed files with 28177 additions and 22467 deletions
@@ -0,0 +1,70 @@
<script lang="ts">
import { cn } from '../../tools';
import CheckboxOptionCard from './checkbox-option-card.svelte';
export interface CheckboxOptionCardOption {
id: string;
title: string;
description?: string;
value: string;
disabled?: boolean;
}
type SnippetWithOption = import('svelte').Snippet<[option: CheckboxOptionCardOption]>;
interface Props {
options: CheckboxOptionCardOption[];
/** Bound array of selected option values */
value?: string[];
/** Overridable CSS class for the grid container (e.g. grid-cols-2, md:grid-cols-3) */
class?: string;
titleSuffix?: SnippetWithOption;
leading?: SnippetWithOption;
footer?: SnippetWithOption;
}
let {
options,
value = $bindable([]),
class: className,
titleSuffix: parentTitleSuffix,
leading: parentLeading,
footer: parentFooter
}: Props = $props();
function toggle(optionValue: string, isChecked: boolean) {
if (isChecked) {
if (value.includes(optionValue)) {
return;
}
value = [...value, optionValue];
return;
}
value = value.filter((entry) => entry !== optionValue);
}
</script>
<div role="group" data-slot="checkbox-option-card-group" class={cn('ui:grid ui:gap-3', className)}>
{#each options as option (option.id)}
<CheckboxOptionCard
id={option.id}
title={option.title}
description={option.description}
checked={value.includes(option.value)}
disabled={option.disabled}
onCheckedChange={(isChecked) => toggle(option.value, isChecked)}
>
{#snippet titleSuffix()}
{@render parentTitleSuffix?.(option)}
{/snippet}
{#snippet leading()}
{@render parentLeading?.(option)}
{/snippet}
{#snippet footer()}
{@render parentFooter?.(option)}
{/snippet}
</CheckboxOptionCard>
{/each}
</div>
@@ -0,0 +1,62 @@
<script lang="ts">
import { Checkbox } from '../../base/checkbox';
import * as Field from '../../base/field';
interface Props {
id: string;
title: string;
description?: string;
checked: boolean;
disabled?: boolean;
onCheckedChange?: (checked: boolean) => void;
titleSuffix?: import('svelte').Snippet;
leading?: import('svelte').Snippet;
footer?: import('svelte').Snippet;
/** Overridable CSS class (e.g. for grid column span or custom layout) */
class?: string;
}
let {
id,
title,
description,
checked,
disabled = false,
onCheckedChange,
titleSuffix,
leading,
footer,
class: className
}: Props = $props();
</script>
<Field.Label for={id} class={className}>
<Field.Field orientation="horizontal">
{#if leading}
{@render leading()}
{/if}
<Field.Content>
<Field.Title class="ui:flex ui:items-center ui:gap-2">
{title}
{#if titleSuffix}
{@render titleSuffix()}
{/if}
</Field.Title>
{#if description}
<Field.Description>{description}</Field.Description>
{/if}
</Field.Content>
<Checkbox
{id}
aria-label={title}
{disabled}
{checked}
onCheckedChange={(value) => onCheckedChange?.(value === true)}
/>
</Field.Field>
{#if footer}
<div class="-mt-1 min-w-0 px-4 pb-3">
{@render footer()}
</div>
{/if}
</Field.Label>
@@ -0,0 +1,6 @@
import CheckboxOptionCard from './checkbox-option-card.svelte';
import CheckboxOptionCardGroup from './checkbox-option-card-group.svelte';
export { CheckboxOptionCard, CheckboxOptionCardGroup };
export type { CheckboxOptionCardOption } from './checkbox-option-card-group.svelte';
export { CheckboxOptionCard as default };