feat: better components

This commit is contained in:
2025-05-12 11:27:33 -04:00
parent 398ddde169
commit cdeaa13d92
135 changed files with 10487 additions and 2088 deletions

View File

@ -0,0 +1,31 @@
import { getContext, hasContext, setContext } from 'svelte';
type Item = {
id: string;
name: string;
}
const key = 'form';
export function setFormContext(id: string, name: string) {
const item = getFormContext();
if (!item) {
const item: Item = $state({
id,
name,
});
setContext(key, item);
return;
}
item.id = id;
item.name = name;
}
export function getFormContext() {
if (!hasContext(key)) {
return null;
}
return getContext(key) as Item;
}

View File

@ -0,0 +1,30 @@
<script lang="ts">
import { cn } from '$lib/utils';
import { getFormContext } from './context.svelte';
import type { WithElementRef, WithoutChildren } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
import type { Violation } from '@bufbuild/protovalidate';
import type { ConnectError } from '@connectrpc/connect';
type Props = WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> & {
errors?: Violation[] | ConnectError;
};
let {
ref = $bindable(null),
class: className,
errors = $bindable(),
...restProps
}: Props = $props();
const item = getFormContext();
</script>
<div bind:this={ref} class={cn('text-red text-sm', className)} {...restProps}>
{#if errors && Array.isArray(errors)}
{#each errors as error}
<label for={item?.id}>{error.message}</label>
{/each}
{:else if errors}
<span>{errors.message}</span>
{/if}
</div>

View File

@ -0,0 +1,21 @@
<script lang="ts">
import { cn } from '$lib/utils';
import { setFormContext } from './context.svelte';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
type Props = WithElementRef<HTMLAttributes<HTMLDivElement>> & {
name?: string;
};
let { ref = $bindable(null), class: className, name, children, ...restProps }: Props = $props();
const uid = $props.id();
if (name) {
setFormContext(uid, name);
}
</script>
<div bind:this={ref} class={cn('flex flex-col gap-1')} {...restProps}>
{@render children?.()}
</div>

View File

@ -0,0 +1,9 @@
import Field from './field.svelte';
import Errors from './errors.svelte';
import Label from './label.svelte';
export {
Field,
Errors,
Label
};

View File

@ -0,0 +1,29 @@
<script lang="ts">
import { cn } from '$lib/utils';
import { getFormContext } from './context.svelte';
import type { WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
type Props = WithElementRef<HTMLAttributes<HTMLLabelElement>>;
let { ref = $bindable(null), class: className, children, ...restProps }: Props = $props();
const item = getFormContext();
function formatName(name: string) {
// Replace _ with spaces
name = name.replace('_', ' ');
// Capitalize first letter
name = name.charAt(0).toUpperCase() + name.slice(1);
return name;
}
</script>
<label bind:this={ref} class={cn('text-sm', className)} for={item?.id} {...restProps}>
{#if children}
{@render children()}
{:else if item}
{formatName(item.name)}
{/if}
</label>