feat: init
This commit is contained in:
commit
8a609da778
59 files changed
+10053
No files matched your search
@@ -0,0 +1,112 @@
|
||||
<script lang="ts" module>
|
||||
export type DayTone = "none" | "inside" | "start" | "end" | "solo";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import ChevronLeft from "@lucide/svelte/icons/chevron-left";
|
||||
import ChevronRight from "@lucide/svelte/icons/chevron-right";
|
||||
import { fromKey, localKey } from "../lib/due";
|
||||
import { WEEKDAYS } from "../lib/vocab";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
interface Props {
|
||||
anchor: string | null;
|
||||
onhover?: (key: string) => void;
|
||||
onpick: (key: string) => void;
|
||||
tone: (key: string) => DayTone;
|
||||
}
|
||||
|
||||
let { anchor, tone, onpick, onhover }: Props = $props();
|
||||
|
||||
const DAY = 86_400_000;
|
||||
const monthName = new Intl.DateTimeFormat("en-US", { month: "long" });
|
||||
const todayKey = localKey(new Date());
|
||||
|
||||
let picked = $state<Date | null>(null);
|
||||
|
||||
const month = $derived.by(() => {
|
||||
if (picked) return picked;
|
||||
const base = anchor ? fromKey(anchor) : new Date();
|
||||
return new Date(base.getFullYear(), base.getMonth(), 1);
|
||||
});
|
||||
|
||||
const days = $derived.by(() => {
|
||||
const offset = (month.getDay() + 6) % 7;
|
||||
const start = new Date(month.getTime() - offset * DAY);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
return Array.from({ length: 42 }, (_, index) => {
|
||||
const date = new Date(start.getTime() + index * DAY);
|
||||
date.setHours(0, 0, 0, 0);
|
||||
return {
|
||||
date,
|
||||
inMonth: date.getMonth() === month.getMonth(),
|
||||
key: localKey(date),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
function shiftMonth(delta: number) {
|
||||
picked = new Date(month.getFullYear(), month.getMonth() + delta, 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-60 select-none">
|
||||
<div class="flex items-center gap-1 px-1 pb-1">
|
||||
<button
|
||||
aria-label="Previous month"
|
||||
class="grid size-6 place-items-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onclick={() => shiftMonth(-1)}
|
||||
type="button"
|
||||
>
|
||||
<ChevronLeft aria-hidden="true" class="size-3.5" />
|
||||
</button>
|
||||
<span class="flex-1 text-center font-medium text-xs first-letter:uppercase">
|
||||
{monthName.format(month)}
|
||||
{month.getFullYear()}
|
||||
</span>
|
||||
<button
|
||||
aria-label="Next month"
|
||||
class="grid size-6 place-items-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onclick={() => shiftMonth(1)}
|
||||
type="button"
|
||||
>
|
||||
<ChevronRight aria-hidden="true" class="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-7 pb-0.5">
|
||||
{#each WEEKDAYS as weekday (weekday)}
|
||||
<span class="py-0.5 text-center text-[10px] text-muted-foreground">
|
||||
{weekday}
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-7 gap-y-px">
|
||||
{#each days as day (day.key)}
|
||||
{@const shape = tone(day.key)}
|
||||
<button
|
||||
aria-label={day.date.toLocaleDateString("en-US")}
|
||||
aria-pressed={shape !== "none"}
|
||||
class={cn(
|
||||
"h-8 text-xs outline-none transition-colors tabular focus-visible:ring-2 focus-visible:ring-ring",
|
||||
day.inMonth ? "text-foreground" : "text-muted-foreground/50",
|
||||
shape === "none" && "rounded-md hover:bg-muted",
|
||||
shape === "inside" && "bg-secondary",
|
||||
shape !== "none" &&
|
||||
shape !== "inside" &&
|
||||
"bg-primary font-medium text-primary-foreground",
|
||||
shape === "solo" && "rounded-md",
|
||||
shape === "start" && "rounded-l-md",
|
||||
shape === "end" && "rounded-r-md",
|
||||
day.key === todayKey && shape === "none" && "font-medium text-signal",
|
||||
)}
|
||||
onclick={() => onpick(day.key)}
|
||||
onmouseenter={() => onhover?.(day.key)}
|
||||
type="button"
|
||||
>
|
||||
{day.date.getDate()}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
Reference in new issue
Block a user