john 2024-11-26 08:53:48

This commit is contained in:
John
2024-11-26 08:53:48 -05:00
parent 23b495c8f7
commit 30f37ea633
63 changed files with 2050 additions and 1 deletions

View File

@@ -0,0 +1,26 @@
<script lang="ts">
import { cn } from "$lib/types/utils";
import { buttonVariants } from "./index.js";
let className = undefined;
export let variant = "";
export let size = "default";
export { className as class };
$: classes = cn(
buttonVariants.base,
buttonVariants.variants.variant[variant as keyof typeof buttonVariants.variants.variant],
buttonVariants.variants.size[size as keyof typeof buttonVariants.variants.size],
className
);
</script>
<button
class={classes}
type="button"
on:click
on:keydown
{...$$restProps}
>
<slot />
</button>

View File

@@ -0,0 +1,31 @@
import Root from "./button.svelte";
const buttonVariants = {
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50",
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm",
outline: "border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline"
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9"
}
},
defaultVariants: {
variant: "default",
size: "default"
}
};
export {
Root,
Root as Button,
buttonVariants
};

View File

@@ -0,0 +1,32 @@
<script lang="ts">
export let url: string = 'https://www.buymeacoffee.com/johnconnor.sec';
export let text: string = 'Buy me a coffee';
</script>
<a
href={url}
target="_blank"
rel="noopener noreferrer"
class="text-sm px-3 py-1.5 btn variant-filled-tertiary hover:variant-filled-secondary transition-all duration-200 flex items-center gap-2"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="transition-transform duration-200 group-hover:rotate-12"
>
<path d="M17 8h1a4 4 0 1 1 0 8h-1" />
<path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z" />
<line x1="6" y1="2" x2="6" y2="4" />
<line x1="10" y1="2" x2="10" y2="4" />
<line x1="14" y1="2" x2="14" y2="4" />
</svg>
{text}
</a>

View File

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

View File

@@ -0,0 +1,33 @@
<script lang="ts">
import { cn } from "$lib/types/utils";
let className: string | undefined = undefined;
export let value = undefined;
export { className as class };
export let readonly = undefined;
</script>
<input
class={cn(
"border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-sm shadow-lg transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50",
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,6 @@
import Root from "./label.svelte";
export {
Root,
Root as Label
};

View File

@@ -0,0 +1,15 @@
<script>
import { cn } from "$lib/types/utils.ts";
let className = undefined;
export { className as class };
</script>
<label
class={cn(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
className
)}
{...$$restProps}
>
<slot />
</label>

View File

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

View File

@@ -0,0 +1,30 @@
<script>
import { Select as SelectPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
import { getContext } from "svelte";
let className = undefined;
export let sideOffset = 4;
export let position = "popper";
</script>
<div
class={cn(
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
>
<SelectPrimitive.Content {position} {sideOffset} {...$$restProps}>
<div
class={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
<slot />
</div>
</SelectPrimitive.Content>
</div>

View File

@@ -0,0 +1,16 @@
<script>
import { cn } from "$lib/utils";
let className = undefined;
export let value;
export let disabled = false;
export { className as class };
</script>
<option
{value}
{disabled}
class={cn("relative flex w-full cursor-default select-none py-1.5 pl-2 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className)}
>
<slot />
</option>

View File

@@ -0,0 +1,10 @@
<script>
import { Select as SelectPrimitive } from "bits-ui";
import { cn } from "$lib/types/utils.ts";
let className = undefined;
export { className as class };
</script>
<SelectPrimitive.Label class={cn("px-2 py-1.5 text-sm variant-filled-secondary font-semibold", className)} {...$$restProps}>
<slot />
</SelectPrimitive.Label>

View File

@@ -0,0 +1,8 @@
<script>
import { Select as SelectPrimitive } from "bits-ui";
import { cn } from "$lib/types/utils.ts";
let className = undefined;
export { className as class };
</script>
<SelectPrimitive.Separator class={cn("bg-muted -mx-1 my-1 h-px", className)} {...$$restProps} />

View File

@@ -0,0 +1,20 @@
<script>
import { Select as SelectPrimitive } from "bits-ui";
import { ChevronDown } from "lucide-svelte";
import { cn } from "$lib/utils";
import { getContext } from "svelte";
let className = undefined;
export { className as class };
</script>
<SelectPrimitive.Trigger
class={cn(
"flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...$$restProps}
>
<slot />
<ChevronDown class="h-4 w-4 opacity-50" />
</SelectPrimitive.Trigger>

View File

@@ -0,0 +1,18 @@
<script lang="ts">
import { Select as SelectPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
type $$Props = SelectPrimitive.ValueProps;
let className: $$Props["class"] = undefined;
export { className as class };
export let placeholder: $$Props["placeholder"] = undefined;
</script>
<SelectPrimitive.Value
class={cn("text-sm", className)}
{placeholder}
{...$$restProps}
>
<slot />
</SelectPrimitive.Value>

View File

@@ -0,0 +1,24 @@
<script lang="ts">
import { cn } from "$lib/types/utils.ts";
import { ChevronDown } from "lucide-svelte";
export let value: any = undefined;
export let disabled = false;
let className = undefined;
export { className as class };
</script>
<div class="relative">
<select
{disabled}
bind:value
class={cn(
"flex h-9 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm shadow-lg ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 appearance-none",
className
)}
{...$$restProps}
>
<slot />
</select>
<ChevronDown class="absolute right-3 top-2.5 h-4 w-4 opacity-50 pointer-events-none" />
</div>

View File

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

View File

@@ -0,0 +1,89 @@
<script>
import { cn } from "$lib/types/utils.ts";
let className = undefined;
export let value = 0;
export let min = 0;
export let max = 100;
export let step = 1;
export { className as class };
let sliderEl;
let isDragging = false;
$: percentage = ((value - min) / (max - min)) * 100;
function handleMouseDown(e) {
isDragging = true;
updateValue(e);
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
}
function handleMouseMove(e) {
if (!isDragging) return;
updateValue(e);
}
function handleMouseUp() {
isDragging = false;
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
}
function updateValue(e) {
if (!sliderEl) return;
const rect = sliderEl.getBoundingClientRect();
const pos = (e.clientX - rect.left) / rect.width;
const rawValue = min + (max - min) * pos;
const steppedValue = Math.round(rawValue / step) * step;
value = Math.max(min, Math.min(max, steppedValue));
}
function handleKeyDown(e) {
const step = e.shiftKey ? 10 : 1;
switch (e.key) {
case 'ArrowLeft':
case 'ArrowDown':
e.preventDefault();
value = Math.max(min, value - step);
break;
case 'ArrowRight':
case 'ArrowUp':
e.preventDefault();
value = Math.min(max, value + step);
break;
case 'Home':
e.preventDefault();
value = min;
break;
case 'End':
e.preventDefault();
value = max;
break;
}
}
</script>
<div
bind:this={sliderEl}
class={cn("relative flex w-full touch-none select-none items-center", className)}
on:mousedown={handleMouseDown}
on:keydown={handleKeyDown}
role="slider"
tabindex="0"
aria-valuemin={min}
aria-valuemax={max}
aria-valuenow={value}
>
<div class="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
<div
class="absolute h-full bg-primary transition-all"
style="width: {percentage}%"
/>
</div>
<div
class="absolute h-4 w-4 rounded-full border border-secondary bg-primary-500 shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"
style="left: calc({percentage}% - 0.5rem)"
/>
</div>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
import { cn } from '$lib/types/utils';
import { Loader2 } from 'lucide-svelte';
let className: string | undefined = undefined;
export { className as class };
</script>
<Loader2 class={cn('h-4 w-4 animate-spin', className)} {...$$restProps} />

View File

@@ -0,0 +1,66 @@
<script lang="ts">
import { ChevronLeft, ChevronRight } from 'lucide-svelte';
import { slide } from 'svelte/transition';
import { cn } from '$lib/types/utils';
export let tags: string[] = [];
export let tagsPerPage = 5;
export let className: string | undefined = undefined;
let currentPage = 0;
let containerWidth: number;
$: totalPages = Math.ceil(tags.length / tagsPerPage);
$: startIndex = currentPage * tagsPerPage;
$: endIndex = Math.min(startIndex + tagsPerPage, tags.length);
$: visibleTags = tags.slice(startIndex, endIndex);
$: canGoBack = currentPage > 0;
$: canGoForward = currentPage < totalPages - 1;
function nextPage() {
if (canGoForward) {
currentPage++;
}
}
function prevPage() {
if (canGoBack) {
currentPage--;
}
}
</script>
<div class={cn('relative flex items-center gap-2', className)} bind:clientWidth={containerWidth}>
{#if totalPages > 1 && canGoBack}
<button
on:click={prevPage}
class="flex h-6 w-6 items-center justify-center rounded-md border bg-background hover:bg-muted"
transition:slide
>
<ChevronLeft class="h-4 w-4" />
<span class="sr-only">Previous page</span>
</button>
{/if}
<div class="flex flex-wrap gap-2">
{#each visibleTags as tag (tag)}
<a
href="/tags/{tag}"
class="inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-semibold transition-colors hover:bg-muted"
>
{tag}
</a>
{/each}
</div>
{#if totalPages > 1 && canGoForward}
<button
on:click={nextPage}
class="flex h-6 w-6 items-center justify-center rounded-md border bg-background hover:bg-muted"
transition:slide
>
<ChevronRight class="h-4 w-4" />
<span class="sr-only">Next page</span>
</button>
{/if}
</div>

View File

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

View File

@@ -0,0 +1,29 @@
<script>
import { cn } from "$lib/types/utils.ts";
let className = undefined;
export let value = undefined;
export { className as class };
export let readonly = undefined;
</script>
<textarea
class={cn(
"border-input placeholder:text-muted-foreground focus-visible:ring-ring flex min-h-[60px] w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-lg focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50",
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>