style: apply prettier formatting across frontend components and pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -320,6 +320,5 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,50 +1,49 @@
|
|||||||
<script lang="ts" module>
|
<script lang="ts" module>
|
||||||
import { type VariantProps, tv } from "tailwind-variants";
|
import { type VariantProps, tv } from "tailwind-variants";
|
||||||
|
|
||||||
export const badgeVariants = tv({
|
export const badgeVariants = tv({
|
||||||
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] [&>svg]:pointer-events-none [&>svg]:size-3",
|
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] [&>svg]:pointer-events-none [&>svg]:size-3",
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent",
|
||||||
"bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent",
|
secondary:
|
||||||
secondary:
|
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent",
|
||||||
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent",
|
destructive:
|
||||||
destructive:
|
"bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-white",
|
||||||
"bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-white",
|
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||||
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
},
|
||||||
},
|
},
|
||||||
},
|
defaultVariants: {
|
||||||
defaultVariants: {
|
variant: "default",
|
||||||
variant: "default",
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
export type BadgeVariant = VariantProps<typeof badgeVariants>["variant"];
|
export type BadgeVariant = VariantProps<typeof badgeVariants>["variant"];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { HTMLAnchorAttributes } from "svelte/elements";
|
import type { HTMLAnchorAttributes } from "svelte/elements";
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
href,
|
href,
|
||||||
class: className,
|
class: className,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithElementRef<HTMLAnchorAttributes> & {
|
}: WithElementRef<HTMLAnchorAttributes> & {
|
||||||
variant?: BadgeVariant;
|
variant?: BadgeVariant;
|
||||||
} = $props();
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:element
|
<svelte:element
|
||||||
this={href ? "a" : "span"}
|
this={href ? "a" : "span"}
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="badge"
|
data-slot="badge"
|
||||||
{href}
|
{href}
|
||||||
class={cn(badgeVariants({ variant }), className)}
|
class={cn(badgeVariants({ variant }), className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</svelte:element>
|
</svelte:element>
|
||||||
|
|||||||
@@ -1,76 +1,76 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ComponentProps } from "svelte";
|
import type { ComponentProps } from "svelte";
|
||||||
import type Calendar from "./calendar.svelte";
|
import type Calendar from "./calendar.svelte";
|
||||||
import CalendarMonthSelect from "./calendar-month-select.svelte";
|
import CalendarMonthSelect from "./calendar-month-select.svelte";
|
||||||
import CalendarYearSelect from "./calendar-year-select.svelte";
|
import CalendarYearSelect from "./calendar-year-select.svelte";
|
||||||
import { DateFormatter, getLocalTimeZone, type DateValue } from "@internationalized/date";
|
import { DateFormatter, getLocalTimeZone, type DateValue } from "@internationalized/date";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
captionLayout,
|
captionLayout,
|
||||||
months,
|
months,
|
||||||
monthFormat,
|
monthFormat,
|
||||||
years,
|
years,
|
||||||
yearFormat,
|
yearFormat,
|
||||||
month,
|
month,
|
||||||
locale,
|
locale,
|
||||||
placeholder = $bindable(),
|
placeholder = $bindable(),
|
||||||
monthIndex = 0,
|
monthIndex = 0,
|
||||||
}: {
|
}: {
|
||||||
captionLayout: ComponentProps<typeof Calendar>["captionLayout"];
|
captionLayout: ComponentProps<typeof Calendar>["captionLayout"];
|
||||||
months: ComponentProps<typeof CalendarMonthSelect>["months"];
|
months: ComponentProps<typeof CalendarMonthSelect>["months"];
|
||||||
monthFormat: ComponentProps<typeof CalendarMonthSelect>["monthFormat"];
|
monthFormat: ComponentProps<typeof CalendarMonthSelect>["monthFormat"];
|
||||||
years: ComponentProps<typeof CalendarYearSelect>["years"];
|
years: ComponentProps<typeof CalendarYearSelect>["years"];
|
||||||
yearFormat: ComponentProps<typeof CalendarYearSelect>["yearFormat"];
|
yearFormat: ComponentProps<typeof CalendarYearSelect>["yearFormat"];
|
||||||
month: DateValue;
|
month: DateValue;
|
||||||
placeholder: DateValue | undefined;
|
placeholder: DateValue | undefined;
|
||||||
locale: string;
|
locale: string;
|
||||||
monthIndex: number;
|
monthIndex: number;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
function formatYear(date: DateValue) {
|
function formatYear(date: DateValue) {
|
||||||
const dateObj = date.toDate(getLocalTimeZone());
|
const dateObj = date.toDate(getLocalTimeZone());
|
||||||
if (typeof yearFormat === "function") return yearFormat(dateObj.getFullYear());
|
if (typeof yearFormat === "function") return yearFormat(dateObj.getFullYear());
|
||||||
return new DateFormatter(locale, { year: yearFormat }).format(dateObj);
|
return new DateFormatter(locale, { year: yearFormat }).format(dateObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatMonth(date: DateValue) {
|
function formatMonth(date: DateValue) {
|
||||||
const dateObj = date.toDate(getLocalTimeZone());
|
const dateObj = date.toDate(getLocalTimeZone());
|
||||||
if (typeof monthFormat === "function") return monthFormat(dateObj.getMonth() + 1);
|
if (typeof monthFormat === "function") return monthFormat(dateObj.getMonth() + 1);
|
||||||
return new DateFormatter(locale, { month: monthFormat }).format(dateObj);
|
return new DateFormatter(locale, { month: monthFormat }).format(dateObj);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet MonthSelect()}
|
{#snippet MonthSelect()}
|
||||||
<CalendarMonthSelect
|
<CalendarMonthSelect
|
||||||
{months}
|
{months}
|
||||||
{monthFormat}
|
{monthFormat}
|
||||||
value={month.month}
|
value={month.month}
|
||||||
onchange={(e) => {
|
onchange={(e) => {
|
||||||
if (!placeholder) return;
|
if (!placeholder) return;
|
||||||
const v = Number.parseInt(e.currentTarget.value);
|
const v = Number.parseInt(e.currentTarget.value);
|
||||||
const newPlaceholder = placeholder.set({ month: v });
|
const newPlaceholder = placeholder.set({ month: v });
|
||||||
placeholder = newPlaceholder.subtract({ months: monthIndex });
|
placeholder = newPlaceholder.subtract({ months: monthIndex });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#snippet YearSelect()}
|
{#snippet YearSelect()}
|
||||||
<CalendarYearSelect {years} {yearFormat} value={month.year} />
|
<CalendarYearSelect {years} {yearFormat} value={month.year} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#if captionLayout === "dropdown"}
|
{#if captionLayout === "dropdown"}
|
||||||
{@render MonthSelect()}
|
{@render MonthSelect()}
|
||||||
{@render YearSelect()}
|
{@render YearSelect()}
|
||||||
{:else if captionLayout === "dropdown-months"}
|
{:else if captionLayout === "dropdown-months"}
|
||||||
{@render MonthSelect()}
|
{@render MonthSelect()}
|
||||||
{#if placeholder}
|
{#if placeholder}
|
||||||
{formatYear(placeholder)}
|
{formatYear(placeholder)}
|
||||||
{/if}
|
{/if}
|
||||||
{:else if captionLayout === "dropdown-years"}
|
{:else if captionLayout === "dropdown-years"}
|
||||||
{#if placeholder}
|
{#if placeholder}
|
||||||
{formatMonth(placeholder)}
|
{formatMonth(placeholder)}
|
||||||
{/if}
|
{/if}
|
||||||
{@render YearSelect()}
|
{@render YearSelect()}
|
||||||
{:else}
|
{:else}
|
||||||
{formatMonth(month)} {formatYear(month)}
|
{formatMonth(month)} {formatYear(month)}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
}: CalendarPrimitive.CellProps = $props();
|
}: CalendarPrimitive.CellProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CalendarPrimitive.Cell
|
<CalendarPrimitive.Cell
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn(
|
class={cn(
|
||||||
"relative size-(--cell-size) p-0 text-center text-sm focus-within:z-20 [&:first-child[data-selected]_[data-bits-day]]:rounded-s-md [&:last-child[data-selected]_[data-bits-day]]:rounded-e-md",
|
"relative size-(--cell-size) p-0 text-center text-sm focus-within:z-20 [&:first-child[data-selected]_[data-bits-day]]:rounded-s-md [&:last-child[data-selected]_[data-bits-day]]:rounded-e-md",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
}: CalendarPrimitive.DayProps = $props();
|
}: CalendarPrimitive.DayProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CalendarPrimitive.Day
|
<CalendarPrimitive.Day
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn(
|
class={cn(
|
||||||
buttonVariants({ variant: "ghost" }),
|
buttonVariants({ variant: "ghost" }),
|
||||||
"flex size-(--cell-size) flex-col items-center justify-center gap-1 p-0 leading-none font-normal whitespace-nowrap select-none",
|
"flex size-(--cell-size) flex-col items-center justify-center gap-1 p-0 leading-none font-normal whitespace-nowrap select-none",
|
||||||
"[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground [&[data-today][data-disabled]]:text-muted-foreground",
|
"[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground [&[data-today][data-disabled]]:text-muted-foreground",
|
||||||
"data-[selected]:bg-primary dark:data-[selected]:hover:bg-accent/50 data-[selected]:text-primary-foreground",
|
"data-[selected]:bg-primary dark:data-[selected]:hover:bg-accent/50 data-[selected]:text-primary-foreground",
|
||||||
// Outside months
|
// Outside months
|
||||||
"[&[data-outside-month]:not([data-selected])]:text-muted-foreground [&[data-outside-month]:not([data-selected])]:hover:text-accent-foreground",
|
"[&[data-outside-month]:not([data-selected])]:text-muted-foreground [&[data-outside-month]:not([data-selected])]:hover:text-accent-foreground",
|
||||||
// Disabled
|
// Disabled
|
||||||
"data-[disabled]:text-muted-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"data-[disabled]:text-muted-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
// Unavailable
|
// Unavailable
|
||||||
"data-[unavailable]:text-muted-foreground data-[unavailable]:line-through",
|
"data-[unavailable]:text-muted-foreground data-[unavailable]:line-through",
|
||||||
// hover
|
// hover
|
||||||
"dark:hover:text-accent-foreground",
|
"dark:hover:text-accent-foreground",
|
||||||
// focus
|
// focus
|
||||||
"focus:border-ring focus:ring-ring/50 focus:relative",
|
"focus:border-ring focus:ring-ring/50 focus:relative",
|
||||||
// inner spans
|
// inner spans
|
||||||
"[&>span]:text-xs [&>span]:opacity-70",
|
"[&>span]:text-xs [&>span]:opacity-70",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
}: CalendarPrimitive.GridBodyProps = $props();
|
}: CalendarPrimitive.GridBodyProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CalendarPrimitive.GridBody bind:ref class={cn(className)} {...restProps} />
|
<CalendarPrimitive.GridBody bind:ref class={cn(className)} {...restProps} />
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
}: CalendarPrimitive.GridHeadProps = $props();
|
}: CalendarPrimitive.GridHeadProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CalendarPrimitive.GridHead bind:ref class={cn(className)} {...restProps} />
|
<CalendarPrimitive.GridHead bind:ref class={cn(className)} {...restProps} />
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
}: CalendarPrimitive.GridRowProps = $props();
|
}: CalendarPrimitive.GridRowProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CalendarPrimitive.GridRow bind:ref class={cn("flex", className)} {...restProps} />
|
<CalendarPrimitive.GridRow bind:ref class={cn("flex", className)} {...restProps} />
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
}: CalendarPrimitive.GridProps = $props();
|
}: CalendarPrimitive.GridProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CalendarPrimitive.Grid
|
<CalendarPrimitive.Grid
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn("mt-4 flex w-full border-collapse flex-col gap-1", className)}
|
class={cn("mt-4 flex w-full border-collapse flex-col gap-1", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
}: CalendarPrimitive.HeadCellProps = $props();
|
}: CalendarPrimitive.HeadCellProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CalendarPrimitive.HeadCell
|
<CalendarPrimitive.HeadCell
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn(
|
class={cn(
|
||||||
"text-muted-foreground w-(--cell-size) rounded-md text-[0.8rem] font-normal",
|
"text-muted-foreground w-(--cell-size) rounded-md text-[0.8rem] font-normal",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
}: CalendarPrimitive.HeaderProps = $props();
|
}: CalendarPrimitive.HeaderProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CalendarPrimitive.Header
|
<CalendarPrimitive.Header
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn(
|
class={cn(
|
||||||
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
|
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
}: CalendarPrimitive.HeadingProps = $props();
|
}: CalendarPrimitive.HeadingProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CalendarPrimitive.Heading
|
<CalendarPrimitive.Heading
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn("px-(--cell-size) text-sm font-medium", className)}
|
class={cn("px-(--cell-size) text-sm font-medium", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,48 +1,48 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||||
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
|
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
value,
|
value,
|
||||||
onchange,
|
onchange,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithoutChildrenOrChild<CalendarPrimitive.MonthSelectProps> = $props();
|
}: WithoutChildrenOrChild<CalendarPrimitive.MonthSelectProps> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class={cn(
|
class={cn(
|
||||||
"has-focus:border-ring border-input has-focus:ring-ring/50 relative flex rounded-md border shadow-xs has-focus:ring-[3px]",
|
"has-focus:border-ring border-input has-focus:ring-ring/50 relative flex rounded-md border shadow-xs has-focus:ring-[3px]",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CalendarPrimitive.MonthSelect
|
<CalendarPrimitive.MonthSelect
|
||||||
bind:ref
|
bind:ref
|
||||||
class="dark:bg-popover dark:text-popover-foreground absolute inset-0 opacity-0"
|
class="dark:bg-popover dark:text-popover-foreground absolute inset-0 opacity-0"
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{#snippet child({ props, monthItems, selectedMonthItem })}
|
{#snippet child({ props, monthItems, selectedMonthItem })}
|
||||||
<select {...props} {value} {onchange}>
|
<select {...props} {value} {onchange}>
|
||||||
{#each monthItems as monthItem (monthItem.value)}
|
{#each monthItems as monthItem (monthItem.value)}
|
||||||
<option
|
<option
|
||||||
value={monthItem.value}
|
value={monthItem.value}
|
||||||
selected={value !== undefined
|
selected={value !== undefined
|
||||||
? monthItem.value === value
|
? monthItem.value === value
|
||||||
: monthItem.value === selectedMonthItem.value}
|
: monthItem.value === selectedMonthItem.value}
|
||||||
>
|
>
|
||||||
{monthItem.label}
|
{monthItem.label}
|
||||||
</option>
|
</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<span
|
<span
|
||||||
class="[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md ps-2 pe-1 text-sm font-medium select-none [&>svg]:size-3.5"
|
class="[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md ps-2 pe-1 text-sm font-medium select-none [&>svg]:size-3.5"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
{monthItems.find((item) => item.value === value)?.label || selectedMonthItem.label}
|
{monthItems.find((item) => item.value === value)?.label || selectedMonthItem.label}
|
||||||
<ChevronDownIcon class="size-4" />
|
<ChevronDownIcon class="size-4" />
|
||||||
</span>
|
</span>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</CalendarPrimitive.MonthSelect>
|
</CalendarPrimitive.MonthSelect>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type WithElementRef, cn } from "$lib/utils.js";
|
import { type WithElementRef, cn } from "$lib/utils.js";
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div {...restProps} bind:this={ref} class={cn("flex flex-col", className)}>
|
<div {...restProps} bind:this={ref} class={cn("flex flex-col", className)}>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
class={cn("relative flex flex-col gap-4 md:flex-row", className)}
|
class={cn("relative flex flex-col gap-4 md:flex-row", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav
|
<nav
|
||||||
{...restProps}
|
{...restProps}
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
class={cn("absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1", className)}
|
class={cn("absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1", className)}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
||||||
import { buttonVariants, type ButtonVariant } from "$lib/components/ui/button/index.js";
|
import { buttonVariants, type ButtonVariant } from "$lib/components/ui/button/index.js";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
variant = "ghost",
|
variant = "ghost",
|
||||||
...restProps
|
...restProps
|
||||||
}: CalendarPrimitive.NextButtonProps & {
|
}: CalendarPrimitive.NextButtonProps & {
|
||||||
variant?: ButtonVariant;
|
variant?: ButtonVariant;
|
||||||
} = $props();
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet Fallback()}
|
{#snippet Fallback()}
|
||||||
<ChevronRightIcon class="size-4" />
|
<ChevronRightIcon class="size-4" />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
<CalendarPrimitive.NextButton
|
<CalendarPrimitive.NextButton
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn(
|
class={cn(
|
||||||
buttonVariants({ variant }),
|
buttonVariants({ variant }),
|
||||||
"size-(--cell-size) bg-transparent p-0 select-none disabled:opacity-50 rtl:rotate-180",
|
"size-(--cell-size) bg-transparent p-0 select-none disabled:opacity-50 rtl:rotate-180",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
children={children || Fallback}
|
children={children || Fallback}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
import ChevronLeftIcon from "@lucide/svelte/icons/chevron-left";
|
import ChevronLeftIcon from "@lucide/svelte/icons/chevron-left";
|
||||||
import { buttonVariants, type ButtonVariant } from "$lib/components/ui/button/index.js";
|
import { buttonVariants, type ButtonVariant } from "$lib/components/ui/button/index.js";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
variant = "ghost",
|
variant = "ghost",
|
||||||
...restProps
|
...restProps
|
||||||
}: CalendarPrimitive.PrevButtonProps & {
|
}: CalendarPrimitive.PrevButtonProps & {
|
||||||
variant?: ButtonVariant;
|
variant?: ButtonVariant;
|
||||||
} = $props();
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet Fallback()}
|
{#snippet Fallback()}
|
||||||
<ChevronLeftIcon class="size-4" />
|
<ChevronLeftIcon class="size-4" />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
<CalendarPrimitive.PrevButton
|
<CalendarPrimitive.PrevButton
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn(
|
class={cn(
|
||||||
buttonVariants({ variant }),
|
buttonVariants({ variant }),
|
||||||
"size-(--cell-size) bg-transparent p-0 select-none disabled:opacity-50 rtl:rotate-180",
|
"size-(--cell-size) bg-transparent p-0 select-none disabled:opacity-50 rtl:rotate-180",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
children={children || Fallback}
|
children={children || Fallback}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,47 +1,47 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||||
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
|
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
value,
|
value,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithoutChildrenOrChild<CalendarPrimitive.YearSelectProps> = $props();
|
}: WithoutChildrenOrChild<CalendarPrimitive.YearSelectProps> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class={cn(
|
class={cn(
|
||||||
"has-focus:border-ring border-input has-focus:ring-ring/50 relative flex rounded-md border shadow-xs has-focus:ring-[3px]",
|
"has-focus:border-ring border-input has-focus:ring-ring/50 relative flex rounded-md border shadow-xs has-focus:ring-[3px]",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CalendarPrimitive.YearSelect
|
<CalendarPrimitive.YearSelect
|
||||||
bind:ref
|
bind:ref
|
||||||
class="dark:bg-popover dark:text-popover-foreground absolute inset-0 opacity-0"
|
class="dark:bg-popover dark:text-popover-foreground absolute inset-0 opacity-0"
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{#snippet child({ props, yearItems, selectedYearItem })}
|
{#snippet child({ props, yearItems, selectedYearItem })}
|
||||||
<select {...props} {value}>
|
<select {...props} {value}>
|
||||||
{#each yearItems as yearItem (yearItem.value)}
|
{#each yearItems as yearItem (yearItem.value)}
|
||||||
<option
|
<option
|
||||||
value={yearItem.value}
|
value={yearItem.value}
|
||||||
selected={value !== undefined
|
selected={value !== undefined
|
||||||
? yearItem.value === value
|
? yearItem.value === value
|
||||||
: yearItem.value === selectedYearItem.value}
|
: yearItem.value === selectedYearItem.value}
|
||||||
>
|
>
|
||||||
{yearItem.label}
|
{yearItem.label}
|
||||||
</option>
|
</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<span
|
<span
|
||||||
class="[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md ps-2 pe-1 text-sm font-medium select-none [&>svg]:size-3.5"
|
class="[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md ps-2 pe-1 text-sm font-medium select-none [&>svg]:size-3.5"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
{yearItems.find((item) => item.value === value)?.label || selectedYearItem.label}
|
{yearItems.find((item) => item.value === value)?.label || selectedYearItem.label}
|
||||||
<ChevronDownIcon class="size-4" />
|
<ChevronDownIcon class="size-4" />
|
||||||
</span>
|
</span>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</CalendarPrimitive.YearSelect>
|
</CalendarPrimitive.YearSelect>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,42 +1,42 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
import * as Calendar from "./index.js";
|
import * as Calendar from "./index.js";
|
||||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||||
import type { ButtonVariant } from "../button/button.svelte";
|
import type { ButtonVariant } from "../button/button.svelte";
|
||||||
import { isEqualMonth, type DateValue } from "@internationalized/date";
|
import { isEqualMonth, type DateValue } from "@internationalized/date";
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
value = $bindable(),
|
value = $bindable(),
|
||||||
placeholder = $bindable(),
|
placeholder = $bindable(),
|
||||||
class: className,
|
class: className,
|
||||||
weekdayFormat = "short",
|
weekdayFormat = "short",
|
||||||
buttonVariant = "ghost",
|
buttonVariant = "ghost",
|
||||||
captionLayout = "label",
|
captionLayout = "label",
|
||||||
locale = "en-US",
|
locale = "en-US",
|
||||||
months: monthsProp,
|
months: monthsProp,
|
||||||
years,
|
years,
|
||||||
monthFormat: monthFormatProp,
|
monthFormat: monthFormatProp,
|
||||||
yearFormat = "numeric",
|
yearFormat = "numeric",
|
||||||
day,
|
day,
|
||||||
disableDaysOutsideMonth = false,
|
disableDaysOutsideMonth = false,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithoutChildrenOrChild<CalendarPrimitive.RootProps> & {
|
}: WithoutChildrenOrChild<CalendarPrimitive.RootProps> & {
|
||||||
buttonVariant?: ButtonVariant;
|
buttonVariant?: ButtonVariant;
|
||||||
captionLayout?: "dropdown" | "dropdown-months" | "dropdown-years" | "label";
|
captionLayout?: "dropdown" | "dropdown-months" | "dropdown-years" | "label";
|
||||||
months?: CalendarPrimitive.MonthSelectProps["months"];
|
months?: CalendarPrimitive.MonthSelectProps["months"];
|
||||||
years?: CalendarPrimitive.YearSelectProps["years"];
|
years?: CalendarPrimitive.YearSelectProps["years"];
|
||||||
monthFormat?: CalendarPrimitive.MonthSelectProps["monthFormat"];
|
monthFormat?: CalendarPrimitive.MonthSelectProps["monthFormat"];
|
||||||
yearFormat?: CalendarPrimitive.YearSelectProps["yearFormat"];
|
yearFormat?: CalendarPrimitive.YearSelectProps["yearFormat"];
|
||||||
day?: Snippet<[{ day: DateValue; outsideMonth: boolean }]>;
|
day?: Snippet<[{ day: DateValue; outsideMonth: boolean }]>;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const monthFormat = $derived.by(() => {
|
const monthFormat = $derived.by(() => {
|
||||||
if (monthFormatProp) return monthFormatProp;
|
if (monthFormatProp) return monthFormatProp;
|
||||||
if (captionLayout.startsWith("dropdown")) return "short";
|
if (captionLayout.startsWith("dropdown")) return "short";
|
||||||
return "long";
|
return "long";
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@@ -44,72 +44,72 @@ Discriminated Unions + Destructing (required for bindable) do not
|
|||||||
get along, so we shut typescript up by casting `value` to `never`.
|
get along, so we shut typescript up by casting `value` to `never`.
|
||||||
-->
|
-->
|
||||||
<CalendarPrimitive.Root
|
<CalendarPrimitive.Root
|
||||||
bind:value={value as never}
|
bind:value={value as never}
|
||||||
bind:ref
|
bind:ref
|
||||||
bind:placeholder
|
bind:placeholder
|
||||||
{weekdayFormat}
|
{weekdayFormat}
|
||||||
{disableDaysOutsideMonth}
|
{disableDaysOutsideMonth}
|
||||||
class={cn(
|
class={cn(
|
||||||
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{locale}
|
{locale}
|
||||||
{monthFormat}
|
{monthFormat}
|
||||||
{yearFormat}
|
{yearFormat}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{#snippet children({ months, weekdays })}
|
{#snippet children({ months, weekdays })}
|
||||||
<Calendar.Months>
|
<Calendar.Months>
|
||||||
<Calendar.Nav>
|
<Calendar.Nav>
|
||||||
<Calendar.PrevButton variant={buttonVariant} />
|
<Calendar.PrevButton variant={buttonVariant} />
|
||||||
<Calendar.NextButton variant={buttonVariant} />
|
<Calendar.NextButton variant={buttonVariant} />
|
||||||
</Calendar.Nav>
|
</Calendar.Nav>
|
||||||
{#each months as month, monthIndex (month)}
|
{#each months as month, monthIndex (month)}
|
||||||
<Calendar.Month>
|
<Calendar.Month>
|
||||||
<Calendar.Header>
|
<Calendar.Header>
|
||||||
<Calendar.Caption
|
<Calendar.Caption
|
||||||
{captionLayout}
|
{captionLayout}
|
||||||
months={monthsProp}
|
months={monthsProp}
|
||||||
{monthFormat}
|
{monthFormat}
|
||||||
{years}
|
{years}
|
||||||
{yearFormat}
|
{yearFormat}
|
||||||
month={month.value}
|
month={month.value}
|
||||||
bind:placeholder
|
bind:placeholder
|
||||||
{locale}
|
{locale}
|
||||||
{monthIndex}
|
{monthIndex}
|
||||||
/>
|
/>
|
||||||
</Calendar.Header>
|
</Calendar.Header>
|
||||||
<Calendar.Grid>
|
<Calendar.Grid>
|
||||||
<Calendar.GridHead>
|
<Calendar.GridHead>
|
||||||
<Calendar.GridRow class="select-none">
|
<Calendar.GridRow class="select-none">
|
||||||
{#each weekdays as weekday (weekday)}
|
{#each weekdays as weekday (weekday)}
|
||||||
<Calendar.HeadCell>
|
<Calendar.HeadCell>
|
||||||
{weekday.slice(0, 2)}
|
{weekday.slice(0, 2)}
|
||||||
</Calendar.HeadCell>
|
</Calendar.HeadCell>
|
||||||
{/each}
|
{/each}
|
||||||
</Calendar.GridRow>
|
</Calendar.GridRow>
|
||||||
</Calendar.GridHead>
|
</Calendar.GridHead>
|
||||||
<Calendar.GridBody>
|
<Calendar.GridBody>
|
||||||
{#each month.weeks as weekDates (weekDates)}
|
{#each month.weeks as weekDates (weekDates)}
|
||||||
<Calendar.GridRow class="mt-2 w-full">
|
<Calendar.GridRow class="mt-2 w-full">
|
||||||
{#each weekDates as date (date)}
|
{#each weekDates as date (date)}
|
||||||
<Calendar.Cell {date} month={month.value}>
|
<Calendar.Cell {date} month={month.value}>
|
||||||
{#if day}
|
{#if day}
|
||||||
{@render day({
|
{@render day({
|
||||||
day: date,
|
day: date,
|
||||||
outsideMonth: !isEqualMonth(date, month.value),
|
outsideMonth: !isEqualMonth(date, month.value),
|
||||||
})}
|
})}
|
||||||
{:else}
|
{:else}
|
||||||
<Calendar.Day />
|
<Calendar.Day />
|
||||||
{/if}
|
{/if}
|
||||||
</Calendar.Cell>
|
</Calendar.Cell>
|
||||||
{/each}
|
{/each}
|
||||||
</Calendar.GridRow>
|
</Calendar.GridRow>
|
||||||
{/each}
|
{/each}
|
||||||
</Calendar.GridBody>
|
</Calendar.GridBody>
|
||||||
</Calendar.Grid>
|
</Calendar.Grid>
|
||||||
</Calendar.Month>
|
</Calendar.Month>
|
||||||
{/each}
|
{/each}
|
||||||
</Calendar.Months>
|
</Calendar.Months>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</CalendarPrimitive.Root>
|
</CalendarPrimitive.Root>
|
||||||
|
|||||||
@@ -18,23 +18,23 @@ import Nav from "./calendar-nav.svelte";
|
|||||||
import Caption from "./calendar-caption.svelte";
|
import Caption from "./calendar-caption.svelte";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Day,
|
Day,
|
||||||
Cell,
|
Cell,
|
||||||
Grid,
|
Grid,
|
||||||
Header,
|
Header,
|
||||||
Months,
|
Months,
|
||||||
GridRow,
|
GridRow,
|
||||||
Heading,
|
Heading,
|
||||||
GridBody,
|
GridBody,
|
||||||
GridHead,
|
GridHead,
|
||||||
HeadCell,
|
HeadCell,
|
||||||
NextButton,
|
NextButton,
|
||||||
PrevButton,
|
PrevButton,
|
||||||
Nav,
|
Nav,
|
||||||
Month,
|
Month,
|
||||||
YearSelect,
|
YearSelect,
|
||||||
MonthSelect,
|
MonthSelect,
|
||||||
Caption,
|
Caption,
|
||||||
//
|
//
|
||||||
Root as Calendar,
|
Root as Calendar,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ import Trigger from "./popover-trigger.svelte";
|
|||||||
import Portal from "./popover-portal.svelte";
|
import Portal from "./popover-portal.svelte";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Root,
|
Root,
|
||||||
Content,
|
Content,
|
||||||
Trigger,
|
Trigger,
|
||||||
Close,
|
Close,
|
||||||
Portal,
|
Portal,
|
||||||
//
|
//
|
||||||
Root as Popover,
|
Root as Popover,
|
||||||
Content as PopoverContent,
|
Content as PopoverContent,
|
||||||
Trigger as PopoverTrigger,
|
Trigger as PopoverTrigger,
|
||||||
Close as PopoverClose,
|
Close as PopoverClose,
|
||||||
Portal as PopoverPortal,
|
Portal as PopoverPortal,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Popover as PopoverPrimitive } from "bits-ui";
|
import { Popover as PopoverPrimitive } from "bits-ui";
|
||||||
|
|
||||||
let { ref = $bindable(null), ...restProps }: PopoverPrimitive.CloseProps = $props();
|
let { ref = $bindable(null), ...restProps }: PopoverPrimitive.CloseProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PopoverPrimitive.Close bind:ref data-slot="popover-close" {...restProps} />
|
<PopoverPrimitive.Close bind:ref data-slot="popover-close" {...restProps} />
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Popover as PopoverPrimitive } from "bits-ui";
|
import { Popover as PopoverPrimitive } from "bits-ui";
|
||||||
import PopoverPortal from "./popover-portal.svelte";
|
import PopoverPortal from "./popover-portal.svelte";
|
||||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||||
import type { ComponentProps } from "svelte";
|
import type { ComponentProps } from "svelte";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
sideOffset = 4,
|
sideOffset = 4,
|
||||||
align = "center",
|
align = "center",
|
||||||
portalProps,
|
portalProps,
|
||||||
...restProps
|
...restProps
|
||||||
}: PopoverPrimitive.ContentProps & {
|
}: PopoverPrimitive.ContentProps & {
|
||||||
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof PopoverPortal>>;
|
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof PopoverPortal>>;
|
||||||
} = $props();
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PopoverPortal {...portalProps}>
|
<PopoverPortal {...portalProps}>
|
||||||
<PopoverPrimitive.Content
|
<PopoverPrimitive.Content
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="popover-content"
|
data-slot="popover-content"
|
||||||
{sideOffset}
|
{sideOffset}
|
||||||
{align}
|
{align}
|
||||||
class={cn(
|
class={cn(
|
||||||
"bg-popover text-popover-foreground 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-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--bits-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
"bg-popover text-popover-foreground 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-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--bits-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
</PopoverPortal>
|
</PopoverPortal>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Popover as PopoverPrimitive } from "bits-ui";
|
import { Popover as PopoverPrimitive } from "bits-ui";
|
||||||
|
|
||||||
let { ...restProps }: PopoverPrimitive.PortalProps = $props();
|
let { ...restProps }: PopoverPrimitive.PortalProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PopoverPrimitive.Portal {...restProps} />
|
<PopoverPrimitive.Portal {...restProps} />
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
import { Popover as PopoverPrimitive } from "bits-ui";
|
import { Popover as PopoverPrimitive } from "bits-ui";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
}: PopoverPrimitive.TriggerProps = $props();
|
}: PopoverPrimitive.TriggerProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PopoverPrimitive.Trigger
|
<PopoverPrimitive.Trigger
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="popover-trigger"
|
data-slot="popover-trigger"
|
||||||
class={cn("", className)}
|
class={cn("", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Popover as PopoverPrimitive } from "bits-ui";
|
import { Popover as PopoverPrimitive } from "bits-ui";
|
||||||
|
|
||||||
let { open = $bindable(false), ...restProps }: PopoverPrimitive.RootProps = $props();
|
let { open = $bindable(false), ...restProps }: PopoverPrimitive.RootProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PopoverPrimitive.Root bind:open {...restProps} />
|
<PopoverPrimitive.Root bind:open {...restProps} />
|
||||||
|
|||||||
@@ -17,10 +17,12 @@
|
|||||||
|
|
||||||
<div class="min-h-screen bg-background">
|
<div class="min-h-screen bg-background">
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
|
|
||||||
<!-- Mobile top nav -->
|
<!-- Mobile top nav -->
|
||||||
<div class="lg:hidden flex items-center gap-2 py-3 border-b border-border/40">
|
<div class="lg:hidden flex items-center gap-2 py-3 border-b border-border/40">
|
||||||
<a href="/" class="text-xs text-muted-foreground hover:text-foreground transition-colors shrink-0 mr-2">
|
<a
|
||||||
|
href="/"
|
||||||
|
class="text-xs text-muted-foreground hover:text-foreground transition-colors shrink-0 mr-2"
|
||||||
|
>
|
||||||
{$_("admin.nav.back_mobile")}
|
{$_("admin.nav.back_mobile")}
|
||||||
</a>
|
</a>
|
||||||
{#each navLinks as link (link.href)}
|
{#each navLinks as link (link.href)}
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ export async function load({ params, fetch, cookies }) {
|
|||||||
const token = cookies.get("session_token") || "";
|
const token = cookies.get("session_token") || "";
|
||||||
const [articles, modelsResult] = await Promise.all([
|
const [articles, modelsResult] = await Promise.all([
|
||||||
adminListArticles(fetch, token).catch(() => []),
|
adminListArticles(fetch, token).catch(() => []),
|
||||||
adminListUsers({ role: "model", limit: 200 }, fetch, token).catch(() => ({ items: [], total: 0 })),
|
adminListUsers({ role: "model", limit: 200 }, fetch, token).catch(() => ({
|
||||||
|
items: [],
|
||||||
|
total: 0,
|
||||||
|
})),
|
||||||
]);
|
]);
|
||||||
const article = articles.find((a) => a.id === params.id);
|
const article = articles.find((a) => a.id === params.id);
|
||||||
if (!article) throw error(404, "Article not found");
|
if (!article) throw error(404, "Article not found");
|
||||||
|
|||||||
@@ -107,13 +107,13 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={`px-3 py-1 transition-colors ${editorTab === "write" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
|
class={`px-3 py-1 transition-colors ${editorTab === "write" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
|
||||||
onclick={() => (editorTab = "write")}
|
onclick={() => (editorTab = "write")}>{$_("admin.common.write")}</button
|
||||||
>{$_("admin.common.write")}</button>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={`px-3 py-1 transition-colors ${editorTab === "preview" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
|
class={`px-3 py-1 transition-colors ${editorTab === "preview" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
|
||||||
onclick={() => (editorTab = "preview")}
|
onclick={() => (editorTab = "preview")}>{$_("admin.common.preview")}</button
|
||||||
>{$_("admin.common.preview")}</button>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sm:grid sm:grid-cols-2 sm:gap-4 min-h-96">
|
<div class="sm:grid sm:grid-cols-2 sm:gap-4 min-h-96">
|
||||||
@@ -127,7 +127,9 @@
|
|||||||
{#if preview}
|
{#if preview}
|
||||||
{@html preview}
|
{@html preview}
|
||||||
{:else}
|
{:else}
|
||||||
<p class="text-muted-foreground italic text-sm">{$_("admin.article_form.preview_placeholder")}</p>
|
<p class="text-muted-foreground italic text-sm">
|
||||||
|
{$_("admin.article_form.preview_placeholder")}
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -152,7 +154,11 @@
|
|||||||
<SelectTrigger class="w-full">
|
<SelectTrigger class="w-full">
|
||||||
{#if selectedAuthor}
|
{#if selectedAuthor}
|
||||||
{#if selectedAuthor.avatar}
|
{#if selectedAuthor.avatar}
|
||||||
<img src={getAssetUrl(selectedAuthor.avatar, "mini")} alt="" class="h-5 w-5 rounded-full object-cover shrink-0" />
|
<img
|
||||||
|
src={getAssetUrl(selectedAuthor.avatar, "mini")}
|
||||||
|
alt=""
|
||||||
|
class="h-5 w-5 rounded-full object-cover shrink-0"
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{selectedAuthor.artist_name}
|
{selectedAuthor.artist_name}
|
||||||
{:else}
|
{:else}
|
||||||
@@ -164,7 +170,11 @@
|
|||||||
{#each data.authors as author (author.id)}
|
{#each data.authors as author (author.id)}
|
||||||
<SelectItem value={author.id}>
|
<SelectItem value={author.id}>
|
||||||
{#if author.avatar}
|
{#if author.avatar}
|
||||||
<img src={getAssetUrl(author.avatar, "mini")} alt="" class="h-5 w-5 rounded-full object-cover shrink-0" />
|
<img
|
||||||
|
src={getAssetUrl(author.avatar, "mini")}
|
||||||
|
alt=""
|
||||||
|
class="h-5 w-5 rounded-full object-cover shrink-0"
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{author.artist_name}
|
{author.artist_name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
@@ -195,7 +205,11 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="flex gap-3 pt-2">
|
<div class="flex gap-3 pt-2">
|
||||||
<Button onclick={handleSubmit} disabled={saving} class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90">
|
<Button
|
||||||
|
onclick={handleSubmit}
|
||||||
|
disabled={saving}
|
||||||
|
class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
|
||||||
|
>
|
||||||
{saving ? $_("admin.common.saving") : $_("admin.common.save_changes")}
|
{saving ? $_("admin.common.saving") : $_("admin.common.save_changes")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" href="/admin/articles">{$_("common.cancel")}</Button>
|
<Button variant="outline" href="/admin/articles">{$_("common.cancel")}</Button>
|
||||||
|
|||||||
@@ -98,13 +98,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="slug">{$_("admin.common.slug_field")}</Label>
|
<Label for="slug">{$_("admin.common.slug_field")}</Label>
|
||||||
<Input id="slug" bind:value={slug} placeholder={$_("admin.article_form.slug_placeholder")} />
|
<Input
|
||||||
|
id="slug"
|
||||||
|
bind:value={slug}
|
||||||
|
placeholder={$_("admin.article_form.slug_placeholder")}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="excerpt">{$_("admin.article_form.excerpt")}</Label>
|
<Label for="excerpt">{$_("admin.article_form.excerpt")}</Label>
|
||||||
<Textarea id="excerpt" bind:value={excerpt} placeholder={$_("admin.article_form.excerpt_placeholder")} rows={2} />
|
<Textarea
|
||||||
|
id="excerpt"
|
||||||
|
bind:value={excerpt}
|
||||||
|
placeholder={$_("admin.article_form.excerpt_placeholder")}
|
||||||
|
rows={2}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Markdown editor with live preview -->
|
<!-- Markdown editor with live preview -->
|
||||||
@@ -115,13 +124,13 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={`px-3 py-1 transition-colors ${editorTab === "write" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
|
class={`px-3 py-1 transition-colors ${editorTab === "write" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
|
||||||
onclick={() => (editorTab = "write")}
|
onclick={() => (editorTab = "write")}>{$_("admin.common.write")}</button
|
||||||
>{$_("admin.common.write")}</button>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={`px-3 py-1 transition-colors ${editorTab === "preview" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
|
class={`px-3 py-1 transition-colors ${editorTab === "preview" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
|
||||||
onclick={() => (editorTab = "preview")}
|
onclick={() => (editorTab = "preview")}>{$_("admin.common.preview")}</button
|
||||||
>{$_("admin.common.preview")}</button>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Mobile: single pane toggled; Desktop: side by side -->
|
<!-- Mobile: single pane toggled; Desktop: side by side -->
|
||||||
@@ -137,7 +146,9 @@
|
|||||||
{#if preview}
|
{#if preview}
|
||||||
{@html preview}
|
{@html preview}
|
||||||
{:else}
|
{:else}
|
||||||
<p class="text-muted-foreground italic text-sm">{$_("admin.article_form.preview_placeholder")}</p>
|
<p class="text-muted-foreground italic text-sm">
|
||||||
|
{$_("admin.article_form.preview_placeholder")}
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -146,13 +157,19 @@
|
|||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label>{$_("admin.common.cover_image")}</Label>
|
<Label>{$_("admin.common.cover_image")}</Label>
|
||||||
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleImageUpload} />
|
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleImageUpload} />
|
||||||
{#if imageId}<p class="text-xs text-green-600 mt-1">{$_("admin.common.image_uploaded")} ✓</p>{/if}
|
{#if imageId}<p class="text-xs text-green-600 mt-1">
|
||||||
|
{$_("admin.common.image_uploaded")} ✓
|
||||||
|
</p>{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="category">{$_("admin.article_form.category")}</Label>
|
<Label for="category">{$_("admin.article_form.category")}</Label>
|
||||||
<Input id="category" bind:value={category} placeholder={$_("admin.article_form.category_placeholder")} />
|
<Input
|
||||||
|
id="category"
|
||||||
|
bind:value={category}
|
||||||
|
placeholder={$_("admin.article_form.category_placeholder")}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label>{$_("admin.common.publish_date")}</Label>
|
<Label>{$_("admin.common.publish_date")}</Label>
|
||||||
@@ -171,7 +188,11 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="flex gap-3 pt-2">
|
<div class="flex gap-3 pt-2">
|
||||||
<Button onclick={handleSubmit} disabled={saving} class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90">
|
<Button
|
||||||
|
onclick={handleSubmit}
|
||||||
|
disabled={saving}
|
||||||
|
class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
|
||||||
|
>
|
||||||
{saving ? $_("admin.common.creating") : $_("admin.article_form.create")}
|
{saving ? $_("admin.common.creating") : $_("admin.article_form.create")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" href="/admin/articles">{$_("common.cancel")}</Button>
|
<Button variant="outline" href="/admin/articles">{$_("common.cancel")}</Button>
|
||||||
|
|||||||
@@ -87,7 +87,9 @@
|
|||||||
<div class="py-3 sm:py-6 sm:pl-6">
|
<div class="py-3 sm:py-6 sm:pl-6">
|
||||||
<div class="flex items-center justify-between mb-6 px-3 sm:px-0">
|
<div class="flex items-center justify-between mb-6 px-3 sm:px-0">
|
||||||
<h1 class="text-2xl font-bold">{$_("admin.users.title")}</h1>
|
<h1 class="text-2xl font-bold">{$_("admin.users.title")}</h1>
|
||||||
<span class="text-sm text-muted-foreground">{$_("admin.users.total", { values: { total: data.total } })}</span>
|
<span class="text-sm text-muted-foreground"
|
||||||
|
>{$_("admin.users.total", { values: { total: data.total } })}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
@@ -120,11 +122,21 @@
|
|||||||
<table class="w-full text-sm">
|
<table class="w-full text-sm">
|
||||||
<thead class="bg-muted/30">
|
<thead class="bg-muted/30">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left font-medium text-muted-foreground">{$_("admin.users.col_user")}</th>
|
<th class="px-4 py-3 text-left font-medium text-muted-foreground"
|
||||||
<th class="px-4 py-3 text-left font-medium text-muted-foreground hidden sm:table-cell">{$_("admin.users.col_email")}</th>
|
>{$_("admin.users.col_user")}</th
|
||||||
<th class="px-4 py-3 text-left font-medium text-muted-foreground">{$_("admin.users.col_role")}</th>
|
>
|
||||||
<th class="px-4 py-3 text-left font-medium text-muted-foreground hidden md:table-cell">{$_("admin.users.col_joined")}</th>
|
<th class="px-4 py-3 text-left font-medium text-muted-foreground hidden sm:table-cell"
|
||||||
<th class="px-4 py-3 text-right font-medium text-muted-foreground">{$_("admin.users.col_actions")}</th>
|
>{$_("admin.users.col_email")}</th
|
||||||
|
>
|
||||||
|
<th class="px-4 py-3 text-left font-medium text-muted-foreground"
|
||||||
|
>{$_("admin.users.col_role")}</th
|
||||||
|
>
|
||||||
|
<th class="px-4 py-3 text-left font-medium text-muted-foreground hidden md:table-cell"
|
||||||
|
>{$_("admin.users.col_joined")}</th
|
||||||
|
>
|
||||||
|
<th class="px-4 py-3 text-right font-medium text-muted-foreground"
|
||||||
|
>{$_("admin.users.col_actions")}</th
|
||||||
|
>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-border/30">
|
<tbody class="divide-y divide-border/30">
|
||||||
@@ -147,12 +159,18 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
<span class="font-medium truncate">{user.artist_name || user.first_name || "—"}</span>
|
<span class="font-medium truncate"
|
||||||
|
>{user.artist_name || user.first_name || "—"}</span
|
||||||
|
>
|
||||||
{#if user.is_admin}
|
{#if user.is_admin}
|
||||||
<Badge variant="default" class="shrink-0 text-[10px] px-1.5 py-0">{$_("admin.users.admin_badge")}</Badge>
|
<Badge variant="default" class="shrink-0 text-[10px] px-1.5 py-0"
|
||||||
|
>{$_("admin.users.admin_badge")}</Badge
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs text-muted-foreground sm:hidden truncate block">{user.email}</span>
|
<span class="text-xs text-muted-foreground sm:hidden truncate block"
|
||||||
|
>{user.email}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -173,7 +191,9 @@
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-muted-foreground hidden md:table-cell">{formatDate(user.date_created)}</td>
|
<td class="px-4 py-3 text-muted-foreground hidden md:table-cell"
|
||||||
|
>{formatDate(user.date_created)}</td
|
||||||
|
>
|
||||||
<td class="px-4 py-3 text-right">
|
<td class="px-4 py-3 text-right">
|
||||||
<div class="flex items-center justify-end gap-1">
|
<div class="flex items-center justify-end gap-1">
|
||||||
<Button size="sm" variant="ghost" href="/admin/users/{user.id}">
|
<Button size="sm" variant="ghost" href="/admin/users/{user.id}">
|
||||||
@@ -195,7 +215,9 @@
|
|||||||
|
|
||||||
{#if data.items.length === 0}
|
{#if data.items.length === 0}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="px-4 py-8 text-center text-muted-foreground">{$_("admin.users.no_results")}</td>
|
<td colspan="5" class="px-4 py-8 text-center text-muted-foreground"
|
||||||
|
>{$_("admin.users.no_results")}</td
|
||||||
|
>
|
||||||
</tr>
|
</tr>
|
||||||
{/if}
|
{/if}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -206,7 +228,13 @@
|
|||||||
{#if data.total > data.limit}
|
{#if data.total > data.limit}
|
||||||
<div class="flex items-center justify-between mt-4 px-3 sm:px-0">
|
<div class="flex items-center justify-between mt-4 px-3 sm:px-0">
|
||||||
<span class="text-sm text-muted-foreground">
|
<span class="text-sm text-muted-foreground">
|
||||||
{$_("admin.users.showing", { values: { start: data.offset + 1, end: Math.min(data.offset + data.limit, data.total), total: data.total } })}
|
{$_("admin.users.showing", {
|
||||||
|
values: {
|
||||||
|
start: data.offset + 1,
|
||||||
|
end: Math.min(data.offset + data.limit, data.total),
|
||||||
|
total: data.total,
|
||||||
|
},
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
@@ -244,7 +272,9 @@
|
|||||||
<Dialog.Header>
|
<Dialog.Header>
|
||||||
<Dialog.Title>{$_("admin.users.delete_title")}</Dialog.Title>
|
<Dialog.Title>{$_("admin.users.delete_title")}</Dialog.Title>
|
||||||
<Dialog.Description>
|
<Dialog.Description>
|
||||||
{$_("admin.users.delete_description", { values: { name: deleteTarget?.artist_name || deleteTarget?.email } })}
|
{$_("admin.users.delete_description", {
|
||||||
|
values: { name: deleteTarget?.artist_name || deleteTarget?.email },
|
||||||
|
})}
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
</Dialog.Header>
|
</Dialog.Header>
|
||||||
<Dialog.Footer>
|
<Dialog.Footer>
|
||||||
|
|||||||
@@ -106,7 +106,11 @@
|
|||||||
</Button>
|
</Button>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold">{data.user.artist_name || data.user.email}</h1>
|
<h1 class="text-2xl font-bold">{data.user.artist_name || data.user.email}</h1>
|
||||||
<p class="text-xs text-muted-foreground">{data.user.email} · {data.user.role}{data.user.is_admin ? " · " + $_("admin.users.admin_badge").toLowerCase() : ""}</p>
|
<p class="text-xs text-muted-foreground">
|
||||||
|
{data.user.email} · {data.user.role}{data.user.is_admin
|
||||||
|
? " · " + $_("admin.users.admin_badge").toLowerCase()
|
||||||
|
: ""}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -155,8 +159,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Admin flag -->
|
<!-- Admin flag -->
|
||||||
<label class="flex items-center gap-3 rounded-lg border border-border/40 px-4 py-3 cursor-pointer hover:bg-muted/20 transition-colors">
|
<label
|
||||||
<input type="checkbox" bind:checked={isAdmin} class="h-4 w-4 rounded accent-primary shrink-0" />
|
class="flex items-center gap-3 rounded-lg border border-border/40 px-4 py-3 cursor-pointer hover:bg-muted/20 transition-colors"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={isAdmin}
|
||||||
|
class="h-4 w-4 rounded accent-primary shrink-0"
|
||||||
|
/>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-sm font-medium">{$_("admin.user_edit.is_admin")}</span>
|
<span class="text-sm font-medium">{$_("admin.user_edit.is_admin")}</span>
|
||||||
<p class="text-xs text-muted-foreground">{$_("admin.user_edit.is_admin_hint")}</p>
|
<p class="text-xs text-muted-foreground">{$_("admin.user_edit.is_admin_hint")}</p>
|
||||||
@@ -164,7 +174,11 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<Button onclick={handleSave} disabled={saving} class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90">
|
<Button
|
||||||
|
onclick={handleSave}
|
||||||
|
disabled={saving}
|
||||||
|
class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
|
||||||
|
>
|
||||||
{saving ? $_("admin.common.saving") : $_("admin.common.save_changes")}
|
{saving ? $_("admin.common.saving") : $_("admin.common.save_changes")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -97,7 +97,11 @@
|
|||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="title">{$_("admin.common.title_field")}</Label>
|
<Label for="title">{$_("admin.common.title_field")}</Label>
|
||||||
<Input id="title" bind:value={title} placeholder={$_("admin.video_form.title_placeholder")} />
|
<Input
|
||||||
|
id="title"
|
||||||
|
bind:value={title}
|
||||||
|
placeholder={$_("admin.video_form.title_placeholder")}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="slug">{$_("admin.common.slug_field")}</Label>
|
<Label for="slug">{$_("admin.common.slug_field")}</Label>
|
||||||
@@ -107,7 +111,12 @@
|
|||||||
|
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="description">{$_("admin.video_form.description")}</Label>
|
<Label for="description">{$_("admin.video_form.description")}</Label>
|
||||||
<Textarea id="description" bind:value={description} placeholder={$_("admin.video_form.description_placeholder")} rows={3} />
|
<Textarea
|
||||||
|
id="description"
|
||||||
|
bind:value={description}
|
||||||
|
placeholder={$_("admin.video_form.description_placeholder")}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
@@ -127,7 +136,7 @@
|
|||||||
{#if movieId}
|
{#if movieId}
|
||||||
<video
|
<video
|
||||||
src={getAssetUrl(movieId)}
|
src={getAssetUrl(movieId)}
|
||||||
poster={imageId ? getAssetUrl(imageId, "preview") ?? undefined : undefined}
|
poster={imageId ? (getAssetUrl(imageId, "preview") ?? undefined) : undefined}
|
||||||
controls
|
controls
|
||||||
class="w-full rounded-lg bg-black max-h-72 mb-2"
|
class="w-full rounded-lg bg-black max-h-72 mb-2"
|
||||||
></video>
|
></video>
|
||||||
@@ -142,7 +151,11 @@
|
|||||||
|
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label>{$_("admin.common.publish_date")}</Label>
|
<Label>{$_("admin.common.publish_date")}</Label>
|
||||||
<DatePicker bind:value={uploadDate} placeholder={$_("admin.common.publish_date")} showTime={false} />
|
<DatePicker
|
||||||
|
bind:value={uploadDate}
|
||||||
|
placeholder={$_("admin.common.publish_date")}
|
||||||
|
showTime={false}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-6">
|
<div class="flex gap-6">
|
||||||
@@ -162,7 +175,9 @@
|
|||||||
<Select type="multiple" bind:value={selectedModelIds}>
|
<Select type="multiple" bind:value={selectedModelIds}>
|
||||||
<SelectTrigger class="w-full">
|
<SelectTrigger class="w-full">
|
||||||
{#if selectedModelIds.length}
|
{#if selectedModelIds.length}
|
||||||
{$_("admin.video_form.models_selected", { values: { count: selectedModelIds.length } })}
|
{$_("admin.video_form.models_selected", {
|
||||||
|
values: { count: selectedModelIds.length },
|
||||||
|
})}
|
||||||
{:else}
|
{:else}
|
||||||
<span class="text-muted-foreground">{$_("admin.video_form.no_models")}</span>
|
<span class="text-muted-foreground">{$_("admin.video_form.no_models")}</span>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -171,7 +186,11 @@
|
|||||||
{#each data.models as model (model.id)}
|
{#each data.models as model (model.id)}
|
||||||
<SelectItem value={model.id}>
|
<SelectItem value={model.id}>
|
||||||
{#if model.avatar}
|
{#if model.avatar}
|
||||||
<img src={getAssetUrl(model.avatar, "mini")} alt="" class="h-5 w-5 rounded-full object-cover shrink-0" />
|
<img
|
||||||
|
src={getAssetUrl(model.avatar, "mini")}
|
||||||
|
alt=""
|
||||||
|
class="h-5 w-5 rounded-full object-cover shrink-0"
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{model.artist_name}
|
{model.artist_name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
@@ -182,7 +201,11 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="flex gap-3 pt-2">
|
<div class="flex gap-3 pt-2">
|
||||||
<Button onclick={handleSubmit} disabled={saving} class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90">
|
<Button
|
||||||
|
onclick={handleSubmit}
|
||||||
|
disabled={saving}
|
||||||
|
class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
|
||||||
|
>
|
||||||
{saving ? $_("admin.common.saving") : $_("admin.common.save_changes")}
|
{saving ? $_("admin.common.saving") : $_("admin.common.save_changes")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" href="/admin/videos">{$_("common.cancel")}</Button>
|
<Button variant="outline" href="/admin/videos">{$_("common.cancel")}</Button>
|
||||||
|
|||||||
@@ -137,13 +137,17 @@
|
|||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label>{$_("admin.common.cover_image")}</Label>
|
<Label>{$_("admin.common.cover_image")}</Label>
|
||||||
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleImageUpload} />
|
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleImageUpload} />
|
||||||
{#if imageId}<p class="text-xs text-green-600 mt-1">{$_("admin.common.image_uploaded")} ✓</p>{/if}
|
{#if imageId}<p class="text-xs text-green-600 mt-1">
|
||||||
|
{$_("admin.common.image_uploaded")} ✓
|
||||||
|
</p>{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label>{$_("admin.video_form.video_file")}</Label>
|
<Label>{$_("admin.video_form.video_file")}</Label>
|
||||||
<FileDropZone accept="video/*" maxFileSize={2000 * MEGABYTE} onUpload={handleVideoUpload} />
|
<FileDropZone accept="video/*" maxFileSize={2000 * MEGABYTE} onUpload={handleVideoUpload} />
|
||||||
{#if movieId}<p class="text-xs text-green-600 mt-1">{$_("admin.video_form.video_uploaded")} ✓</p>{/if}
|
{#if movieId}<p class="text-xs text-green-600 mt-1">
|
||||||
|
{$_("admin.video_form.video_uploaded")} ✓
|
||||||
|
</p>{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
@@ -153,7 +157,11 @@
|
|||||||
|
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label>{$_("admin.common.publish_date")}</Label>
|
<Label>{$_("admin.common.publish_date")}</Label>
|
||||||
<DatePicker bind:value={uploadDate} placeholder={$_("admin.common.publish_date")} showTime={false} />
|
<DatePicker
|
||||||
|
bind:value={uploadDate}
|
||||||
|
placeholder={$_("admin.common.publish_date")}
|
||||||
|
showTime={false}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-6">
|
<div class="flex gap-6">
|
||||||
@@ -189,7 +197,11 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="flex gap-3 pt-2">
|
<div class="flex gap-3 pt-2">
|
||||||
<Button onclick={handleSubmit} disabled={saving} class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90">
|
<Button
|
||||||
|
onclick={handleSubmit}
|
||||||
|
disabled={saving}
|
||||||
|
class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
|
||||||
|
>
|
||||||
{saving ? $_("admin.common.creating") : $_("admin.video_form.create")}
|
{saving ? $_("admin.common.creating") : $_("admin.video_form.create")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" href="/admin/videos">{$_("common.cancel")}</Button>
|
<Button variant="outline" href="/admin/videos">{$_("common.cancel")}</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user