feat: overwrite form components

This commit is contained in:
Bart van der Braak 2024-02-20 19:44:05 +01:00
parent e96783230e
commit a7900c2ba5
23 changed files with 198 additions and 339 deletions

View file

@ -1,15 +1,15 @@
<script lang="ts"> <script lang="ts">
import { Button as ButtonPrimitive } from 'bits-ui'; import { Button as ButtonPrimitive } from "bits-ui";
import { cn } from '$lib/utils'; import { cn } from "$lib/utils";
import { buttonVariants, type Props, type Events } from '.'; import { buttonVariants, type Props, type Events } from ".";
type $$Props = Props; type $$Props = Props;
type $$Events = Events; type $$Events = Events;
let className: $$Props['class'] = undefined; let className: $$Props["class"] = undefined;
export let variant: $$Props['variant'] = 'default'; export let variant: $$Props["variant"] = "default";
export let size: $$Props['size'] = 'default'; export let size: $$Props["size"] = "default";
export let builders: $$Props['builders'] = []; export let builders: $$Props["builders"] = [];
export { className as class }; export { className as class };
</script> </script>

View file

@ -1,34 +1,35 @@
import type { Button as ButtonPrimitive } from 'bits-ui'; import type { Button as ButtonPrimitive } from "bits-ui";
import { tv, type VariantProps } from 'tailwind-variants'; import { tv, type VariantProps } from "tailwind-variants";
import Root from './button.svelte'; import Root from "./button.svelte";
const buttonVariants = tv({ const buttonVariants = tv({
base: 'inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', base: "inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
variants: { variants: {
variant: { variant: {
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline: outline:
'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground', "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: 'hover:bg-accent hover:text-accent-foreground', ghost: "hover:bg-accent hover:text-accent-foreground",
link: 'text-primary underline-offset-4 hover:underline' link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {
default: 'h-9 px-4 py-2', default: "h-9 px-4 py-2",
sm: 'h-8 rounded-md px-3 text-xs', sm: "h-8 rounded-md px-3 text-xs",
lg: 'h-10 rounded-md px-8', lg: "h-10 rounded-md px-8",
icon: 'h-9 w-9' icon: "h-9 w-9",
} },
}, },
defaultVariants: { defaultVariants: {
variant: 'default', variant: "default",
size: 'default' size: "default",
} },
}); });
type Variant = VariantProps<typeof buttonVariants>['variant']; type Variant = VariantProps<typeof buttonVariants>["variant"];
type Size = VariantProps<typeof buttonVariants>['size']; type Size = VariantProps<typeof buttonVariants>["size"];
type Props = ButtonPrimitive.Props & { type Props = ButtonPrimitive.Props & {
variant?: Variant; variant?: Variant;
@ -45,5 +46,5 @@ export {
Root as Button, Root as Button,
type Props as ButtonProps, type Props as ButtonProps,
type Events as ButtonEvents, type Events as ButtonEvents,
buttonVariants buttonVariants,
}; };

View file

@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import * as Button from '$lib/components/ui/button'; import * as Button from "$lib/components/ui/button";
type $$Props = Button.Props; type $$Props = Button.Props;
type $$Events = Button.Events;
</script> </script>
<Button.Root type="submit" {...$$restProps} on:click on:keydown> <Button.Root type="submit" {...$$restProps}>
<slot /> <slot />
</Button.Root> </Button.Root>

View file

@ -1,26 +0,0 @@
<script lang="ts">
import { getFormField } from 'formsnap';
import type { Checkbox as CheckboxPrimitive } from 'bits-ui';
import { Checkbox } from '$lib/components/ui/checkbox';
type $$Props = CheckboxPrimitive.Props;
type $$Events = CheckboxPrimitive.Events;
export let onCheckedChange: $$Props['onCheckedChange'] = undefined;
const { name, setValue, attrStore, value } = getFormField();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { name: nameAttr, value: valueAttr, ...rest } = $attrStore;
</script>
<Checkbox
{...rest}
checked={typeof $value === 'boolean' ? $value : false}
onCheckedChange={(v) => {
onCheckedChange?.(v);
setValue(v);
}}
{...$$restProps}
on:click
on:keydown
/>
<input hidden {name} value={$value} />

View file

@ -1,16 +1,16 @@
<script lang="ts"> <script lang="ts">
import { Form as FormPrimitive } from 'formsnap'; import * as FormPrimitive from "formsnap";
import { cn } from '$lib/utils'; import { cn } from "$lib/utils";
import type { HTMLAttributes } from 'svelte/elements';
type $$Props = HTMLAttributes<HTMLSpanElement>; type $$Props = FormPrimitive.DescriptionProps;
let className: string | undefined | null = undefined; let className: $$Props["class"] = undefined;
export { className as class }; export { className as class };
</script> </script>
<FormPrimitive.Description <FormPrimitive.Description
class={cn('text-[0.8rem] text-muted-foreground', className)} class={cn("text-[0.8rem] text-muted-foreground", className)}
{...$$restProps} {...$$restProps}
let:descriptionAttrs
> >
<slot /> <slot {descriptionAttrs} />
</FormPrimitive.Description> </FormPrimitive.Description>

View file

@ -0,0 +1,26 @@
<script lang="ts" context="module">
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { FormPathLeaves, SuperForm } from "sveltekit-superforms";
type T = Record<string, unknown>;
type U = unknown;
</script>
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
import type { HTMLAttributes } from "svelte/elements";
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils";
type $$Props = FormPrimitive.ElementFieldProps<T, U> & HTMLAttributes<HTMLDivElement>;
export let form: SuperForm<T>;
export let name: U;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<FormPrimitive.ElementField {form} {name} let:constraints let:errors let:tainted let:value>
<div class={cn("space-y-2", className)}>
<slot {constraints} {errors} {tainted} {value} />
</div>
</FormPrimitive.ElementField>

View file

@ -0,0 +1,26 @@
<script lang="ts">
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils";
type $$Props = FormPrimitive.FieldErrorsProps & {
errorClasses?: string | undefined | null;
};
let className: $$Props["class"] = undefined;
export { className as class };
export let errorClasses: $$Props["class"] = undefined;
</script>
<FormPrimitive.FieldErrors
class={cn("text-[0.8rem] font-medium text-destructive", className)}
{...$$restProps}
let:errors
let:fieldErrorsAttrs
let:errorAttrs
>
<slot {errors} {fieldErrorsAttrs} {errorAttrs}>
{#each errors as error}
<div {...errorAttrs} class={cn(errorClasses)}>{error}</div>
{/each}
</slot>
</FormPrimitive.FieldErrors>

View file

@ -0,0 +1,26 @@
<script lang="ts" context="module">
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { FormPath, SuperForm } from "sveltekit-superforms";
type T = Record<string, unknown>;
type U = unknown;
</script>
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import type { HTMLAttributes } from "svelte/elements";
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils";
type $$Props = FormPrimitive.FieldProps<T, U> & HTMLAttributes<HTMLElement>;
export let form: SuperForm<T>;
export let name: U;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<FormPrimitive.Field {form} {name} let:constraints let:errors let:tainted let:value>
<div class={cn("space-y-2", className)}>
<slot {constraints} {errors} {tainted} {value} />
</div>
</FormPrimitive.Field>

View file

@ -0,0 +1,31 @@
<script lang="ts" context="module">
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { FormPath, SuperForm } from "sveltekit-superforms";
type T = Record<string, unknown>;
type U = unknown;
</script>
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils";
type $$Props = FormPrimitive.FieldsetProps<T, U>;
export let form: SuperForm<T>;
export let name: U;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<FormPrimitive.Fieldset
{form}
{name}
let:constraints
let:errors
let:tainted
let:value
class={cn("space-y-2", className)}
>
<slot {constraints} {errors} {tainted} {value} />
</FormPrimitive.Fieldset>

View file

@ -1,28 +0,0 @@
<script lang="ts">
import { getFormField } from 'formsnap';
import type { HTMLInputAttributes } from 'svelte/elements';
import { Input, type InputEvents } from '$lib/components/ui/input';
type $$Props = HTMLInputAttributes;
type $$Events = InputEvents;
const { attrStore, value } = getFormField();
</script>
<Input
{...$attrStore}
bind:value={$value}
{...$$restProps}
on:blur
on:change
on:click
on:focus
on:keydown
on:keypress
on:keyup
on:mouseover
on:mouseenter
on:mouseleave
on:paste
on:input
/>

View file

@ -1,12 +0,0 @@
<script lang="ts">
import { cn } from '$lib/utils';
import type { HTMLAttributes } from 'svelte/elements';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<div class={cn('space-y-2', className)} {...$$restProps}>
<slot />
</div>

View file

@ -1,17 +1,17 @@
<script lang="ts"> <script lang="ts">
import type { Label as LabelPrimitive } from 'bits-ui'; import type { Label as LabelPrimitive } from "bits-ui";
import { getFormField } from 'formsnap'; import { getFormControl } from "formsnap";
import { cn } from '$lib/utils'; import { cn } from "$lib/utils";
import { Label } from '$lib/components/ui/label'; import { Label } from "$lib/components/ui/label";
type $$Props = LabelPrimitive.Props; type $$Props = LabelPrimitive.Props;
let className: $$Props['class'] = undefined; let className: $$Props["class"] = undefined;
export { className as class }; export { className as class };
const { errors, ids } = getFormField(); const { labelAttrs } = getFormControl();
</script> </script>
<Label for={$ids.input} class={cn($errors && 'text-destructive', className)} {...$$restProps}> <Label {...$labelAttrs} class={cn("data-[fs-error]:text-destructive", className)} {...$$restProps}>
<slot /> <slot {labelAttrs} />
</Label> </Label>

View file

@ -0,0 +1,17 @@
<script lang="ts">
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils";
type $$Props = FormPrimitive.LegendProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<FormPrimitive.Legend
{...$$restProps}
class={cn("text-sm font-medium leading-none data-[fs-error]:text-destructive", className)}
let:legendAttrs
>
<slot {legendAttrs} />
</FormPrimitive.Legend>

View file

@ -1,26 +0,0 @@
<script lang="ts">
import { Form as FormPrimitive } from 'formsnap';
import { buttonVariants } from '$lib/components/ui/button';
import { cn } from '$lib/utils';
import { CaretSort } from 'radix-icons-svelte';
import type { HTMLSelectAttributes } from 'svelte/elements';
type $$Props = HTMLSelectAttributes;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<div class="relative">
<FormPrimitive.Select
class={cn(
buttonVariants({ variant: 'outline' }),
'appearance-none bg-transparent font-normal',
className
)}
{...$$restProps}
>
<slot />
</FormPrimitive.Select>
<CaretSort class="absolute right-3 top-2.5 h-4 w-4 opacity-50" />
</div>

View file

@ -1,22 +0,0 @@
<script lang="ts">
import { getFormField } from 'formsnap';
import type { RadioGroup as RadioGroupPrimitive } from 'bits-ui';
import * as RadioGroup from '$lib/components/ui/radio-group';
type $$Props = RadioGroupPrimitive.Props;
const { attrStore, setValue, name, value } = getFormField();
export let onValueChange: $$Props['onValueChange'] = undefined;
</script>
<RadioGroup.Root
{...$attrStore}
onValueChange={(v) => {
onValueChange?.(v);
setValue(v);
}}
{...$$restProps}
>
<slot />
<input hidden {name} value={$value} />
</RadioGroup.Root>

View file

@ -1,18 +0,0 @@
<script lang="ts">
import * as Select from '$lib/components/ui/select';
import type { Select as SelectPrimitive } from 'bits-ui';
import { getFormField } from 'formsnap';
type $$Props = SelectPrimitive.TriggerProps & {
placeholder?: string;
};
type $$Events = SelectPrimitive.TriggerEvents;
const { attrStore, value } = getFormField();
export let placeholder = '';
</script>
<Select.Trigger {...$$restProps} {...$attrStore} on:click on:keydown>
<slot value={$value}>
<Select.Value {placeholder} />
</slot>
</Select.Trigger>

View file

@ -1,20 +0,0 @@
<script lang="ts">
import * as Select from '$lib/components/ui/select';
import { getFormField } from 'formsnap';
import type { Select as SelectPrimitive } from 'bits-ui';
type $$Props = SelectPrimitive.Props<unknown>;
const { setValue, name, value } = getFormField();
export let onSelectedChange: $$Props['onSelectedChange'] = undefined;
</script>
<Select.Root
onSelectedChange={(v) => {
onSelectedChange?.(v);
setValue(v ? v.value : undefined);
}}
{...$$restProps}
>
<slot />
<input hidden {name} value={$value} />
</Select.Root>

View file

@ -1,24 +0,0 @@
<script lang="ts">
import { getFormField } from 'formsnap';
import type { Switch as SwitchPrimitive } from 'bits-ui';
import { Switch } from '$lib/components/ui/switch';
type $$Props = SwitchPrimitive.Props;
type $$Events = SwitchPrimitive.Events;
export let onCheckedChange: $$Props['onCheckedChange'] = undefined;
const { name, setValue, attrStore, value } = getFormField();
</script>
<Switch
{...$attrStore}
checked={typeof $value === 'boolean' ? $value : false}
onCheckedChange={(v) => {
onCheckedChange?.(v);
setValue(v);
}}
{...$$restProps}
on:click
on:keydown
/>
<input hidden {name} value={$value} />

View file

@ -1,29 +0,0 @@
<script lang="ts">
import { getFormField } from 'formsnap';
import type { HTMLTextareaAttributes } from 'svelte/elements';
import type { TextareaGetFormField } from '.';
import { Textarea, type TextareaEvents } from '$lib/components/ui/textarea';
type $$Props = HTMLTextareaAttributes;
type $$Events = TextareaEvents;
const { attrStore, value } = getFormField() as TextareaGetFormField;
</script>
<Textarea
{...$attrStore}
bind:value={$value}
{...$$restProps}
on:blur
on:change
on:click
on:focus
on:keydown
on:keypress
on:keyup
on:mouseover
on:mouseenter
on:mouseleave
on:paste
on:input
/>

View file

@ -1,14 +0,0 @@
<script lang="ts">
import { Form as FormPrimitive } from 'formsnap';
import { cn } from '$lib/utils';
import type { HTMLAttributes } from 'svelte/elements';
type $$Props = HTMLAttributes<HTMLParagraphElement>;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<FormPrimitive.Validation
class={cn('text-[0.8rem] font-medium text-destructive', className)}
{...$$restProps}
/>

View file

@ -1,82 +1,33 @@
import { Form as FormPrimitive, getFormField } from 'formsnap'; import * as FormPrimitive from "formsnap";
import type { Writable } from 'svelte/store'; import Description from "./form-description.svelte";
import * as RadioGroupComp from '$lib/components/ui/radio-group'; import Label from "./form-label.svelte";
import * as SelectComp from '$lib/components/ui/select'; import FieldErrors from "./form-field-errors.svelte";
import Item from './form-item.svelte'; import Field from "./form-field.svelte";
import Input from './form-input.svelte'; import Button from "./form-button.svelte";
import Textarea from './form-textarea.svelte'; import Fieldset from "./form-fieldset.svelte";
import Description from './form-description.svelte'; import Legend from "./form-legend.svelte";
import Label from './form-label.svelte'; import ElementField from "./form-element-field.svelte";
import Validation from './form-validation.svelte';
import Checkbox from './form-checkbox.svelte';
import Switch from './form-switch.svelte';
import NativeSelect from './form-native-select.svelte';
import RadioGroup from './form-radio-group.svelte';
import Select from './form-select.svelte';
import SelectTrigger from './form-select-trigger.svelte';
import Button from './form-button.svelte';
const Root = FormPrimitive.Root;
const Field = FormPrimitive.Field;
const Control = FormPrimitive.Control; const Control = FormPrimitive.Control;
const RadioItem = RadioGroupComp.Item;
const NativeRadio = FormPrimitive.Radio;
const SelectContent = SelectComp.Content;
const SelectLabel = SelectComp.Label;
const SelectGroup = SelectComp.Group;
const SelectItem = SelectComp.Item;
const SelectSeparator = SelectComp.Separator;
export type TextareaGetFormField = Omit<ReturnType<typeof getFormField>, 'value'> & {
value: Writable<string>;
};
export { export {
Root,
Field, Field,
Control, Control,
Item,
Input,
Label, Label,
Button, FieldErrors,
Switch,
Select,
Checkbox,
Textarea,
Validation,
RadioGroup,
RadioItem,
Description, Description,
SelectContent, Fieldset,
SelectLabel, Legend,
SelectGroup, ElementField,
SelectItem, Button,
SelectSeparator,
SelectTrigger,
NativeSelect,
NativeRadio,
// //
Root as Form,
Field as FormField, Field as FormField,
Control as FormControl, Control as FormControl,
Item as FormItem,
Input as FormInput,
Textarea as FormTextarea,
Description as FormDescription, Description as FormDescription,
Label as FormLabel, Label as FormLabel,
Validation as FormValidation, FieldErrors as FormFieldErrors,
NativeSelect as FormNativeSelect, Fieldset as FormFieldset,
NativeRadio as FormNativeRadio, Legend as FormLegend,
Checkbox as FormCheckbox, ElementField as FormElementField,
Switch as FormSwitch, Button as FormButton,
RadioGroup as FormRadioGroup,
RadioItem as FormRadioItem,
Select as FormSelect,
SelectContent as FormSelectContent,
SelectLabel as FormSelectLabel,
SelectGroup as FormSelectGroup,
SelectItem as FormSelectItem,
SelectSeparator as FormSelectSeparator,
SelectTrigger as FormSelectTrigger,
Button as FormButton
}; };

View file

@ -1,7 +1,7 @@
import Root from './label.svelte'; import Root from "./label.svelte";
export { export {
Root, Root,
// //
Root as Label Root as Label,
}; };

View file

@ -1,16 +1,16 @@
<script lang="ts"> <script lang="ts">
import { Label as LabelPrimitive } from 'bits-ui'; import { Label as LabelPrimitive } from "bits-ui";
import { cn } from '$lib/utils'; import { cn } from "$lib/utils";
type $$Props = LabelPrimitive.Props; type $$Props = LabelPrimitive.Props;
let className: $$Props['class'] = undefined; let className: $$Props["class"] = undefined;
export { className as class }; export { className as class };
</script> </script>
<LabelPrimitive.Root <LabelPrimitive.Root
class={cn( class={cn(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
className className
)} )}
{...$$restProps} {...$$restProps}