fix: restore test-used functions, silence tx.Rollback closed errors, prune knip dead code
CI / Frontend deps check (push) Successful in 22s
CI / Go vulnerabilities (push) Successful in 32s
CI / Go build (push) Successful in 32s
CI / go mod tidy (push) Successful in 13s
CI / Knip (push) Failing after 33s
CI / Frontend build (push) Successful in 1m12s
CI / Svelte strict check (push) Has been skipped
CI / Frontend QC (audit) (push) Has been skipped
CI / Frontend QC (typecheck) (push) Has been skipped
CI / Frontend QC (lint) (push) Has been skipped
CI / Go vet (push) Successful in 57s
CI / golangci-lint (push) Successful in 1m8s
CI / Tests (prod) (push) Successful in 1m45s
CI / Tests (dev) (push) Successful in 2m5s
CI / Race (prod) (push) Successful in 3m27s
CI / Race (dev) (push) Successful in 4m52s
CI / Frontend deps check (push) Successful in 22s
CI / Go vulnerabilities (push) Successful in 32s
CI / Go build (push) Successful in 32s
CI / go mod tidy (push) Successful in 13s
CI / Knip (push) Failing after 33s
CI / Frontend build (push) Successful in 1m12s
CI / Svelte strict check (push) Has been skipped
CI / Frontend QC (audit) (push) Has been skipped
CI / Frontend QC (typecheck) (push) Has been skipped
CI / Frontend QC (lint) (push) Has been skipped
CI / Go vet (push) Successful in 57s
CI / golangci-lint (push) Successful in 1m8s
CI / Tests (prod) (push) Successful in 1m45s
CI / Tests (dev) (push) Successful in 2m5s
CI / Race (prod) (push) Successful in 3m27s
CI / Race (dev) (push) Successful in 4m52s
Restore processImage (images.go) and nonDepositPaymentType (handlers.go) with //nolint:unused — used in test files. Fix 97 tx.Rollback defers to silently discard expected "tx is closed" error after commit. Frontend: remove 44 unused shadcn-svelte files, 2 dead components, 9 stale npm deps, prune unused exports. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,70 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import Calendar from '$lib/components/ui/calendar/calendar.svelte';
|
||||
import { CalendarDate, getLocalTimeZone } from '@internationalized/date';
|
||||
|
||||
let value = $state<CalendarDate | undefined>(new CalendarDate(2025, 6, 12));
|
||||
let selectedTime = $state<string | null>('10:00');
|
||||
|
||||
const bookedDates = Array.from({ length: 3 }, (_, i) => new CalendarDate(2025, 6, 17 + i));
|
||||
const timeSlots = Array.from({ length: 37 }, (_, i) => {
|
||||
const totalMinutes = i * 15;
|
||||
const hour = Math.floor(totalMinutes / 60) + 9;
|
||||
const minute = totalMinutes % 60;
|
||||
return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<Card.Root class="gap-0 p-0">
|
||||
<Card.Content class="relative p-0 md:pr-48">
|
||||
<div class="p-6">
|
||||
<Calendar
|
||||
type="single"
|
||||
bind:value
|
||||
isDateUnavailable={(date) => bookedDates.some((d) => d.compare(date) === 0)}
|
||||
class="bg-transparent p-0 [--cell-size:--spacing(10)] data-unavailable:line-through data-unavailable:opacity-100 md:[--cell-size:--spacing(12)] [&_[data-outside-month]]:hidden"
|
||||
weekdayFormat="short"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="inset-y-0 right-0 no-scrollbar flex max-h-72 w-full scroll-pb-6 flex-col gap-4 overflow-y-auto border-t p-6 md:absolute md:max-h-none md:w-48 md:border-t-0 md:border-l"
|
||||
>
|
||||
<div class="grid gap-2">
|
||||
{#each timeSlots as time (time)}
|
||||
<Button
|
||||
variant={selectedTime === time ? 'default' : 'outline'}
|
||||
onclick={() => (selectedTime = time)}
|
||||
class="w-full"
|
||||
>
|
||||
{time}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
<Card.Footer class="flex flex-col gap-4 border-t px-6 !py-5 md:flex-row">
|
||||
<div class="text-sm">
|
||||
{#if value && selectedTime}
|
||||
Your meeting is booked for
|
||||
<span class="font-medium">
|
||||
{value.toDate(getLocalTimeZone()).toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'short'
|
||||
})}
|
||||
</span>
|
||||
at <span class="font-medium">{selectedTime}</span>.
|
||||
{:else}
|
||||
Select a date and time for your meeting.
|
||||
{/if}
|
||||
</div>
|
||||
<Button
|
||||
disabled={!value || !selectedTime}
|
||||
class="w-full md:ml-auto md:w-auto"
|
||||
variant="outline"
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.FallbackProps = $props();
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Fallback
|
||||
bind:ref
|
||||
data-slot="avatar-fallback"
|
||||
class={cn('flex size-full items-center justify-center rounded-full bg-muted', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,17 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.ImageProps = $props();
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Image
|
||||
bind:ref
|
||||
data-slot="avatar-image"
|
||||
class={cn('aspect-square size-full', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,17 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Root
|
||||
bind:ref
|
||||
data-slot="avatar"
|
||||
class={cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,13 +0,0 @@
|
||||
import Root from './avatar.svelte';
|
||||
import Image from './avatar-image.svelte';
|
||||
import Fallback from './avatar-fallback.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
Image,
|
||||
Fallback,
|
||||
//
|
||||
Root as Avatar,
|
||||
Image as AvatarImage,
|
||||
Fallback as AvatarFallback
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as Button from '$lib/components/ui/button/index.js';
|
||||
|
||||
const { ...restProps }: Record<string, unknown> = $props();
|
||||
let ref = $state<HTMLElement | null>(null);
|
||||
</script>
|
||||
|
||||
<Button.Root bind:ref type="submit" {...restProps} />
|
||||
@@ -1,17 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn, type WithoutChild } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChild<FormPrimitive.DescriptionProps> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Description
|
||||
bind:ref
|
||||
data-slot="form-description"
|
||||
class={cn('text-sm text-muted-foreground', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,24 +0,0 @@
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import type { FormPathLeaves } from 'sveltekit-superforms';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef, type WithoutChildren } 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>
|
||||
@@ -1,30 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn, type WithoutChild } 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-sm font-medium text-destructive', 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>
|
||||
@@ -1,24 +0,0 @@
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import type { FormPath } from 'sveltekit-superforms';
|
||||
import { cn, type WithElementRef, type WithoutChildren } 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} data-slot="form-item" class={cn('space-y-2', className)} {...restProps}>
|
||||
{@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })}
|
||||
</div>
|
||||
{/snippet}
|
||||
</FormPrimitive.Field>
|
||||
@@ -1,15 +0,0 @@
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import type { FormPath } from 'sveltekit-superforms';
|
||||
import { cn, type WithoutChild } 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} />
|
||||
@@ -1,24 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { cn, type WithoutChild } 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}
|
||||
data-slot="form-label"
|
||||
class={cn('data-[fs-error]:text-destructive', className)}
|
||||
>
|
||||
{@render children?.()}
|
||||
</Label>
|
||||
{/snippet}
|
||||
</FormPrimitive.Label>
|
||||
@@ -1,16 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn, type WithoutChild } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChild<FormPrimitive.LegendProps> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Legend
|
||||
bind:ref
|
||||
class={cn('text-sm leading-none font-medium data-[fs-error]:text-destructive', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,33 +0,0 @@
|
||||
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
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { type ButtonElementProps, Button } from '$lib/components/ui/button';
|
||||
import { useImageCropperCancel } from './image-cropper.svelte.js';
|
||||
import Trash2Icon from '@lucide/svelte/icons/trash-2';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
variant = 'outline',
|
||||
size = 'sm',
|
||||
onclick,
|
||||
...rest
|
||||
}: ButtonElementProps = $props();
|
||||
|
||||
const cancelState = useImageCropperCancel();
|
||||
</script>
|
||||
|
||||
<Button
|
||||
{...rest}
|
||||
bind:ref
|
||||
{size}
|
||||
{variant}
|
||||
onclick={(
|
||||
e: MouseEvent & {
|
||||
currentTarget: EventTarget & HTMLButtonElement;
|
||||
}
|
||||
) => {
|
||||
onclick?.(e);
|
||||
|
||||
cancelState.onclick();
|
||||
}}
|
||||
>
|
||||
<Trash2Icon />
|
||||
<span>Cancel</span>
|
||||
</Button>
|
||||
@@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils.js';
|
||||
import type { ImageCropperControlsProps } from './types';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...rest
|
||||
}: ImageCropperControlsProps = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
{...rest}
|
||||
bind:this={ref}
|
||||
class={cn('flex w-full place-items-center justify-center gap-2', className)}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -1,34 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { type ButtonElementProps, Button } from '$lib/components/ui/button';
|
||||
import { useImageCropperCrop } from './image-cropper.svelte.js';
|
||||
import CropIcon from '@lucide/svelte/icons/crop';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
variant = 'default',
|
||||
size = 'sm',
|
||||
onclick,
|
||||
...rest
|
||||
}: ButtonElementProps = $props();
|
||||
|
||||
const cropState = useImageCropperCrop();
|
||||
</script>
|
||||
|
||||
<Button
|
||||
{...rest}
|
||||
bind:ref
|
||||
{size}
|
||||
{variant}
|
||||
onclick={(
|
||||
e: MouseEvent & {
|
||||
currentTarget: EventTarget & HTMLButtonElement;
|
||||
}
|
||||
) => {
|
||||
onclick?.(e);
|
||||
|
||||
cropState.onclick();
|
||||
}}
|
||||
>
|
||||
<CropIcon />
|
||||
<span>Crop</span>
|
||||
</Button>
|
||||
@@ -1,26 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Cropper from 'svelte-easy-crop';
|
||||
import { useImageCropperCropper } from './image-cropper.svelte.js';
|
||||
import type { ImageCropperCropperProps } from './types.js';
|
||||
|
||||
const {
|
||||
cropShape = 'round',
|
||||
aspect = 1,
|
||||
showGrid = false,
|
||||
...rest
|
||||
}: ImageCropperCropperProps = $props();
|
||||
|
||||
const cropperState = useImageCropperCropper();
|
||||
</script>
|
||||
|
||||
<!-- This needs to be relative https://github.com/ValentinH/svelte-easy-crop#basic-usage -->
|
||||
<div class="relative h-full w-full">
|
||||
<Cropper
|
||||
{...rest}
|
||||
{cropShape}
|
||||
{aspect}
|
||||
{showGrid}
|
||||
image={cropperState.rootState.tempUrl}
|
||||
oncropcomplete={cropperState.onCropComplete}
|
||||
/>
|
||||
</div>
|
||||
@@ -1,25 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { useImageCropperDialog } from './image-cropper.svelte.js';
|
||||
import type { ImageCropperDialogProps } from './types';
|
||||
|
||||
const { children, class: className, ...rest }: ImageCropperDialogProps = $props();
|
||||
|
||||
const dialogState = useImageCropperDialog();
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open={dialogState.rootState.open}>
|
||||
<Dialog.Content
|
||||
{...rest}
|
||||
hideClose
|
||||
class={cn(
|
||||
'min-h-96 max-w-full rounded-none border-x-0 sm:max-w-lg sm:rounded-lg sm:border-x',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -1,25 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as Avatar from '$lib/components/ui/avatar';
|
||||
import type { ImageCropperPreviewProps } from './types';
|
||||
import { useImageCropperPreview } from './image-cropper.svelte.js';
|
||||
import UploadIcon from '@lucide/svelte/icons/upload';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
const { child, class: className }: ImageCropperPreviewProps = $props();
|
||||
|
||||
const previewState = useImageCropperPreview();
|
||||
</script>
|
||||
|
||||
{#if child}
|
||||
{@render child({ src: previewState.rootState.src })}
|
||||
{:else}
|
||||
<Avatar.Root
|
||||
class={cn('size-20 ring-2 ring-accent ring-offset-2 ring-offset-background', className)}
|
||||
>
|
||||
<Avatar.Image src={previewState.rootState.src} />
|
||||
<Avatar.Fallback>
|
||||
<UploadIcon class="size-4" />
|
||||
<span class="sr-only">Upload image</span>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
{/if}
|
||||
@@ -1,12 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { useImageCropperTrigger } from './image-cropper.svelte.js';
|
||||
import type { ImageCropperUploadTriggerProps } from './types';
|
||||
|
||||
let { ref = $bindable(null), children, ...rest }: ImageCropperUploadTriggerProps = $props();
|
||||
|
||||
const triggerState = useImageCropperTrigger();
|
||||
</script>
|
||||
|
||||
<label {...rest} bind:this={ref} for={triggerState.rootState.id} class="hover:cursor-pointer">
|
||||
{@render children?.()}
|
||||
</label>
|
||||
@@ -1,43 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { box } from 'svelte-toolbelt';
|
||||
import { useImageCropperRoot } from './image-cropper.svelte.js';
|
||||
import type { ImageCropperRootProps } from './types';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { useId } from 'bits-ui';
|
||||
|
||||
let {
|
||||
id = useId(),
|
||||
src = $bindable(''),
|
||||
onCropped = () => {},
|
||||
onUnsupportedFile = () => {},
|
||||
children,
|
||||
...rest
|
||||
}: ImageCropperRootProps = $props();
|
||||
|
||||
const rootState = useImageCropperRoot({
|
||||
id: box.with(() => id),
|
||||
src: box.with(
|
||||
() => src,
|
||||
(v) => (src = v)
|
||||
),
|
||||
onCropped: box.with(() => onCropped),
|
||||
onUnsupportedFile: box.with(() => onUnsupportedFile)
|
||||
});
|
||||
|
||||
onDestroy(() => rootState.dispose());
|
||||
</script>
|
||||
|
||||
{@render children?.()}
|
||||
<input
|
||||
{...rest}
|
||||
onchange={(e) => {
|
||||
const file = e.currentTarget.files?.[0];
|
||||
if (!file) return;
|
||||
rootState.onUpload(file);
|
||||
// reset so that we can reupload the same file
|
||||
(e.target! as HTMLInputElement).value = '';
|
||||
}}
|
||||
type="file"
|
||||
{id}
|
||||
style="display: none;"
|
||||
/>
|
||||
@@ -1,167 +0,0 @@
|
||||
import type { ReadableBoxedValues, WritableBoxedValues } from 'svelte-toolbelt';
|
||||
import { Context } from 'runed';
|
||||
import type { CropArea, DispatchEvents } from 'svelte-easy-crop';
|
||||
import { getCroppedImg } from './utils';
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#supported_image_formats
|
||||
export const VALID_IMAGE_TYPES = [
|
||||
'image/apng',
|
||||
'image/avif',
|
||||
'image/gif',
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/svg+xml',
|
||||
'image/webp'
|
||||
];
|
||||
|
||||
export type ImageCropperRootProps = WritableBoxedValues<{
|
||||
src: string;
|
||||
}> &
|
||||
ReadableBoxedValues<{
|
||||
id: string;
|
||||
onCropped: (url: string) => void;
|
||||
onUnsupportedFile: (file: File) => void;
|
||||
}>;
|
||||
|
||||
class ImageCropperRootState {
|
||||
#createdUrls = $state<string[]>([]);
|
||||
open = $state(false);
|
||||
tempUrl = $state<string>();
|
||||
pixelCrop = $state<CropArea>();
|
||||
|
||||
constructor(readonly opts: ImageCropperRootProps) {
|
||||
this.onUpload = this.onUpload.bind(this);
|
||||
this.onCancel = this.onCancel.bind(this);
|
||||
this.onCrop = this.onCrop.bind(this);
|
||||
this.dispose = this.dispose.bind(this);
|
||||
}
|
||||
|
||||
onUpload(file: File) {
|
||||
if (!VALID_IMAGE_TYPES.includes(file.type)) {
|
||||
this.opts.onUnsupportedFile.current(file);
|
||||
return;
|
||||
}
|
||||
|
||||
this.tempUrl = URL.createObjectURL(file);
|
||||
this.#createdUrls.push(this.tempUrl);
|
||||
this.open = true;
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.tempUrl = undefined;
|
||||
this.open = false;
|
||||
this.pixelCrop = undefined;
|
||||
}
|
||||
|
||||
async onCrop() {
|
||||
if (!this.pixelCrop || !this.tempUrl) return;
|
||||
|
||||
this.opts.src.current = await getCroppedImg(this.tempUrl, this.pixelCrop);
|
||||
|
||||
this.open = false;
|
||||
|
||||
this.opts.onCropped.current(this.opts.src.current);
|
||||
}
|
||||
|
||||
get src() {
|
||||
return this.opts.src.current;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.opts.id.current;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
for (const url of this.#createdUrls) {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ImageCropperTriggerProps = ReadableBoxedValues<{
|
||||
id?: string;
|
||||
}>;
|
||||
|
||||
class ImageCropperTriggerState {
|
||||
constructor(readonly rootState: ImageCropperRootState) {}
|
||||
}
|
||||
|
||||
class ImageCropperPreviewState {
|
||||
constructor(readonly rootState: ImageCropperRootState) {}
|
||||
}
|
||||
|
||||
class ImageCropperDialogState {
|
||||
constructor(readonly rootState: ImageCropperRootState) {}
|
||||
}
|
||||
|
||||
class ImageCropperCropperState {
|
||||
constructor(readonly rootState: ImageCropperRootState) {
|
||||
this.onCropComplete = this.onCropComplete.bind(this);
|
||||
}
|
||||
|
||||
onCropComplete(e: DispatchEvents['cropcomplete']) {
|
||||
this.rootState.pixelCrop = e.pixels;
|
||||
}
|
||||
}
|
||||
|
||||
class ImageCropperCropState {
|
||||
constructor(readonly rootState: ImageCropperRootState) {
|
||||
this.onclick = this.onclick.bind(this);
|
||||
}
|
||||
|
||||
onclick() {
|
||||
this.rootState.onCrop();
|
||||
}
|
||||
}
|
||||
|
||||
class ImageCropperCancelState {
|
||||
constructor(readonly rootState: ImageCropperRootState) {
|
||||
this.onclick = this.onclick.bind(this);
|
||||
}
|
||||
|
||||
onclick() {
|
||||
this.rootState.onCancel();
|
||||
}
|
||||
}
|
||||
|
||||
const ImageCropperRootContext = new Context<ImageCropperRootState>('ImageCropper.Root');
|
||||
|
||||
export const useImageCropperRoot = (props: ImageCropperRootProps) => {
|
||||
return ImageCropperRootContext.set(new ImageCropperRootState(props));
|
||||
};
|
||||
|
||||
export const useImageCropperTrigger = () => {
|
||||
const rootState = ImageCropperRootContext.get();
|
||||
|
||||
return new ImageCropperTriggerState(rootState);
|
||||
};
|
||||
|
||||
export const useImageCropperPreview = () => {
|
||||
const rootState = ImageCropperRootContext.get();
|
||||
|
||||
return new ImageCropperPreviewState(rootState);
|
||||
};
|
||||
|
||||
export const useImageCropperDialog = () => {
|
||||
const rootState = ImageCropperRootContext.get();
|
||||
|
||||
return new ImageCropperDialogState(rootState);
|
||||
};
|
||||
|
||||
export const useImageCropperCropper = () => {
|
||||
const rootState = ImageCropperRootContext.get();
|
||||
|
||||
return new ImageCropperCropperState(rootState);
|
||||
};
|
||||
|
||||
export const useImageCropperCrop = () => {
|
||||
const rootState = ImageCropperRootContext.get();
|
||||
|
||||
return new ImageCropperCropState(rootState);
|
||||
};
|
||||
|
||||
export const useImageCropperCancel = () => {
|
||||
const rootState = ImageCropperRootContext.get();
|
||||
|
||||
return new ImageCropperCancelState(rootState);
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
import Root from './image-cropper.svelte';
|
||||
import UploadTrigger from './image-cropper-upload-trigger.svelte';
|
||||
import Preview from './image-cropper-preview.svelte';
|
||||
import Dialog from './image-cropper-dialog.svelte';
|
||||
import Cropper from './image-cropper-cropper.svelte';
|
||||
import Controls from './image-cropper-controls.svelte';
|
||||
import Crop from './image-cropper-crop.svelte';
|
||||
import Cancel from './image-cropper-cancel.svelte';
|
||||
import { getFileFromUrl } from './utils';
|
||||
|
||||
export { Root, UploadTrigger, Preview, Dialog, Cropper, Controls, Crop, Cancel, getFileFromUrl };
|
||||
|
||||
export type * from './types';
|
||||
@@ -1,44 +0,0 @@
|
||||
import type {
|
||||
AvatarRootProps,
|
||||
DialogContentProps,
|
||||
WithChildren,
|
||||
WithoutChild,
|
||||
WithoutChildren
|
||||
} from 'bits-ui';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { CropperProps } from 'svelte-easy-crop';
|
||||
import type { HTMLAttributes, HTMLInputAttributes } from 'svelte/elements';
|
||||
|
||||
export type ImageCropperRootPropsWithoutHTML = WithChildren<{
|
||||
id?: string;
|
||||
src?: string;
|
||||
onCropped?: (url: string) => void;
|
||||
onUnsupportedFile?: (file: File) => void;
|
||||
}>;
|
||||
|
||||
export type ImageCropperRootProps = ImageCropperRootPropsWithoutHTML & HTMLInputAttributes;
|
||||
|
||||
export type ImageCropperDialogProps = DialogContentProps;
|
||||
|
||||
export type ImageCropperCropperProps = Omit<Partial<CropperProps>, 'oncropcomplete' | 'image'>;
|
||||
|
||||
export type ImageCropperControlsWithoutHTML = WithChildren<{
|
||||
ref?: HTMLDivElement | null;
|
||||
}>;
|
||||
|
||||
export type ImageCropperControlsProps = ImageCropperControlsWithoutHTML &
|
||||
WithoutChildren<HTMLAttributes<HTMLDivElement>>;
|
||||
|
||||
export type ImageCropperPreviewPropsWithoutHTML = {
|
||||
child?: Snippet<[{ src: string }]>;
|
||||
};
|
||||
|
||||
export type ImageCropperPreviewProps = ImageCropperPreviewPropsWithoutHTML &
|
||||
WithoutChild<AvatarRootProps>;
|
||||
|
||||
export type ImageCropperUploadTriggerPropsWithoutHTML = WithChildren<{
|
||||
ref?: HTMLLabelElement | null;
|
||||
}>;
|
||||
|
||||
export type ImageCropperUploadTriggerProps = ImageCropperUploadTriggerPropsWithoutHTML &
|
||||
WithoutChildren<HTMLAttributes<HTMLLabelElement>>;
|
||||
@@ -1,85 +0,0 @@
|
||||
import type { CropArea } from 'svelte-easy-crop';
|
||||
|
||||
export const getFileFromUrl = async (url: string, fileName = 'cropped.png'): Promise<File> => {
|
||||
// Fetch the file data from the URL
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch resource: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
// Convert the response into a Blob
|
||||
const blob = await response.blob();
|
||||
|
||||
// Create and return a File. You can set a custom type if needed.
|
||||
return new File([blob], fileName, { type: blob.type });
|
||||
};
|
||||
|
||||
const createImage = (url: string): Promise<HTMLImageElement> => {
|
||||
return new Promise<HTMLImageElement>((resolve, reject) => {
|
||||
const image = new Image();
|
||||
image.addEventListener('load', () => resolve(image));
|
||||
image.addEventListener('error', (error) => reject(error));
|
||||
image.setAttribute('crossOrigin', 'anonymous'); // needed to avoid cross-origin issues on CodeSandbox
|
||||
image.src = url;
|
||||
});
|
||||
};
|
||||
|
||||
const getRadianAngle = (degreeValue: number) => {
|
||||
return (degreeValue * Math.PI) / 180;
|
||||
};
|
||||
|
||||
/** Gets the cropped image from the src using the cropped area
|
||||
*
|
||||
* @param imageSrc
|
||||
* @param pixelCrop
|
||||
* @param rotation
|
||||
* @returns
|
||||
*/
|
||||
export const getCroppedImg = async (
|
||||
imageSrc: string,
|
||||
pixelCrop: CropArea,
|
||||
rotation = 0
|
||||
): Promise<string> => {
|
||||
const image = await createImage(imageSrc);
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
if (!ctx) {
|
||||
throw new Error('Error getting 2d rendering context');
|
||||
}
|
||||
|
||||
const maxSize = Math.max(image.width, image.height);
|
||||
const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2));
|
||||
|
||||
// set each dimensions to double largest dimension to allow for a safe area for the
|
||||
// image to rotate in without being clipped by canvas context
|
||||
canvas.width = safeArea;
|
||||
canvas.height = safeArea;
|
||||
|
||||
// translate canvas context to a central location on image to allow rotating around the center.
|
||||
ctx.translate(safeArea / 2, safeArea / 2);
|
||||
ctx.rotate(getRadianAngle(rotation));
|
||||
ctx.translate(-safeArea / 2, -safeArea / 2);
|
||||
|
||||
// draw rotated image and store data.
|
||||
ctx.drawImage(image, safeArea / 2 - image.width * 0.5, safeArea / 2 - image.height * 0.5);
|
||||
const data = ctx.getImageData(0, 0, safeArea, safeArea);
|
||||
|
||||
// set canvas width to final desired crop size - this will clear existing context
|
||||
canvas.width = pixelCrop.width;
|
||||
canvas.height = pixelCrop.height;
|
||||
|
||||
// paste generated rotate image with correct offsets for x,y crop values.
|
||||
ctx.putImageData(
|
||||
data,
|
||||
Math.round(0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x),
|
||||
Math.round(0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y)
|
||||
);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
canvas.toBlob((file) => {
|
||||
resolve(URL.createObjectURL(file!));
|
||||
}, 'image/png');
|
||||
});
|
||||
};
|
||||
@@ -3,11 +3,4 @@ export type { MapViewport } from './Map.svelte';
|
||||
export { default as MapMarker } from './MapMarker.svelte';
|
||||
export { default as MarkerContent } from './MarkerContent.svelte';
|
||||
export { default as MarkerPopup } from './MarkerPopup.svelte';
|
||||
export { default as MarkerTooltip } from './MarkerTooltip.svelte';
|
||||
export { default as MarkerLabel } from './MarkerLabel.svelte';
|
||||
export { default as MapControls } from './MapControls.svelte';
|
||||
export { default as MapPopup } from './MapPopup.svelte';
|
||||
export { default as MapRoute } from './MapRoute.svelte';
|
||||
export { default as MapClusterLayer } from './MapClusterLayer.svelte';
|
||||
export { default as MapArc } from './MapArc.svelte';
|
||||
export type { MapArcDatum, MapArcEvent, MapArcProps } from './MapArc.svelte';
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
|
||||
import Group from './select-group.svelte';
|
||||
import Label from './select-label.svelte';
|
||||
import Item from './select-item.svelte';
|
||||
import Content from './select-content.svelte';
|
||||
import Trigger from './select-trigger.svelte';
|
||||
import Separator from './select-separator.svelte';
|
||||
import ScrollDownButton from './select-scroll-down-button.svelte';
|
||||
import ScrollUpButton from './select-scroll-up-button.svelte';
|
||||
import GroupHeading from './select-group-heading.svelte';
|
||||
|
||||
const Root = SelectPrimitive.Root;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Group,
|
||||
Label,
|
||||
Item,
|
||||
Content,
|
||||
Trigger,
|
||||
Separator,
|
||||
ScrollDownButton,
|
||||
ScrollUpButton,
|
||||
GroupHeading,
|
||||
//
|
||||
Root as Select,
|
||||
Group as SelectGroup,
|
||||
Label as SelectLabel,
|
||||
Item as SelectItem,
|
||||
Content as SelectContent,
|
||||
Trigger as SelectTrigger,
|
||||
Separator as SelectSeparator,
|
||||
ScrollDownButton as SelectScrollDownButton,
|
||||
ScrollUpButton as SelectScrollUpButton,
|
||||
GroupHeading as SelectGroupHeading
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import SelectScrollUpButton from './select-scroll-up-button.svelte';
|
||||
import SelectScrollDownButton from './select-scroll-down-button.svelte';
|
||||
import { cn, type WithoutChild } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
sideOffset = 4,
|
||||
portalProps,
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChild<SelectPrimitive.ContentProps> & {
|
||||
portalProps?: SelectPrimitive.PortalProps;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Portal {...portalProps}>
|
||||
<SelectPrimitive.Content
|
||||
bind:ref
|
||||
{sideOffset}
|
||||
data-slot="select-content"
|
||||
class={cn(
|
||||
'relative z-50 max-h-(--bits-select-content-available-height) min-w-[8rem] origin-(--bits-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:translate-y-1 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:-translate-x-1 data-[side=left]:slide-in-from-right-2 data-[side=right]:translate-x-1 data-[side=right]:slide-in-from-left-2 data-[side=top]:-translate-y-1 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
class={cn(
|
||||
'h-(--bits-select-anchor-height) w-full min-w-(--bits-select-anchor-width) scroll-my-1 p-1'
|
||||
)}
|
||||
>
|
||||
{@render children?.()}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
@@ -1,21 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: ComponentProps<typeof SelectPrimitive.GroupHeading> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.GroupHeading
|
||||
bind:ref
|
||||
data-slot="select-group-heading"
|
||||
class={cn('px-2 py-1.5 text-xs text-muted-foreground', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</SelectPrimitive.GroupHeading>
|
||||
@@ -1,7 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: SelectPrimitive.GroupProps = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Group data-slot="select-group" {...restProps} />
|
||||
@@ -1,38 +0,0 @@
|
||||
<script lang="ts">
|
||||
import CheckIcon from '@lucide/svelte/icons/check';
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { cn, type WithoutChild } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
value,
|
||||
label,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChild<SelectPrimitive.ItemProps> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Item
|
||||
bind:ref
|
||||
{value}
|
||||
data-slot="select-item"
|
||||
class={cn(
|
||||
"relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ selected, highlighted })}
|
||||
<span class="absolute right-2 flex size-3.5 items-center justify-center">
|
||||
{#if selected}
|
||||
<CheckIcon class="size-4" />
|
||||
{/if}
|
||||
</span>
|
||||
{#if childrenProp}
|
||||
{@render childrenProp({ selected, highlighted })}
|
||||
{:else}
|
||||
{label || value}
|
||||
{/if}
|
||||
{/snippet}
|
||||
</SelectPrimitive.Item>
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {} = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="select-label"
|
||||
class={cn('px-2 py-1.5 text-xs text-muted-foreground', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<SelectPrimitive.ScrollDownButtonProps> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
bind:ref
|
||||
data-slot="select-scroll-down-button"
|
||||
class={cn('flex cursor-default items-center justify-center py-1', className)}
|
||||
{...restProps}
|
||||
>
|
||||
<ChevronDownIcon class="size-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<SelectPrimitive.ScrollUpButtonProps> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
bind:ref
|
||||
data-slot="select-scroll-up-button"
|
||||
class={cn('flex cursor-default items-center justify-center py-1', className)}
|
||||
{...restProps}
|
||||
>
|
||||
<ChevronUpIcon class="size-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
@@ -1,18 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Separator as SeparatorPrimitive } from 'bits-ui';
|
||||
import { Separator } from '$lib/components/ui/separator/index.js';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: SeparatorPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<Separator
|
||||
bind:ref
|
||||
data-slot="select-separator"
|
||||
class={cn('pointer-events-none -mx-1 my-1 h-px bg-border', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,29 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||
import { cn, type WithoutChild } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
size = 'default',
|
||||
...restProps
|
||||
}: WithoutChild<SelectPrimitive.TriggerProps> & {
|
||||
size?: 'sm' | 'default';
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Trigger
|
||||
bind:ref
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
class={cn(
|
||||
"flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronDownIcon class="size-4 opacity-50" />
|
||||
</SelectPrimitive.Trigger>
|
||||
@@ -1,28 +0,0 @@
|
||||
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
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
|
||||
</script>
|
||||
|
||||
<tbody
|
||||
bind:this={ref}
|
||||
data-slot="table-body"
|
||||
class={cn('[&_tr:last-child]:border-0', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</tbody>
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
||||
</script>
|
||||
|
||||
<caption
|
||||
bind:this={ref}
|
||||
data-slot="table-caption"
|
||||
class={cn('mt-4 text-sm text-muted-foreground', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</caption>
|
||||
@@ -1,23 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
import type { HTMLTdAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLTdAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<td
|
||||
bind:this={ref}
|
||||
data-slot="table-cell"
|
||||
class={cn(
|
||||
'bg-clip-padding p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</td>
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
|
||||
</script>
|
||||
|
||||
<tfoot
|
||||
bind:this={ref}
|
||||
data-slot="table-footer"
|
||||
class={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</tfoot>
|
||||
@@ -1,23 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
import type { HTMLThAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLThAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<th
|
||||
bind:this={ref}
|
||||
data-slot="table-head"
|
||||
class={cn(
|
||||
'h-10 bg-clip-padding px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</th>
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
|
||||
</script>
|
||||
|
||||
<thead
|
||||
bind:this={ref}
|
||||
data-slot="table-header"
|
||||
class={cn('[&_tr]:border-b', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</thead>
|
||||
@@ -1,23 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLTableRowElement>> = $props();
|
||||
</script>
|
||||
|
||||
<tr
|
||||
bind:this={ref}
|
||||
data-slot="table-row"
|
||||
class={cn(
|
||||
'border-b transition-colors data-[state=selected]:bg-muted hover:[&,&>svelte-css-wrapper]:[&>th,td]:bg-muted/50',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</tr>
|
||||
@@ -1,22 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLTableAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLTableAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<div data-slot="table-container" class="relative w-full overflow-x-auto">
|
||||
<table
|
||||
bind:this={ref}
|
||||
data-slot="table"
|
||||
class={cn('w-full caption-bottom text-sm', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</table>
|
||||
</div>
|
||||
Reference in New Issue
Block a user