feat: add db, form actions, load data
This commit is contained in:
13
src/lib/components/Card.svelte
Normal file
13
src/lib/components/Card.svelte
Normal file
@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
let { img, name, link } = $props();
|
||||
|
||||
console.log(img)
|
||||
|
||||
</script>
|
||||
|
||||
<div class="border-2 rounded-md max-w-48 p-2">
|
||||
{#if img}
|
||||
<img src={img!.src} alt={img!.alt} />
|
||||
{/if}
|
||||
<p>{name}</p>
|
||||
</div>
|
74
src/lib/components/ui/button/button.svelte
Normal file
74
src/lib/components/ui/button/button.svelte
Normal file
@ -0,0 +1,74 @@
|
||||
<script lang="ts" module>
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
|
||||
export const buttonVariants = tv({
|
||||
base: "ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border-input bg-background hover:bg-accent hover:text-accent-foreground border",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
});
|
||||
|
||||
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
|
||||
export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
|
||||
|
||||
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
|
||||
WithElementRef<HTMLAnchorAttributes> & {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
class: className,
|
||||
variant = "default",
|
||||
size = "default",
|
||||
ref = $bindable(null),
|
||||
href = undefined,
|
||||
type = "button",
|
||||
children,
|
||||
...restProps
|
||||
}: ButtonProps = $props();
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
bind:this={ref}
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
{href}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</a>
|
||||
{:else}
|
||||
<button
|
||||
bind:this={ref}
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
{type}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</button>
|
||||
{/if}
|
17
src/lib/components/ui/button/index.ts
Normal file
17
src/lib/components/ui/button/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import Root, {
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
buttonVariants,
|
||||
} from "./button.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
type ButtonProps as Props,
|
||||
//
|
||||
Root as Button,
|
||||
buttonVariants,
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
};
|
7
src/lib/components/ui/form/form-button.svelte
Normal file
7
src/lib/components/ui/form/form-button.svelte
Normal file
@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import * as Button from "$lib/components/ui/button/index.js";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: Button.Props = $props();
|
||||
</script>
|
||||
|
||||
<Button.Root bind:ref type="submit" {...restProps} />
|
17
src/lib/components/ui/form/form-description.svelte
Normal file
17
src/lib/components/ui/form/form-description.svelte
Normal file
@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import type { WithoutChild } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChild<FormPrimitive.DescriptionProps> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Description
|
||||
bind:ref
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
/>
|
30
src/lib/components/ui/form/form-element-field.svelte
Normal file
30
src/lib/components/ui/form/form-element-field.svelte
Normal file
@ -0,0 +1,30 @@
|
||||
<script lang="ts" module>
|
||||
import type { FormPathLeaves as _FormPathLeaves } from "sveltekit-superforms";
|
||||
type T = Record<string, unknown>;
|
||||
type U = _FormPathLeaves<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends _FormPathLeaves<T>">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { WithElementRef, WithoutChildren } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
form,
|
||||
name,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> &
|
||||
FormPrimitive.ElementFieldProps<T, U> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.ElementField {form} {name}>
|
||||
{#snippet children({ constraints, errors, tainted, value })}
|
||||
<div bind:this={ref} class={cn("space-y-2", className)} {...restProps}>
|
||||
{@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })}
|
||||
</div>
|
||||
{/snippet}
|
||||
</FormPrimitive.ElementField>
|
31
src/lib/components/ui/form/form-field-errors.svelte
Normal file
31
src/lib/components/ui/form/form-field-errors.svelte
Normal file
@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import type { WithoutChild } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
errorClasses,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChild<FormPrimitive.FieldErrorsProps> & {
|
||||
errorClasses?: string | undefined | null;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.FieldErrors
|
||||
bind:ref
|
||||
class={cn("text-destructive text-sm font-medium", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ errors, errorProps })}
|
||||
{#if childrenProp}
|
||||
{@render childrenProp({ errors, errorProps })}
|
||||
{:else}
|
||||
{#each errors as error (error)}
|
||||
<div {...errorProps} class={cn(errorClasses)}>{error}</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{/snippet}
|
||||
</FormPrimitive.FieldErrors>
|
30
src/lib/components/ui/form/form-field.svelte
Normal file
30
src/lib/components/ui/form/form-field.svelte
Normal file
@ -0,0 +1,30 @@
|
||||
<script lang="ts" module>
|
||||
import type { FormPath as _FormPath } from "sveltekit-superforms";
|
||||
type T = Record<string, unknown>;
|
||||
type U = _FormPath<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends _FormPath<T>">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import type { WithoutChildren, WithElementRef } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
form,
|
||||
name,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: FormPrimitive.FieldProps<T, U> &
|
||||
WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Field {form} {name}>
|
||||
{#snippet children({ constraints, errors, tainted, value })}
|
||||
<div bind:this={ref} class={cn("space-y-2", className)} {...restProps}>
|
||||
{@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })}
|
||||
</div>
|
||||
{/snippet}
|
||||
</FormPrimitive.Field>
|
21
src/lib/components/ui/form/form-fieldset.svelte
Normal file
21
src/lib/components/ui/form/form-fieldset.svelte
Normal file
@ -0,0 +1,21 @@
|
||||
<script lang="ts" module>
|
||||
import type { FormPath as _FormPath } from "sveltekit-superforms";
|
||||
type T = Record<string, unknown>;
|
||||
type U = _FormPath<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends _FormPath<T>">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import type { WithoutChild } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
form,
|
||||
name,
|
||||
...restProps
|
||||
}: WithoutChild<FormPrimitive.FieldsetProps<T, U>> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Fieldset bind:ref {form} {name} class={cn("space-y-2", className)} {...restProps} />
|
21
src/lib/components/ui/form/form-label.svelte
Normal file
21
src/lib/components/ui/form/form-label.svelte
Normal file
@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import type { WithoutChild } from "bits-ui";
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { Label } from "$lib/components/ui/label/index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
children,
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChild<FormPrimitive.LabelProps> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Label {...restProps} bind:ref>
|
||||
{#snippet child({ props })}
|
||||
<Label {...props} class={cn("data-[fs-error]:text-destructive", className)}>
|
||||
{@render children?.()}
|
||||
</Label>
|
||||
{/snippet}
|
||||
</FormPrimitive.Label>
|
17
src/lib/components/ui/form/form-legend.svelte
Normal file
17
src/lib/components/ui/form/form-legend.svelte
Normal file
@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import type { WithoutChild } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChild<FormPrimitive.LegendProps> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Legend
|
||||
bind:ref
|
||||
class={cn("data-[fs-error]:text-destructive text-sm font-medium leading-none", className)}
|
||||
{...restProps}
|
||||
/>
|
33
src/lib/components/ui/form/index.ts
Normal file
33
src/lib/components/ui/form/index.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import Description from "./form-description.svelte";
|
||||
import Label from "./form-label.svelte";
|
||||
import FieldErrors from "./form-field-errors.svelte";
|
||||
import Field from "./form-field.svelte";
|
||||
import Fieldset from "./form-fieldset.svelte";
|
||||
import Legend from "./form-legend.svelte";
|
||||
import ElementField from "./form-element-field.svelte";
|
||||
import Button from "./form-button.svelte";
|
||||
|
||||
const Control = FormPrimitive.Control;
|
||||
|
||||
export {
|
||||
Field,
|
||||
Control,
|
||||
Label,
|
||||
Button,
|
||||
FieldErrors,
|
||||
Description,
|
||||
Fieldset,
|
||||
Legend,
|
||||
ElementField,
|
||||
//
|
||||
Field as FormField,
|
||||
Control as FormControl,
|
||||
Description as FormDescription,
|
||||
Label as FormLabel,
|
||||
FieldErrors as FormFieldErrors,
|
||||
Fieldset as FormFieldset,
|
||||
Legend as FormLegend,
|
||||
ElementField as FormElementField,
|
||||
Button as FormButton,
|
||||
};
|
7
src/lib/components/ui/input/index.ts
Normal file
7
src/lib/components/ui/input/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import Root from "./input.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
};
|
46
src/lib/components/ui/input/input.svelte
Normal file
46
src/lib/components/ui/input/input.svelte
Normal file
@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type InputType = Exclude<HTMLInputTypeAttribute, "file">;
|
||||
|
||||
type Props = WithElementRef<
|
||||
Omit<HTMLInputAttributes, "type"> &
|
||||
({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
|
||||
>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
type,
|
||||
files = $bindable(),
|
||||
class: className,
|
||||
...restProps
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if type === "file"}
|
||||
<input
|
||||
bind:this={ref}
|
||||
class={cn(
|
||||
"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-base file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className
|
||||
)}
|
||||
type="file"
|
||||
bind:files
|
||||
bind:value
|
||||
{...restProps}
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
bind:this={ref}
|
||||
class={cn(
|
||||
"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-base file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className
|
||||
)}
|
||||
{type}
|
||||
bind:value
|
||||
{...restProps}
|
||||
/>
|
||||
{/if}
|
7
src/lib/components/ui/label/index.ts
Normal file
7
src/lib/components/ui/label/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import Root from "./label.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Label,
|
||||
};
|
19
src/lib/components/ui/label/label.svelte
Normal file
19
src/lib/components/ui/label/label.svelte
Normal file
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { Label as LabelPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: LabelPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<LabelPrimitive.Root
|
||||
bind:ref
|
||||
class={cn(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
3
src/lib/db.ts
Normal file
3
src/lib/db.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { drizzle } from "drizzle-orm/libsql";
|
||||
|
||||
export const db = drizzle(process.env.DATABASE_URL || 'local.db')
|
@ -1,6 +1,21 @@
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
||||
|
||||
export const user = sqliteTable('user', {
|
||||
export const images = sqliteTable('images', {
|
||||
id: integer('id').primaryKey(),
|
||||
age: integer('age')
|
||||
});
|
||||
source: text('source').unique(),
|
||||
alt: text('alt')
|
||||
})
|
||||
|
||||
export const cards = sqliteTable('cards', {
|
||||
id: integer('id').primaryKey(),
|
||||
imageId: integer('image_id'),
|
||||
name: text('name'),
|
||||
link: text('link'),
|
||||
})
|
||||
|
||||
export const cardsRelations = relations(cards, ({ one }) => ({
|
||||
images: one(images, {
|
||||
fields: [cards.imageId], references: [images.id]
|
||||
})
|
||||
}))
|
||||
|
31
src/routes/+page.server.ts
Normal file
31
src/routes/+page.server.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { db } from "$lib/server/db";
|
||||
import { cards } from "$lib/server/db/schema";
|
||||
import type { Actions, PageServerLoad } from "./$types";
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
return {
|
||||
cards: await db.query.cards.findMany({
|
||||
with: {
|
||||
images: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
createCard: async ({ request }) => {
|
||||
const data = await request.formData();
|
||||
|
||||
const name = data.get('name');
|
||||
const link = data.get('link');
|
||||
const image = data.get('image')
|
||||
|
||||
console.log(image)
|
||||
|
||||
await db.insert(cards).values({
|
||||
link: link,
|
||||
name: name,
|
||||
})
|
||||
|
||||
}
|
||||
} satisfies Actions;
|
@ -1,2 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from "$app/forms";
|
||||
import Card from "$lib/components/Card.svelte";
|
||||
import Button from "$lib/components/ui/button/button.svelte";
|
||||
import Input from "$lib/components/ui/input/input.svelte";
|
||||
import Label from "$lib/components/ui/label/label.svelte";
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
console.log("cards", data.cards);
|
||||
</script>
|
||||
|
||||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
||||
<p>
|
||||
Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the
|
||||
documentation
|
||||
</p>
|
||||
|
||||
{#each data.cards as card}
|
||||
<Card {...card} />
|
||||
{/each}
|
||||
|
||||
<form method="POST" action="?/createCard" enctype="multipart/form-data" use:enhance>
|
||||
<Label for="image">Card image</Label>
|
||||
<Input id="image" name="image" type="file" />
|
||||
<Label for="name">The name of the card</Label>
|
||||
<Input id="name" name="name" />
|
||||
<Label for="link">where to go</Label>
|
||||
<Input id="link" name="link" />
|
||||
<Button type="submit">Create</Button>
|
||||
</form>
|
||||
|
Reference in New Issue
Block a user