time tool progress

This commit is contained in:
vcoppe
2024-06-13 17:36:43 +02:00
parent 9132a45798
commit b29500cfeb
27 changed files with 872 additions and 11 deletions

View File

@@ -0,0 +1,21 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = CalendarPrimitive.CellProps;
export let date: $$Props["date"];
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.Cell
{date}
class={cn(
"relative h-9 w-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md [&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-month])]:bg-accent/50",
className
)}
{...$$restProps}
>
<slot />
</CalendarPrimitive.Cell>

View File

@@ -0,0 +1,42 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
type $$Props = CalendarPrimitive.DayProps;
type $$Events = CalendarPrimitive.DayEvents;
export let date: $$Props["date"];
export let month: $$Props["month"];
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.Day
on:click
{date}
{month}
class={cn(
buttonVariants({ variant: "ghost" }),
"h-9 w-9 p-0 font-normal ",
"[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground",
// Selected
"data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:opacity-100 data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-foreground data-[selected]:focus:bg-primary data-[selected]:focus:text-primary-foreground",
// Disabled
"data-[disabled]:text-muted-foreground data-[disabled]:opacity-50",
// Unavailable
"data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through",
// Outside months
"data-[outside-month]:pointer-events-none data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50 [&[data-outside-month][data-selected]]:bg-accent/50 [&[data-outside-month][data-selected]]:text-muted-foreground [&[data-outside-month][data-selected]]:opacity-30",
className
)}
{...$$restProps}
let:selected
let:disabled
let:unavailable
let:builder
>
<slot {selected} {disabled} {unavailable} {builder}>
{date.day}
</slot>
</CalendarPrimitive.Day>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = CalendarPrimitive.GridBodyProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.GridBody class={cn(className)} {...$$restProps}>
<slot />
</CalendarPrimitive.GridBody>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = CalendarPrimitive.GridHeadProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.GridHead class={cn(className)} {...$$restProps}>
<slot />
</CalendarPrimitive.GridHead>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = CalendarPrimitive.GridRowProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.GridRow class={cn("flex", className)} {...$$restProps}>
<slot />
</CalendarPrimitive.GridRow>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = CalendarPrimitive.GridProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.Grid class={cn("w-full border-collapse space-y-1", className)} {...$$restProps}>
<slot />
</CalendarPrimitive.Grid>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = CalendarPrimitive.HeadCellProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.HeadCell
class={cn("w-9 rounded-md text-[0.8rem] font-normal text-muted-foreground", className)}
{...$$restProps}
>
<slot />
</CalendarPrimitive.HeadCell>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = CalendarPrimitive.HeaderProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.Header
class={cn("relative flex w-full items-center justify-between pt-1", className)}
{...$$restProps}
>
<slot />
</CalendarPrimitive.Header>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = CalendarPrimitive.HeadingProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.Heading
let:headingValue
class={cn("text-sm font-medium", className)}
{...$$restProps}
>
<slot {headingValue}>
{headingValue}
</slot>
</CalendarPrimitive.Heading>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
class={cn("mt-4 flex flex-col space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0", className)}
{...$$restProps}
>
<slot />
</div>

View File

@@ -0,0 +1,27 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import ChevronRight from "lucide-svelte/icons/chevron-right";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
type $$Props = CalendarPrimitive.NextButtonProps;
type $$Events = CalendarPrimitive.NextButtonEvents;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.NextButton
on:click
class={cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
className
)}
{...$$restProps}
let:builder
>
<slot {builder}>
<ChevronRight class="h-4 w-4" />
</slot>
</CalendarPrimitive.NextButton>

View File

@@ -0,0 +1,27 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import ChevronLeft from "lucide-svelte/icons/chevron-left";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
type $$Props = CalendarPrimitive.PrevButtonProps;
type $$Events = CalendarPrimitive.PrevButtonEvents;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.PrevButton
on:click
class={cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
className
)}
{...$$restProps}
let:builder
>
<slot {builder}>
<ChevronLeft class="h-4 w-4" />
</slot>
</CalendarPrimitive.PrevButton>

View File

@@ -0,0 +1,59 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import * as Calendar from "./index.js";
import { cn } from "$lib/utils.js";
type $$Props = CalendarPrimitive.Props;
type $$Events = CalendarPrimitive.Events;
export let value: $$Props["value"] = undefined;
export let placeholder: $$Props["placeholder"] = undefined;
export let weekdayFormat: $$Props["weekdayFormat"] = "short";
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.Root
bind:value
bind:placeholder
{weekdayFormat}
class={cn("p-3", className)}
{...$$restProps}
on:keydown
let:months
let:weekdays
>
<Calendar.Header>
<Calendar.PrevButton />
<Calendar.Heading />
<Calendar.NextButton />
</Calendar.Header>
<Calendar.Months>
{#each months as month}
<Calendar.Grid>
<Calendar.GridHead>
<Calendar.GridRow class="flex">
{#each weekdays as weekday}
<Calendar.HeadCell>
{weekday.slice(0, 2)}
</Calendar.HeadCell>
{/each}
</Calendar.GridRow>
</Calendar.GridHead>
<Calendar.GridBody>
{#each month.weeks as weekDates}
<Calendar.GridRow class="mt-2 w-full">
{#each weekDates as date}
<Calendar.Cell {date}>
<Calendar.Day {date} month={month.value} />
</Calendar.Cell>
{/each}
</Calendar.GridRow>
{/each}
</Calendar.GridBody>
</Calendar.Grid>
{/each}
</Calendar.Months>
</CalendarPrimitive.Root>

View File

@@ -0,0 +1,30 @@
import Root from "./calendar.svelte";
import Cell from "./calendar-cell.svelte";
import Day from "./calendar-day.svelte";
import Grid from "./calendar-grid.svelte";
import Header from "./calendar-header.svelte";
import Months from "./calendar-months.svelte";
import GridRow from "./calendar-grid-row.svelte";
import Heading from "./calendar-heading.svelte";
import GridBody from "./calendar-grid-body.svelte";
import GridHead from "./calendar-grid-head.svelte";
import HeadCell from "./calendar-head-cell.svelte";
import NextButton from "./calendar-next-button.svelte";
import PrevButton from "./calendar-prev-button.svelte";
export {
Day,
Cell,
Grid,
Header,
Months,
GridRow,
Heading,
GridBody,
GridHead,
HeadCell,
NextButton,
PrevButton,
//
Root as Calendar,
};

View File

@@ -0,0 +1,39 @@
<script lang="ts">
import CalendarIcon from 'lucide-svelte/icons/calendar';
import { DateFormatter, type DateValue, getLocalTimeZone } from '@internationalized/date';
import { cn } from '$lib/utils.js';
import { Button } from '$lib/components/ui/button/index.js';
import { Calendar } from '$lib/components/ui/calendar/index.js';
import * as Popover from '$lib/components/ui/popover/index.js';
export let value: DateValue | undefined = undefined;
export let placeholder: string = 'Pick a date';
export let locale = 'en';
export let disabled: boolean = false;
export let onValueChange: any;
const df = new DateFormatter(locale, {
dateStyle: 'long'
});
</script>
<Popover.Root>
<Popover.Trigger asChild let:builder>
<Button
variant="outline"
class={cn(
'w-[280px] justify-start text-left font-normal',
!value && 'text-muted-foreground',
$$props.class
)}
{disabled}
builders={[builder]}
>
<CalendarIcon class="mr-2 h-4 w-4" />
{value ? df.format(value.toDate(getLocalTimeZone())) : placeholder}
</Button>
</Popover.Trigger>
<Popover.Content class="w-auto p-0">
<Calendar bind:value initialFocus {locale} {onValueChange} />
</Popover.Content>
</Popover.Root>

View File

@@ -0,0 +1,17 @@
import { Popover as PopoverPrimitive } from "bits-ui";
import Content from "./popover-content.svelte";
const Root = PopoverPrimitive.Root;
const Trigger = PopoverPrimitive.Trigger;
const Close = PopoverPrimitive.Close;
export {
Root,
Content,
Trigger,
Close,
//
Root as Popover,
Content as PopoverContent,
Trigger as PopoverTrigger,
Close as PopoverClose,
};

View File

@@ -0,0 +1,22 @@
<script lang="ts">
import { Popover as PopoverPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/utils.js";
type $$Props = PopoverPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = undefined;
export { className as class };
</script>
<PopoverPrimitive.Content
{transition}
{transitionConfig}
class={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
className
)}
{...$$restProps}
>
<slot />
</PopoverPrimitive.Content>

View File

@@ -0,0 +1,27 @@
<script lang="ts">
import { Input } from '$lib/components/ui/input';
export let value: string | number;
</script>
<div>
<Input
type="text"
step={1}
bind:value
on:input
on:change
class="w-[22px] {$$props.class ?? ''}"
{...$$restProps}
/>
</div>
<style lang="postcss">
div :global(input) {
@apply px-0.5;
@apply text-right;
@apply border-none;
@apply focus-visible:ring-0;
@apply focus-visible:ring-offset-0;
}
</style>

View File

@@ -0,0 +1,125 @@
<script lang="ts">
import TimeComponentInput from './TimeComponentInput.svelte';
export let showHours = true;
export let value: number | undefined = undefined;
export let disabled: boolean = false;
let hours: string | number = '--';
let minutes: string | number = '--';
let seconds: string | number = '--';
function maybeParseInt(value: string | number): number {
if (value === '--') {
return 0;
}
return typeof value === 'string' ? parseInt(value) : value;
}
function computeValue() {
return maybeParseInt(hours) * 3600 + maybeParseInt(minutes) * 60 + maybeParseInt(seconds);
}
function updateValue() {
value = computeValue();
}
$: hours, minutes, seconds, updateValue();
$: if (value === undefined) {
hours = '--';
minutes = '--';
seconds = '--';
} else if (value !== computeValue()) {
let rounded = Math.round(value);
if (showHours) {
hours = Math.floor(rounded / 3600);
minutes = Math.floor((rounded % 3600) / 60)
.toString()
.padStart(2, '0');
} else {
minutes = Math.floor(rounded / 60).toString();
}
seconds = (rounded % 60).toString().padStart(2, '0');
}
</script>
<div
class="flex flex-row items-center w-fit border rounded-md px-3 {disabled
? 'opacity-50 cursor-not-allowed'
: ''}"
>
{#if showHours}
<TimeComponentInput
bind:value={hours}
{disabled}
class="w-[30px]"
on:input={() => {
if (typeof hours === 'string') {
hours = parseInt(hours);
}
if (hours >= 0) {
} else if (hours < 0) {
hours = 0;
} else {
hours = 0;
}
}}
on:change
/>
<span class="text-sm">:</span>
{/if}
<TimeComponentInput
bind:value={minutes}
{disabled}
on:input={() => {
if (typeof minutes === 'string') {
minutes = parseInt(minutes);
}
if (minutes >= 0 && (minutes <= 59 || !showHours)) {
} else if (minutes < 0) {
minutes = 0;
} else if (showHours && minutes > 59) {
minutes = 59;
} else {
minutes = 0;
}
minutes = minutes.toString().padStart(showHours ? 2 : 1, '0');
}}
on:change
/>
<span class="text-sm">:</span>
<TimeComponentInput
bind:value={seconds}
{disabled}
on:input={() => {
if (typeof seconds === 'string') {
seconds = parseInt(seconds);
}
if (seconds >= 0 && seconds <= 59) {
} else if (seconds < 0) {
seconds = 0;
} else if (seconds > 59) {
seconds = 59;
} else {
seconds = 0;
}
seconds = seconds.toString().padStart(2, '0');
}}
on:change
/>
</div>
<style>
div :global(input::-webkit-outer-spin-button) {
-webkit-appearance: none;
margin: 0;
}
div :global(input::-webkit-inner-spin-button) {
-webkit-appearance: none;
margin: 0;
}
div :global(input[type='number']) {
-moz-appearance: textfield;
}
</style>