mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-02-16 00:46:07 -05:00
john 2024-11-26 08:53:48
This commit is contained in:
26
web/src/lib/components/ui/button/button.svelte
Normal file
26
web/src/lib/components/ui/button/button.svelte
Normal 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>
|
||||
31
web/src/lib/components/ui/button/index.js
Normal file
31
web/src/lib/components/ui/button/index.js
Normal 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
|
||||
};
|
||||
32
web/src/lib/components/ui/buymeacoffee/BuyMeCoffee.svelte
Normal file
32
web/src/lib/components/ui/buymeacoffee/BuyMeCoffee.svelte
Normal 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>
|
||||
6
web/src/lib/components/ui/input/index.ts
Normal file
6
web/src/lib/components/ui/input/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import Root from "./input.svelte";
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
};
|
||||
33
web/src/lib/components/ui/input/input.svelte
Normal file
33
web/src/lib/components/ui/input/input.svelte
Normal 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}
|
||||
/>
|
||||
6
web/src/lib/components/ui/label/index.js
Normal file
6
web/src/lib/components/ui/label/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import Root from "./label.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Root as Label
|
||||
};
|
||||
15
web/src/lib/components/ui/label/label.svelte
Normal file
15
web/src/lib/components/ui/label/label.svelte
Normal 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>
|
||||
6
web/src/lib/components/ui/select/index.js
Normal file
6
web/src/lib/components/ui/select/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import Root from "./select.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Root as Select
|
||||
};
|
||||
30
web/src/lib/components/ui/select/select-content.svelte
Normal file
30
web/src/lib/components/ui/select/select-content.svelte
Normal 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>
|
||||
16
web/src/lib/components/ui/select/select-item.svelte
Normal file
16
web/src/lib/components/ui/select/select-item.svelte
Normal 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>
|
||||
10
web/src/lib/components/ui/select/select-label.svelte
Normal file
10
web/src/lib/components/ui/select/select-label.svelte
Normal 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>
|
||||
8
web/src/lib/components/ui/select/select-separator.svelte
Normal file
8
web/src/lib/components/ui/select/select-separator.svelte
Normal 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} />
|
||||
20
web/src/lib/components/ui/select/select-trigger.svelte
Normal file
20
web/src/lib/components/ui/select/select-trigger.svelte
Normal 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>
|
||||
18
web/src/lib/components/ui/select/select-value.svelte
Normal file
18
web/src/lib/components/ui/select/select-value.svelte
Normal 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>
|
||||
24
web/src/lib/components/ui/select/select.svelte
Normal file
24
web/src/lib/components/ui/select/select.svelte
Normal 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>
|
||||
6
web/src/lib/components/ui/slider/index.js
Normal file
6
web/src/lib/components/ui/slider/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import Root from "./slider.svelte";
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Slider,
|
||||
};
|
||||
89
web/src/lib/components/ui/slider/slider.svelte
Normal file
89
web/src/lib/components/ui/slider/slider.svelte
Normal 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>
|
||||
9
web/src/lib/components/ui/spinner/spinner.svelte
Normal file
9
web/src/lib/components/ui/spinner/spinner.svelte
Normal 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} />
|
||||
66
web/src/lib/components/ui/tag-list/TagList.svelte
Normal file
66
web/src/lib/components/ui/tag-list/TagList.svelte
Normal 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>
|
||||
6
web/src/lib/components/ui/textarea/index.js
Normal file
6
web/src/lib/components/ui/textarea/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import Root from "./textarea.svelte";
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Textarea,
|
||||
};
|
||||
29
web/src/lib/components/ui/textarea/textarea.svelte
Normal file
29
web/src/lib/components/ui/textarea/textarea.svelte
Normal 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>
|
||||
Reference in New Issue
Block a user