initial commit ??

This commit is contained in:
bootunloader
2024-09-11 02:57:43 +03:00
commit 33cbeaa9a3
186 changed files with 17269 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
import clsx from "clsx";
import { tv } from "tailwind-variants";
export const buttonStyles = tv(
{
base: clsx(
"p-2 px-4 flex gap-1.5 items-center justify-center rounded-md transition-all duration-150 shadow-sm font-medium tracking-wide",
),
variants: {
intent: {
primary: clsx("bg-sky-500 text-white hover:bg-sky-600"),
secondary: "bg-sky-800 text-white",
success: "bg-emerald-500 text-white hover:bg-emerald-600",
danger: "bg-rose-500 text-white hover:bg-rose-600",
warning: "bg-amber-500 text-white hover:bg-amber-600",
ghost: clsx(
"bg-slate-100 text-slate-500 hover:text-slate-600 hover:bg-slate-200",
),
primaryInverted: clsx("bg-sky-50 text-sky-600 hover:bg-sky-100"),
successInverted: clsx(
"bg-emerald-50 hover:bg-emerald-100 text-emerald-600",
),
dangerInverted: clsx("bg-slate-100 hover:bg-slate-200 text-rose-600"),
warningInverted: clsx("bg-amber-50 hover:bg-amber-100 text-amber-600"),
},
size: { sm: "text-sm", md: "text-md", lg: "text-lg" },
fullwidth: { yes: "w-full", max: "w-max", no: "" },
},
defaultVariants: {
intent: "primary",
size: "sm",
},
},
{},
);

View File

@@ -0,0 +1,30 @@
<script lang="ts">
import type { SvelteComponent } from "svelte";
import { buttonStyles } from "./button.styles";
export let fullwidth: keyof typeof buttonStyles.variants.fullwidth = "no";
export let intent: keyof typeof buttonStyles.variants.intent = "primary";
export let text: string;
export let iconleft: typeof SvelteComponent | undefined = undefined;
export let iconright: typeof SvelteComponent | undefined = undefined;
export let otherOptions: any = {};
export let disabled: boolean = false;
export let onClick: (e: Event) => void = () => {};
</script>
<button
{...otherOptions}
on:click={onClick}
class={buttonStyles({ intent: intent, fullwidth: fullwidth })}
{disabled}
>
{#if iconleft}
<svelte:component this={iconleft} size={24} />
{/if}
<span class="break-keep">
{text}
</span>
{#if iconright}
<svelte:component this={iconright} size={24} />
{/if}
</button>

View File

@@ -0,0 +1,43 @@
<script lang="ts">
import { randomString } from "$lib/utils";
import { COLOR_TRANSITION } from "$lib/utils/constants";
import { createCheckbox } from "@melt-ui/svelte";
import clsx from "clsx";
import IconMdiCheck from "~icons/mdi/check";
import IconIcOutlineMinus from "~icons/ic/outline-minus";
export let checked: boolean | "indeterminate";
export let disabled: boolean = false;
export let onChange: (checked: boolean | "indeterminate") => void = () => {};
const {
root,
input,
isChecked,
isIndeterminate,
checked: checkedStore,
} = createCheckbox({ checked, disabled });
checkedStore.subscribe((v) => {
onChange(v);
});
const inputId = randomString(16);
</script>
<form>
<button
{...$root}
class={clsx(
"flex h-6 w-6 appearance-none items-center justify-center rounded-sm bg-white text-sky-600 shadow-lg hover:bg-slate-50 border border-slate-300 hover:border-sky-500",
COLOR_TRANSITION
)}
id={inputId}
use:root
>
{#if $isIndeterminate}
<IconIcOutlineMinus />
{:else if $isChecked}
<IconMdiCheck />
{/if}
<input {...$input} />
</button>
</form>

View File

@@ -0,0 +1,48 @@
<script lang="ts">
import clsx from "clsx";
import type { SvelteComponent } from "svelte";
const colors = {
gray: "bg-gray-200 text-gray-600 hover:bg-gray-300",
blue: "bg-blue-100 text-blue-500 hover:bg-blue-200 hover:text-blue-600",
amber:
"bg-amber-100 text-amber-500 hover:bg-amber-200 hover:text-amber-600",
green:
"bg-green-100 text-green-500 hover:bg-green-200 hover:text-green-600",
sky:
"bg-sky-100 text-sky-500 hover:bg-sky-200 hover:text-sky-600",
red: "bg-red-100 text-red-500 hover:bg-red-200 hover:text-red-600",
} as const;
export let icon: typeof SvelteComponent | undefined = undefined;
export let color: keyof typeof colors = "sky";
export let size: string = "8";
export let onClick: () => void = () => {};
export let disabled: boolean = false;
export let position: "bottomleft" | "bottomright" | "topleft" | "topright" =
"bottomright";
export let hideOnDesktop: boolean = false;
const onClickHandler = () => {
if (onClick && !disabled) {
onClick();
}
};
</script>
<button
type="button"
class={clsx(
"flex w-max cursor-pointer appearance-none items-center gap-2 rounded-lg p-3 shadow-lg transition-colors duration-150",
colors[color ?? "sky"],
disabled && "opacity-50",
position === "bottomleft" && "fixed bottom-4 left-4",
position === "bottomright" && "fixed bottom-4 right-4",
position === "topleft" && "fixed left-4 top-4",
position === "topright" && "fixed right-4 top-4",
hideOnDesktop && "lg:hidden",
)}
on:click={onClickHandler}
>
<svelte:component this={icon} class={clsx(`h-${size} w-${size}`)} />
</button>

View File

@@ -0,0 +1,107 @@
<script lang="ts">
import clsx from "clsx";
import { createListbox } from "svelte-headlessui";
import IconCheck from "~icons/bi/check";
import IconSelector from "~icons/majesticons/selector-line";
import { randomString } from "$lib/utils";
type Option = {
id?: string | number;
label: string;
value: string;
};
export let componentId: string = randomString(10);
export let label: string = "";
export let options: Option[];
export let multiple: boolean = false;
export let selected: Option[] = [];
export let onSelect: (option: Option) => void = () => {};
export let fullWidth: boolean = false;
const listbox = createListbox({
label: "Actions",
});
const setToSelected = (option: Option) => {
if (multiple) {
const alreadyPresent = selected.find(
(item) => item.value === option.value
);
if (alreadyPresent) {
selected = selected.filter((item) => item.value !== option.value);
return;
}
selected.push(option);
selected = selected;
} else {
selected = [option];
}
};
function _onSelect(e: Event) {
const { selected } = (e as CustomEvent).detail;
setToSelected(selected);
onSelect(selected);
}
</script>
<div class={clsx("relative flex flex-col gap-1", fullWidth && "w-full")}>
<label class="mb-1 pl-1 text-sm" for={componentId}>{label}</label>
<button
id={componentId}
use:listbox.button
on:select={_onSelect}
class={clsx(
"rounded-md relative cursor-pointer border text-left border-gray-300 outline-none p-2 transition-colors duration-200 bg-slate-50 hover:bg-slate-50 hover:border-black focus:border-sky-500 focus:bg-sky-50 focus:placeholder:text-sky-400 focus:text-sky-700",
fullWidth ? "w-full" : "w-max"
)}
>
<span class="block truncate"
>{selected.length && selected[0].label.length > 0
? selected[0].label
: "Select an option"}</span
>
<span
class="pointer-events-none absolute z-50 inset-y-0 right-0 flex items-center pr-2"
>
<IconSelector class="h-5 w-5 text-gray-400" />
</span>
</button>
<div class={clsx($listbox.expanded ? "block" : "hidden")}>
<ul
use:listbox.items
class="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
>
{#each options as value}
{@const chosen = selected.find((item) => item.value === value.value)}
{@const active = $listbox.active === value}
<li
class={clsx(
"duration-50 relative cursor-pointer select-none py-2 pl-10 pr-4 text-slate-800 transition-colors hover:bg-sky-100",
active ? "bg-sky-100" : ""
)}
use:listbox.item={{ value }}
>
<span
class={clsx(
"block truncate",
chosen ? "font-medium" : "font-normal"
)}>{value.label}</span
>
{#if chosen}
<span
class={clsx(
active ? "text-white" : "text-sky-600",
"absolute inset-y-0 left-0 flex items-center pl-3 text-sky-600 hover:text-white"
)}
>
<IconCheck class="h-6 w-6" />
</span>
{/if}
</li>
{/each}
</ul>
</div>
</div>

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import clsx from "clsx";
import type { SvelteComponent } from "svelte";
const colors = {
gray: "bg-gray-200 text-gray-600 hover:bg-gray-300",
blue: "bg-blue-50 text-blue-500 hover:bg-blue-100",
amber: "bg-amber-50 text-amber-500 hover:bg-amber-100",
green: "bg-green-50 text-green-500 hover:bg-green-100",
sky: "bg-sky-50 text-sky-500 hover:bg-sky-100",
red: "bg-red-50 text-red-500 hover:bg-red-100",
} as const;
export let icon: typeof SvelteComponent | undefined = undefined;
export let color: keyof typeof colors = "sky";
export let size: string = "8";
export let onClick: () => void = () => {};
export let disabled: boolean = false;
const onClickHandler = () => {
if (onClick && !disabled) {
onClick();
}
};
</script>
<button
type="button"
class={clsx(
"w-max cursor-pointer rounded-lg p-2 transition-colors duration-150",
colors[color],
)}
on:click={onClickHandler}
>
<svelte:component this={icon} class={clsx(`h-${size} w-${size}`)} />
</button>

View File

@@ -0,0 +1,143 @@
<script lang="ts">
import clsx from "clsx";
import type { SvelteComponent } from "svelte";
import { tv } from "tailwind-variants";
import { randomString } from "$lib/utils";
export let name: string = "";
export let inputType: string = "text";
export let placeholder: string = "";
export let value: string | number = "";
export let disabled: boolean = false;
export let hidden: boolean = false;
export let intent: keyof typeof baseFormFieldStyles.variants.intent =
"primary";
export let bordered: keyof typeof baseFormFieldStyles.variants.bordered =
"none";
export let padding: keyof typeof baseFormFieldStyles.variants.padding =
"default";
export let fieldWidth: "max" | "full" = "full";
export let label: string = "";
export let labelicon: typeof SvelteComponent | undefined = undefined;
export let iconleft: typeof SvelteComponent | undefined = undefined;
export let iconright: typeof SvelteComponent | undefined = undefined;
export let bottomLabel: string = "";
export let isError: boolean = false;
export let onInput: (e: Event) => void = () => {};
export let otherInputOptions: Record<string, string | number | boolean> =
{};
const baseFormFieldStyles = tv({
base: clsx(
"rounded-md border border-gray-300 outline-none cursor-text transition-colors duration-200",
),
variants: {
intent: {
primary: clsx(
"bg-slate-50 hover:bg-slate-50 hover:border-black focus:border-sky-500 focus:bg-sky-50 focus:placeholder:text-sky-400 focus:text-sky-700",
),
danger: clsx(
"bg-red-50 border-red-500 hover:bg-red-100 hover:border-red-700 placeholder:text-rose-400 focus:text-red-700",
),
success: clsx(
"bg-green-50 border-green-500 hover:bg-green-100 hover:border-green-700 placeholder:text-green-400 focus:text-green-700",
),
},
bordered: {
none: "",
yes: "",
},
padding: {
default: "p-2",
sm: "px-2 p-1.5",
},
horizontalPadding: {
none: "",
md: "px-4",
lg: "px-8",
},
disabled: {
none: "",
yes: "cursor-not-allowed bg-slate-200/80",
},
len: { max: "w-max", full: "w-full" },
},
defaultVariants: {
intent: "primary",
len: "full",
disabled: "none",
padding: "default",
},
});
const typeAction = (node: HTMLInputElement) => {
node.type = inputType;
};
const iconCommonStyle = clsx(
"w-6 h-6",
isError
? "text-red-800"
: intent === "success"
? "text-green-700"
: "text-gray-500",
);
const inputId = randomString(16);
</script>
<div
class={clsx(
"flex flex-col gap-2",
fieldWidth === "full" ? "w-full" : "w-max",
hidden ? "hidden" : "",
)}
>
{#if label.length > 0}
<div class="gap flex items-center pl-2 text-black">
{#if labelicon}
<svelte:component this={labelicon} class={"h-4 w-4"} />
{/if}
<label for={inputId} class="text-sm">{label}</label>
</div>
{/if}
<div class="relative">
{#if iconleft}
<svelte:component
this={iconleft}
class={clsx("absolute left-2 top-2.5", iconCommonStyle)}
/>
{/if}
<input
{name}
use:typeAction
{placeholder}
bind:value
on:input={onInput}
class={clsx(
baseFormFieldStyles({
intent: isError ? "danger" : intent,
bordered: bordered,
len: fieldWidth,
horizontalPadding: iconleft || iconright ? "lg" : "none",
disabled: disabled === true ? "yes" : "none",
padding: padding,
}),
)}
disabled={disabled ?? false}
id={inputId}
{...otherInputOptions}
/>
{#if iconright}
<svelte:component
this={iconright}
class={clsx("absolute right-2 top-3", iconCommonStyle)}
/>
{/if}
</div>
{#if bottomLabel.length > 0}
{#if isError}
<small class="text-xs text-red-500">{bottomLabel}</small>
{:else}
<small class="text-xs text-slate-500">{bottomLabel}</small>
{/if}
{/if}
</div>

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import clsx from "clsx";
export let cls = "";
</script>
<div class={clsx("w-full border-b", cls)} />

View File

@@ -0,0 +1,56 @@
<script lang="ts">
import clsx from "clsx";
export let disabled: boolean = false;
export let iconleft: any = undefined;
export let iconright: any = undefined;
export let text: string = "";
export let weight: "thin" | "normal" | "medium" | "semibold" | "bold" =
"normal";
export let link: string = "#";
export let onClick: () => void = () => {};
const iconSizing = "h-5 w-5 lg:h-6 lg:w-6";
const baseLinkStyle = clsx(
"gap flex items-center cursor-pointer outline-none focus:outline focus:outline-sky-500 transition-all duration-200 ease-in-out"
);
</script>
{#if disabled}
<div class={baseLinkStyle}>
{#if iconleft}
<svelte:component
this={iconleft}
size={24}
class={clsx(iconSizing, "text-slate-400")}
/>
{/if}
<span class={clsx("text-slate-400", `font-${weight}`)}>
{text}
</span>
{#if iconright}
<svelte:component
this={iconright}
size={24}
class={clsx(iconSizing, "text-slate-400")}
/>
{/if}
</div>
);
{:else}
<a class={baseLinkStyle} on:click={onClick} href={link}>
{#if iconleft}
<svelte:component
this={iconleft}
size={24}
class={clsx(iconSizing, "text-sky-500")}
/>
{/if}
<span class={clsx("text-sky-500", `font-${weight}`)}>
{text}
</span>
{#if iconright}
<svelte:component this={iconright} size={24} class={clsx(iconSizing)} />
{/if}
</a>
{/if}

View File

@@ -0,0 +1,89 @@
<script lang="ts">
import { createDialog } from "@melt-ui/svelte";
import clsx from "clsx";
import IconX from "~icons/bi/x";
const { trigger, portal, overlay, content, title, description, close, open } =
createDialog();
</script>
<div>
<button
{...$trigger}
use:trigger
class={clsx(
"inline-flex items-center justify-center rounded-md bg-white px-4 py-2 font-medium leading-none text-magnum-700 shadow-lg hover:opacity-75"
)}
>
Open Dialog
</button>
<div use:portal>
{#if $open}
<div {...$overlay} class="fixed inset-0 z-20 bg-black/50" />
<div
class="fixed left-[50%] top-[50%] z-30 max-h-[85vh] w-[90vw] max-w-[450px]
translate-x-[-50%] translate-y-[-50%] rounded-md bg-white p-[25px]
shadow-lg"
{...$content}
use:content
>
<h2 {...title} class="m-0 text-lg font-medium text-black">
Edit profile
</h2>
<p {...description} class="mb-5 mt-[10px] leading-normal text-zinc-600">
Make changes to your profile here. Click save when you're done.
</p>
<fieldset class="mb-4 flex items-center gap-5">
<label class="w-[90px] text-right text-magnum-800" for="name">
Name
</label>
<input
class="inline-flex h-8 w-full flex-1 items-center justify-center rounded-sm border
border-solid px-3 leading-none text-magnum-800"
id="name"
value="Thomas G. Lopes"
/>
</fieldset>
<fieldset class="mb-4 flex items-center gap-5">
<label class="w-[90px] text-right text-magnum-800" for="username">
Username
</label>
<input
class="inline-flex h-8 w-full flex-1 items-center justify-center rounded-sm border
border-solid px-3 leading-none text-magnum-800"
id="username"
value="@thomasglopes"
/>
</fieldset>
<div class="mt-[25px] flex justify-end gap-4">
<button
{...close}
use:close
class="inline-flex h-[35px] items-center justify-center rounded-[4px] bg-zinc-100
px-4 font-medium leading-none text-zinc-600"
>
Cancel
</button>
<button
{...close}
use:close
class="inline-flex h-[35px] items-center justify-center rounded-[4px] bg-magnum-100
px-4 font-medium leading-none text-magnum-900"
>
Save changes
</button>
</div>
<button
{...close}
use:close
class="absolute right-[10px] top-[10px] inline-flex h-[25px] w-[25px] appearance-none items-center justify-center rounded-full
text-magnum-800 hover:bg-magnum-100 focus:shadow-magnum-400"
>
<IconX />
</button>
</div>
{/if}
</div>
</div>

View File

@@ -0,0 +1,30 @@
<script lang="ts">
import clsx from "clsx";
export let links: {
href: string;
label: string;
icon: any;
}[];
export let functionalLinks: {
label: string;
icon: any;
onClick: () => void;
}[];
export let linkStyling: string;
</script>
{#each links as link}
<a href={link.href}>
<div class={linkStyling}>
<svelte:component this={link.icon} class={clsx("h-6 w-6")} />
<p>{link.label}</p>
</div>
</a>
{/each}
{#each functionalLinks as link}
<button class={linkStyling} on:click={link.onClick}>
<svelte:component this={link.icon} class={clsx("h-6 w-6")} />
<p>{link.label}</p>
</button>
{/each}

View File

@@ -0,0 +1,82 @@
<script lang="ts">
import { createPagination } from "@melt-ui/svelte";
import IconChevronRight from "~icons/ion/chevron-right";
import IconChevronLeft from "~icons/ion/chevron-left";
import clsx from "clsx";
import Pill from "./pill.svelte";
export let count: number;
export let perPage: number;
export let page: number;
export let siblingCount: number;
export let setPage: (page: number) => void;
export let reducedText: boolean = false;
const { prevButton, nextButton, pages, pageTrigger, range, root } =
createPagination({
count,
perPage,
page,
siblingCount,
});
range.subscribe((r) => {
const newPointer = r.start;
if (newPointer !== page) {
setPage(newPointer / perPage);
}
page = newPointer / perPage;
});
let btnDimensions = "w-10 h-10";
</script>
<nav
class="flex w-full justify-between items-center gap-4 flex-col-reverse md:flex-row"
aria-label="pagination"
{...root}
>
<Pill
theme={"slate"}
smallerText
text={reducedText
? `${count} Entries`
: `Showing ${Number($range.start)} of ${Number($range.end)} entries (${count} rows)`}
/>
<div class="flex items-center gap-2">
<button
class={clsx(
"grid place-items-center rounded-md bg-slate-100 hover:bg-slate-200 text-sky-600 cursor-pointer transition-colors duration-50 active:bg-sky-500 active:text-slate-100",
btnDimensions,
)}
{...$prevButton}
use:prevButton><IconChevronLeft /></button
>
{#each $pages as _page (_page.key)}
{#if _page.type === "ellipsis"}
<span class="font-bold">. . .</span>
{:else}
<button
class={clsx(
btnDimensions,
"grid place-items-center rounded-md cursor-pointer transition-colors duration-50 active:bg-sky-500 active:text-slate-100",
page === _page.value
? "bg-sky-600 text-slate-100 hover:bg-sky-700"
: "bg-slate-100 hover:bg-slate-200 text-sky-600",
)}
{...$pageTrigger(_page)}
use:pageTrigger>{_page.value}</button
>
{/if}
{/each}
<button
class={clsx(
"grid place-items-center rounded-md bg-slate-100 hover:bg-slate-200 text-sky-600 cursor-pointer transition-colors duration-50 active:bg-sky-500 active:text-slate-100",
btnDimensions,
)}
{...$nextButton}
use:nextButton><IconChevronRight /></button
>
</div>
</nav>

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import clsx from "clsx";
const themes = {
green: "bg-green-100 text-green-500",
rose: "bg-rose-100 text-rose-500",
sky: "bg-sky-100 text-sky-500",
darkSky: "bg-sky-200/80 text-sky-800",
slate: "bg-slate-100 text-slate-500",
};
export let text: string = "";
export let smallerText: boolean = false;
export let theme: keyof typeof themes = "green";
</script>
<div
class={clsx(
themes[theme],
"p-2 px-4 font-semibold tracking-wide rounded-full capitalize",
smallerText ? "text-xs" : "text-sm",
)}
>
{text}
</div>

View File

@@ -0,0 +1,39 @@
<script lang="ts">
import clsx from "clsx";
import { randomString } from "$lib/utils";
type Option = {
id?: string | number;
label: string;
value: string;
};
export let componentId: string = randomString(10);
export let label: string = "";
export let options: Option[];
export let defaultChosen: string = "";
export let onSelect: (e: Event) => void = (_) => {};
export let fullWidth: boolean = false;
export let monoFont: boolean = false;
</script>
<div class={clsx("relative flex flex-col gap-1", fullWidth && "w-full")}>
<label class="mb-1 pl-1 text-sm" for={componentId}>{label}</label>
<select
id={componentId}
on:change={onSelect}
class={clsx(
"rounded-md relative cursor-pointer border text-left border-gray-300 outline-none p-2 transition-colors duration-200 bg-slate-50 hover:bg-slate-50 hover:border-black focus:border-sky-500 focus:bg-sky-50 focus:placeholder:text-sky-400 focus:text-sky-700",
fullWidth ? "w-full" : "w-max",
monoFont ? "font-mono" : "",
)}
>
{#each options as option}
<option
selected={defaultChosen === option.value}
class={"bg-white text-black"}
value={option.value}>{option.label}</option
>
{/each}
</select>
</div>

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import clsx from "clsx";
export let roundness: "none" | "md" | "lg" | "xl" | "2xl" | "full" = "lg";
export let height:
| "1"
| "2"
| "3"
| "4"
| "5"
| "6"
| "8"
| "12"
| "16"
| "20"
| "full" = "full";
</script>
<div
class={clsx(
`rounded-${roundness} h-${height} w-full animate-pulse bg-slate-200 p-4 text-slate-200 cursor-wait`,
)}
>
loader
</div>

View File

@@ -0,0 +1,65 @@
<script lang="ts">
import { randomString } from "$lib/utils";
import { createSwitch } from "@melt-ui/svelte";
import clsx from "clsx";
export let label: string;
export let name: string = "";
export let checked: boolean = false;
export let labelColor: "white" | "primary" | "black" = "black";
export let disabled: boolean = false;
export let onChange: (v: boolean) => void = () => {};
export let componentId: string = "";
export let otherOptions: any = {};
$: componentId = componentId || randomString(12);
const {
root,
input,
checked: checkedStore,
isChecked,
options,
} = createSwitch({
name,
disabled,
checked,
});
$: checkedStore.set(checked);
checkedStore.subscribe((v) => {
checked = v;
onChange(v);
});
$: options.update((o) => ({ ...o, disabled }));
</script>
<form>
<div class="flex w-full items-center gap-2">
<button
{...$root}
use:root
id={componentId}
class={clsx(
"relative h-7 min-w-[56px] max-w-[56px] cursor-pointer rounded-full bg-gray-300 outline-none transition-colors data-[state=checked]:bg-sky-500"
)}
{...otherOptions}
>
<span
class={clsx(
"block h-6 w-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[30px]",
$isChecked && "translate-x-[30px]"
)}
/>
<input {...$input} />
</button>
<span
class={clsx(
"md:text-md w-full text-sm font-medium tracking-wide",
labelColor === "white" && "text-white",
(labelColor === undefined || labelColor === "black") && "text-black",
labelColor === "primary" && "text-sky-500"
)}
>
{label}
</span>
</div>
</form>

View File

@@ -0,0 +1,59 @@
<script lang="ts">
import clsx from "clsx";
type TitleSize = "h1" | "h2" | "h3" | "h4";
type TitleFontWeight = "bold" | "semibold" | "normal";
export let text: string = "";
export let size: TitleSize = "h2";
export let weight: TitleFontWeight = "semibold";
export let capitalize: boolean = true;
const weights = {
bold: "font-bold",
semibold: "font-semibold",
normal: "font-normal",
} as const;
</script>
{#if size == "h1"}
<h1
class={clsx(
"text-3xl text-black lg:text-4xl",
weights[weight ?? "bold"],
capitalize && "capitalize",
)}
>
{text}
</h1>
{:else if size == "h2"}
<h2
class={clsx(
"text-2xl text-black lg:text-3xl",
weights[weight ?? "bold"],
capitalize && "capitalize",
)}
>
{text}
</h2>
{:else if size == "h3"}
<h3
class={clsx(
"text-xl text-black lg:text-2xl",
weights[weight ?? "bold"],
capitalize && "capitalize",
)}
>
{text}
</h3>
{:else if size == "h4"}
<h4
class={clsx(
"text-lg text-black lg:text-xl",
weights[weight ?? "bold"],
capitalize && "capitalize",
)}
>
{text}
</h4>
{/if}

View File

@@ -0,0 +1,8 @@
<script lang="ts">
import clsx from "clsx";
import SpinnerRingResize from "~icons/svg-spinners/ring-resize";
</script>
<span class="w-full grid place-items-center py-24">
<SpinnerRingResize class={clsx(`h-8 w-8 text-sky-500`)} />
</span>

View File

@@ -0,0 +1,30 @@
<script lang="ts">
import clsx from "clsx";
import SpinnerSync from "~icons/svg-spinners/blocks-wave";
import SpinnerRingResize from "~icons/svg-spinners/ring-resize";
export let loader: "normal" | "sync" = "normal";
export let color: "white" | "black" | "sky" = "white";
export let resizeToScreen: boolean = false;
</script>
<section
class={clsx(
"grid place-items-center",
resizeToScreen ? "h-screen w-screen" : "h-full w-full",
)}
>
<div class="h-max w-max">
{#if loader === "normal"}
<SpinnerRingResize
class={clsx(
"h-8 w-8",
color === "sky" ? "text-sky-500" : `text-${color}`,
)}
/>
{/if}
{#if loader === "sync"}
<SpinnerSync class={clsx("h-8 w-8", `text-${color}`)} />
{/if}
</div>
</section>

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import { onDestroy } from "svelte";
let interval: NodeJS.Timeout;
onMount(() => {
interval = setInterval(async () => {
const res = await fetch("/api/auth/verify");
const rC = res.status;
const currentPath = window.location.pathname;
// if rC is not 200 and the current path is not /auth/signin
if (rC !== 200 && !currentPath.includes("/auth/signin")) {
window.location.replace("/auth/signin");
}
}, 1000 * 10);
});
onDestroy(() => {
clearInterval(interval);
});
</script>
<slot />

View File

@@ -0,0 +1,21 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
type $$Props = AlertDialogPrimitive.ActionProps;
type $$Events = AlertDialogPrimitive.ActionEvents;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<AlertDialogPrimitive.Action
class={cn(buttonVariants(), className)}
{...$$restProps}
on:click
on:keydown
let:builder
>
<slot {builder} />
</AlertDialogPrimitive.Action>

View File

@@ -0,0 +1,21 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
type $$Props = AlertDialogPrimitive.CancelProps;
type $$Events = AlertDialogPrimitive.CancelEvents;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<AlertDialogPrimitive.Cancel
class={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
{...$$restProps}
on:click
on:keydown
let:builder
>
<slot {builder} />
</AlertDialogPrimitive.Cancel>

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import * as AlertDialog from "./index.js";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = AlertDialogPrimitive.ContentProps;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = undefined;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<AlertDialog.Portal>
<AlertDialog.Overlay />
<AlertDialogPrimitive.Content
{transition}
{transitionConfig}
class={cn(
"bg-background fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg md:w-full",
className
)}
{...$$restProps}
>
<slot />
</AlertDialogPrimitive.Content>
</AlertDialog.Portal>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = AlertDialogPrimitive.DescriptionProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<AlertDialogPrimitive.Description
class={cn("text-muted-foreground text-sm", className)}
{...$$restProps}
>
<slot />
</AlertDialogPrimitive.Description>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
{...$$restProps}
>
<slot />
</div>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...$$restProps}>
<slot />
</div>

View File

@@ -0,0 +1,21 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { fade } from "svelte/transition";
import { cn } from "$lib/utils.js";
type $$Props = AlertDialogPrimitive.OverlayProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = fade;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 150,
};
export { className as class };
</script>
<AlertDialogPrimitive.Overlay
{transition}
{transitionConfig}
class={cn("bg-background/80 fixed inset-0 z-50 backdrop-blur-sm ", className)}
{...$$restProps}
/>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
type $$Props = AlertDialogPrimitive.PortalProps;
</script>
<AlertDialogPrimitive.Portal {...$$restProps}>
<slot />
</AlertDialogPrimitive.Portal>

View File

@@ -0,0 +1,14 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = AlertDialogPrimitive.TitleProps;
let className: $$Props["class"] = undefined;
export let level: $$Props["level"] = "h3";
export { className as class };
</script>
<AlertDialogPrimitive.Title class={cn("text-lg font-semibold", className)} {level} {...$$restProps}>
<slot />
</AlertDialogPrimitive.Title>

View File

@@ -0,0 +1,40 @@
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import Title from "./alert-dialog-title.svelte";
import Action from "./alert-dialog-action.svelte";
import Cancel from "./alert-dialog-cancel.svelte";
import Portal from "./alert-dialog-portal.svelte";
import Footer from "./alert-dialog-footer.svelte";
import Header from "./alert-dialog-header.svelte";
import Overlay from "./alert-dialog-overlay.svelte";
import Content from "./alert-dialog-content.svelte";
import Description from "./alert-dialog-description.svelte";
const Root = AlertDialogPrimitive.Root;
const Trigger = AlertDialogPrimitive.Trigger;
export {
Root,
Title,
Action,
Cancel,
Portal,
Footer,
Header,
Trigger,
Overlay,
Content,
Description,
//
Root as AlertDialog,
Title as AlertDialogTitle,
Action as AlertDialogAction,
Cancel as AlertDialogCancel,
Portal as AlertDialogPortal,
Footer as AlertDialogFooter,
Header as AlertDialogHeader,
Trigger as AlertDialogTrigger,
Overlay as AlertDialogOverlay,
Content as AlertDialogContent,
Description as AlertDialogDescription,
};

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import { Button as ButtonPrimitive } from "bits-ui";
import { type Events, type Props, buttonVariants } from "./index.js";
import { cn } from "$lib/utils.js";
type $$Props = Props;
type $$Events = Events;
let className: $$Props["class"] = undefined;
export let variant: $$Props["variant"] = "default";
export let size: $$Props["size"] = "default";
export let builders: $$Props["builders"] = [];
export { className as class };
</script>
<ButtonPrimitive.Root
{builders}
class={cn(buttonVariants({ variant, size, className }))}
type="button"
{...$$restProps}
on:click
on:keydown
>
<slot />
</ButtonPrimitive.Root>

View File

@@ -0,0 +1,54 @@
import { type VariantProps, tv } from "tailwind-variants";
import type { Button as ButtonPrimitive } from "bits-ui";
import Root from "./button.svelte";
const buttonVariants = tv({
base: "ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center 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",
variants: {
variant: {
default: "bg-sky-500 text-white hover:bg-sky-600/90",
primaryInverted: "bg-sky-50 text-sky-600 hover:bg-sky-100",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
destructiveInverted:
"bg-destructive/10 text-rose-500 hover:bg-destructive/30",
success: "bg-emerald-500 text-emerald-50 hover:bg-emerald-600/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",
},
});
type Variant = VariantProps<typeof buttonVariants>["variant"];
type Size = VariantProps<typeof buttonVariants>["size"];
type Props = ButtonPrimitive.Props & {
variant?: Variant;
size?: Size;
};
type Events = ButtonPrimitive.Events;
export {
Root,
type Props,
type Events,
//
Root as Button,
type Props as ButtonProps,
type Events as ButtonEvents,
buttonVariants,
};

View File

@@ -0,0 +1,35 @@
<script lang="ts">
import { Checkbox as CheckboxPrimitive } from "bits-ui";
import Check from "~icons/mdi/check";
import Minus from "~icons/ic/outline-minus";
import { cn } from "$lib/utils.js";
type $$Props = CheckboxPrimitive.Props;
type $$Events = CheckboxPrimitive.Events;
let className: $$Props["class"] = undefined;
export let checked: $$Props["checked"] = false;
export { className as class };
</script>
<CheckboxPrimitive.Root
class={cn(
"border-sky-900 ring-offset-background focus-visible:ring-ring data-[state=checked]:border-sky-600 data-[state=checked]:bg-sky-600 data-[state=checked]:text-primary-foreground peer box-content h-4 w-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
className,
)}
bind:checked
{...$$restProps}
on:click
>
<CheckboxPrimitive.Indicator
class={cn("flex h-4 w-4 items-center justify-center text-current")}
let:isChecked
let:isIndeterminate
>
{#if isChecked}
<Check class="h-3.5 w-3.5" />
{:else if isIndeterminate}
<Minus class="h-3.5 w-3.5" />
{/if}
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>

View File

@@ -0,0 +1,6 @@
import Root from "./checkbox.svelte";
export {
Root,
//
Root as Checkbox,
};

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import X from "~icons/mdi/window-close";
import * as Dialog from "./index.js";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = DialogPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 200,
};
export { className as class };
</script>
<Dialog.Portal>
<Dialog.Overlay />
<DialogPrimitive.Content
{transition}
{transitionConfig}
class={cn(
"bg-background fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg md:w-full",
className,
)}
{...$$restProps}
>
<slot />
<DialogPrimitive.Close
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none"
>
<X class="h-4 w-4" />
<span class="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</Dialog.Portal>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DialogPrimitive.DescriptionProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DialogPrimitive.Description
class={cn("text-muted-foreground text-sm", className)}
{...$$restProps}
>
<slot />
</DialogPrimitive.Description>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
{...$$restProps}
>
<slot />
</div>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...$$restProps}>
<slot />
</div>

View File

@@ -0,0 +1,21 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { fade } from "svelte/transition";
import { cn } from "$lib/utils.js";
type $$Props = DialogPrimitive.OverlayProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = fade;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 150,
};
export { className as class };
</script>
<DialogPrimitive.Overlay
{transition}
{transitionConfig}
class={cn("bg-background/80 fixed inset-0 z-50 backdrop-blur-sm", className)}
{...$$restProps}
/>

View File

@@ -0,0 +1,8 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
type $$Props = DialogPrimitive.PortalProps;
</script>
<DialogPrimitive.Portal {...$$restProps}>
<slot />
</DialogPrimitive.Portal>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = DialogPrimitive.TitleProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DialogPrimitive.Title
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...$$restProps}
>
<slot />
</DialogPrimitive.Title>

View File

@@ -0,0 +1,37 @@
import { Dialog as DialogPrimitive } from "bits-ui";
import Title from "./dialog-title.svelte";
import Portal from "./dialog-portal.svelte";
import Footer from "./dialog-footer.svelte";
import Header from "./dialog-header.svelte";
import Overlay from "./dialog-overlay.svelte";
import Content from "./dialog-content.svelte";
import Description from "./dialog-description.svelte";
const Root = DialogPrimitive.Root;
const Trigger = DialogPrimitive.Trigger;
const Close = DialogPrimitive.Close;
export {
Root,
Title,
Portal,
Footer,
Header,
Trigger,
Overlay,
Content,
Description,
Close,
//
Root as Dialog,
Title as DialogTitle,
Portal as DialogPortal,
Footer as DialogFooter,
Header as DialogHeader,
Trigger as DialogTrigger,
Overlay as DialogOverlay,
Content as DialogContent,
Description as DialogDescription,
Close as DialogClose,
};

View File

@@ -0,0 +1,29 @@
import Root from "./input.svelte";
export type FormInputEvent<T extends Event = Event> = T & {
currentTarget: EventTarget & HTMLInputElement;
};
export type InputEvents = {
blur: FormInputEvent<FocusEvent>;
change: FormInputEvent<Event>;
click: FormInputEvent<MouseEvent>;
focus: FormInputEvent<FocusEvent>;
focusin: FormInputEvent<FocusEvent>;
focusout: FormInputEvent<FocusEvent>;
keydown: FormInputEvent<KeyboardEvent>;
keypress: FormInputEvent<KeyboardEvent>;
keyup: FormInputEvent<KeyboardEvent>;
mouseover: FormInputEvent<MouseEvent>;
mouseenter: FormInputEvent<MouseEvent>;
mouseleave: FormInputEvent<MouseEvent>;
mousemove: FormInputEvent<MouseEvent>;
paste: FormInputEvent<ClipboardEvent>;
input: FormInputEvent<InputEvent>;
wheel: FormInputEvent<WheelEvent>;
};
export {
Root,
//
Root as Input,
};

View File

@@ -0,0 +1,42 @@
<script lang="ts">
import type { HTMLInputAttributes } from "svelte/elements";
import type { InputEvents } from "./index.js";
import { cn } from "$lib/utils.js";
type $$Props = HTMLInputAttributes;
type $$Events = InputEvents;
let className: $$Props["class"] = undefined;
export let value: $$Props["value"] = undefined;
export { className as class };
// Workaround for https://github.com/sveltejs/svelte/issues/9305
// Fixed in Svelte 5, but not backported to 4.x.
export let readonly: $$Props["readonly"] = undefined;
</script>
<input
class={cn(
"border-gray-300 placeholder:text-muted-foreground flex w-full rounded-md border px-3 py-2 focus-visible:outline-none duration-200 transition-colors disabled:cursor-not-allowed disabled:opacity-50 bg-slate-50 hover:bg-slate-50 hover:border-black focus:border-sky-500 focus:bg-sky-50 focus:placeholder:text-sky-400 focus:text-sky-700",
className,
)}
bind:value
{readonly}
on:blur
on:change
on:click
on:focus
on:focusin
on:focusout
on:keydown
on:keypress
on:keyup
on:mouseover
on:mouseenter
on:mouseleave
on:mousemove
on:paste
on:input
on:wheel|passive
{...$$restProps}
/>

View File

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

View File

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

View File

@@ -0,0 +1,28 @@
import Root from "./table.svelte";
import Body from "./table-body.svelte";
import Caption from "./table-caption.svelte";
import Cell from "./table-cell.svelte";
import Footer from "./table-footer.svelte";
import Head from "./table-head.svelte";
import Header from "./table-header.svelte";
import Row from "./table-row.svelte";
export {
Root,
Body,
Caption,
Cell,
Footer,
Head,
Header,
Row,
//
Root as Table,
Body as TableBody,
Caption as TableCaption,
Cell as TableCell,
Footer as TableFooter,
Head as TableHead,
Header as TableHeader,
Row as TableRow,
};

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLTableSectionElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<tbody class={cn("[&_tr:last-child]:border-0", className)} {...$$restProps}>
<slot />
</tbody>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLTableCaptionElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<caption class={cn("text-muted-foreground mt-4 text-sm", className)} {...$$restProps}>
<slot />
</caption>

View File

@@ -0,0 +1,18 @@
<script lang="ts">
import type { HTMLTdAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLTdAttributes;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<td
class={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...$$restProps}
on:click
on:keydown
>
<slot />
</td>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLTableSectionElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<tfoot class={cn("bg-primary text-primary-foreground font-medium", className)} {...$$restProps}>
<slot />
</tfoot>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import type { HTMLThAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLThAttributes;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<th
class={cn(
"text-muted-foreground h-12 px-4 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0",
className
)}
{...$$restProps}
>
<slot />
</th>

View File

@@ -0,0 +1,14 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLTableSectionElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<thead class={cn("[&_tr]:border-b", className)} {...$$restProps} on:click on:keydown>
<slot />
</thead>

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLTableRowElement> & {
"data-state"?: unknown;
};
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<tr
class={cn(
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
className
)}
{...$$restProps}
on:click
on:keydown
>
<slot />
</tr>

View File

@@ -0,0 +1,15 @@
<script lang="ts">
import type { HTMLTableAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLTableAttributes;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class="relative w-full overflow-auto">
<table class={cn("w-full caption-bottom text-sm", className)} {...$$restProps}>
<slot />
</table>
</div>

View File

@@ -0,0 +1,28 @@
import Root from "./textarea.svelte";
type FormTextareaEvent<T extends Event = Event> = T & {
currentTarget: EventTarget & HTMLTextAreaElement;
};
type TextareaEvents = {
blur: FormTextareaEvent<FocusEvent>;
change: FormTextareaEvent<Event>;
click: FormTextareaEvent<MouseEvent>;
focus: FormTextareaEvent<FocusEvent>;
keydown: FormTextareaEvent<KeyboardEvent>;
keypress: FormTextareaEvent<KeyboardEvent>;
keyup: FormTextareaEvent<KeyboardEvent>;
mouseover: FormTextareaEvent<MouseEvent>;
mouseenter: FormTextareaEvent<MouseEvent>;
mouseleave: FormTextareaEvent<MouseEvent>;
paste: FormTextareaEvent<ClipboardEvent>;
input: FormTextareaEvent<InputEvent>;
};
export {
Root,
//
Root as Textarea,
type TextareaEvents,
type FormTextareaEvent,
};

View File

@@ -0,0 +1,38 @@
<script lang="ts">
import type { HTMLTextareaAttributes } from "svelte/elements";
import type { TextareaEvents } from "./index.js";
import { cn } from "$lib/utils.js";
type $$Props = HTMLTextareaAttributes;
type $$Events = TextareaEvents;
let className: $$Props["class"] = undefined;
export let value: $$Props["value"] = undefined;
export { className as class };
// Workaround for https://github.com/sveltejs/svelte/issues/9305
// Fixed in Svelte 5, but not backported to 4.x.
export let readonly: $$Props["readonly"] = undefined;
</script>
<textarea
class={cn(
"border-gray-300 placeholder:text-muted-foreground flex min-h-24 w-full rounded-md border px-3 py-2 focus-visible:outline-none duration-200 transition-colors disabled:cursor-not-allowed disabled:opacity-50 bg-slate-50 hover:bg-slate-50 hover:border-black focus:border-sky-500 focus:bg-sky-50 focus:placeholder:text-sky-400 focus:text-sky-700",
className,
)}
bind:value
{readonly}
on:blur
on:change
on:click
on:focus
on:keydown
on:keypress
on:keyup
on:mouseover
on:mouseenter
on:mouseleave
on:paste
on:input
{...$$restProps}
></textarea>