@intinyagroup/calendar 0.1.1-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Calendar.svelte +102 -0
- package/dist/Calendar.svelte.d.ts +16 -0
- package/dist/DatePicker.svelte +73 -0
- package/dist/DatePicker.svelte.d.ts +11 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/package.json +42 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ChevronLeft, ChevronRight } from 'lucide-svelte';
|
|
3
|
+
import { Button } from '@intinyagroup/ui';
|
|
4
|
+
import { cn } from '@intinyagroup/ui/utils';
|
|
5
|
+
|
|
6
|
+
export type CalendarDate = { year: number; month: number; day: number };
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
value = $bindable(null),
|
|
10
|
+
min = null,
|
|
11
|
+
max = null,
|
|
12
|
+
disabled = false,
|
|
13
|
+
class: className,
|
|
14
|
+
onSelect,
|
|
15
|
+
}: {
|
|
16
|
+
value?: CalendarDate | null;
|
|
17
|
+
min?: CalendarDate | null;
|
|
18
|
+
max?: CalendarDate | null;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
class?: string;
|
|
21
|
+
onSelect?: (date: CalendarDate) => void;
|
|
22
|
+
} = $props();
|
|
23
|
+
|
|
24
|
+
const today = new Date();
|
|
25
|
+
let viewYear = $state(value?.year ?? today.getFullYear());
|
|
26
|
+
let viewMonth = $state(value?.month ?? today.getMonth());
|
|
27
|
+
|
|
28
|
+
const monthNames = ['January','February','March','April','May','June','July','August','September','October','November','December'];
|
|
29
|
+
const dayNames = ['Su','Mo','Tu','We','Th','Fr','Sa'];
|
|
30
|
+
const daysInMonth = $derived(new Date(viewYear, viewMonth + 1, 0).getDate());
|
|
31
|
+
const firstDayOfMonth = $derived(new Date(viewYear, viewMonth, 1).getDay());
|
|
32
|
+
|
|
33
|
+
const calendarDays = $derived.by(() => {
|
|
34
|
+
const days: { day: number; currentMonth: boolean; date: CalendarDate }[] = [];
|
|
35
|
+
const prevMonthDays = new Date(viewYear, viewMonth, 0).getDate();
|
|
36
|
+
for (let i = firstDayOfMonth - 1; i >= 0; i--) {
|
|
37
|
+
const day = prevMonthDays - i;
|
|
38
|
+
const month = viewMonth === 0 ? 11 : viewMonth - 1;
|
|
39
|
+
const year = viewMonth === 0 ? viewYear - 1 : viewYear;
|
|
40
|
+
days.push({ day, currentMonth: false, date: { year, month, day } });
|
|
41
|
+
}
|
|
42
|
+
for (let day = 1; day <= daysInMonth; day++) {
|
|
43
|
+
days.push({ day, currentMonth: true, date: { year: viewYear, month: viewMonth, day } });
|
|
44
|
+
}
|
|
45
|
+
const remaining = 42 - days.length;
|
|
46
|
+
for (let day = 1; day <= remaining; day++) {
|
|
47
|
+
const month = viewMonth === 11 ? 0 : viewMonth + 1;
|
|
48
|
+
const year = viewMonth === 11 ? viewYear + 1 : viewYear;
|
|
49
|
+
days.push({ day, currentMonth: false, date: { year, month, day } });
|
|
50
|
+
}
|
|
51
|
+
return days;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
function isSameDate(a: CalendarDate | null, b: CalendarDate) {
|
|
55
|
+
return a?.year === b.year && a?.month === b.month && a?.day === b.day;
|
|
56
|
+
}
|
|
57
|
+
function isToday(date: CalendarDate) {
|
|
58
|
+
return date.year === today.getFullYear() && date.month === today.getMonth() && date.day === today.getDate();
|
|
59
|
+
}
|
|
60
|
+
function isDisabled(date: CalendarDate) {
|
|
61
|
+
if (min && (date.year < min.year || (date.year === min.year && date.month < min.month) || (date.year === min.year && date.month === min.month && date.day < min.day))) return true;
|
|
62
|
+
if (max && (date.year > max.year || (date.year === max.year && date.month > max.month) || (date.year === max.year && date.month === max.month && date.day > max.day))) return true;
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
function prevMonth() { if (viewMonth === 0) { viewMonth = 11; viewYear--; } else { viewMonth--; } }
|
|
66
|
+
function nextMonth() { if (viewMonth === 11) { viewMonth = 0; viewYear++; } else { viewMonth++; } }
|
|
67
|
+
function selectDate(date: CalendarDate) { if (disabled || isDisabled(date)) return; value = date; onSelect?.(date); }
|
|
68
|
+
function goToday() { viewYear = today.getFullYear(); viewMonth = today.getMonth(); selectDate({ year: today.getFullYear(), month: today.getMonth(), day: today.getDate() }); }
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<div class={cn('inline-flex flex-col rounded-xl border border-[var(--ui-border)] bg-[var(--ui-card)] p-4 shadow-sm', className)}>
|
|
72
|
+
<div class="flex items-center justify-between mb-4">
|
|
73
|
+
<Button variant="ghost" size="sm" onclick={prevMonth} {disabled}><ChevronLeft class="size-4" /></Button>
|
|
74
|
+
<div class="flex items-center gap-2">
|
|
75
|
+
<span class="text-sm font-semibold text-[var(--ui-foreground)]">{monthNames[viewMonth]} {viewYear}</span>
|
|
76
|
+
<Button variant="ghost" size="sm" onclick={goToday} {disabled} class="text-xs h-6 px-2">Today</Button>
|
|
77
|
+
</div>
|
|
78
|
+
<Button variant="ghost" size="sm" onclick={nextMonth} {disabled}><ChevronRight class="size-4" /></Button>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div class="grid grid-cols-7 gap-1 mb-2">
|
|
82
|
+
{#each dayNames as d}<div class="text-center text-[11px] font-medium text-[var(--ui-muted-foreground)] py-1">{d}</div>{/each}
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="grid grid-cols-7 gap-1">
|
|
86
|
+
{#each calendarDays as { day, currentMonth, date } (date.year + '-' + date.month + '-' + date.day)}
|
|
87
|
+
{@const isSelected = isSameDate(value, date)}
|
|
88
|
+
{@const isTodayDate = isToday(date)}
|
|
89
|
+
{@const isDisabledDate = isDisabled(date)}
|
|
90
|
+
<button
|
|
91
|
+
onclick={() => selectDate(date)}
|
|
92
|
+
disabled={disabled || isDisabledDate}
|
|
93
|
+
class="flex items-center justify-center size-9 rounded-lg text-sm transition-colors cursor-pointer
|
|
94
|
+
{isSelected ? 'bg-[var(--ui-primary)] text-[var(--ui-primary-foreground)] font-semibold' : ''}
|
|
95
|
+
{!isSelected && currentMonth ? 'text-[var(--ui-foreground)] hover:bg-[var(--ui-secondary)]' : ''}
|
|
96
|
+
{!isSelected && !currentMonth ? 'text-[var(--ui-muted-foreground)]/40' : ''}
|
|
97
|
+
{isTodayDate && !isSelected ? 'ring-2 ring-[var(--ui-primary)]/30 font-medium' : ''}
|
|
98
|
+
{isDisabledDate || disabled ? 'opacity-40 cursor-not-allowed' : ''}"
|
|
99
|
+
>{day}</button>
|
|
100
|
+
{/each}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type CalendarDate = {
|
|
2
|
+
year: number;
|
|
3
|
+
month: number;
|
|
4
|
+
day: number;
|
|
5
|
+
};
|
|
6
|
+
type $$ComponentProps = {
|
|
7
|
+
value?: CalendarDate | null;
|
|
8
|
+
min?: CalendarDate | null;
|
|
9
|
+
max?: CalendarDate | null;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
class?: string;
|
|
12
|
+
onSelect?: (date: CalendarDate) => void;
|
|
13
|
+
};
|
|
14
|
+
declare const Calendar: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
15
|
+
type Calendar = ReturnType<typeof Calendar>;
|
|
16
|
+
export default Calendar;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as Popover from '@intinyagroup/ui';
|
|
3
|
+
import { Button, Input } from '@intinyagroup/ui';
|
|
4
|
+
import { Calendar, type CalendarDate } from '@intinyagroup/calendar';
|
|
5
|
+
import { Calendar as CalendarIcon, X } from 'lucide-svelte';
|
|
6
|
+
import { cn } from '@intinyagroup/grid-core/utils';
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
value = $bindable(null),
|
|
10
|
+
placeholder = 'Select date...',
|
|
11
|
+
disabled = false,
|
|
12
|
+
class: className,
|
|
13
|
+
onSelect,
|
|
14
|
+
}: {
|
|
15
|
+
value?: CalendarDate | null;
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
class?: string;
|
|
19
|
+
onSelect?: (date: CalendarDate) => void;
|
|
20
|
+
} = $props();
|
|
21
|
+
|
|
22
|
+
let open = $state(false);
|
|
23
|
+
|
|
24
|
+
function formatDateDisplay(date: CalendarDate | null): string {
|
|
25
|
+
if (!date) return '';
|
|
26
|
+
const d = new Date(date.year, date.month, date.day);
|
|
27
|
+
return d.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function handleSelect(date: CalendarDate) {
|
|
31
|
+
value = date;
|
|
32
|
+
onSelect?.(date);
|
|
33
|
+
open = false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function clear() {
|
|
37
|
+
value = null;
|
|
38
|
+
onSelect?.(null as any);
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<Popover.Root bind:open>
|
|
43
|
+
<Popover.Trigger>
|
|
44
|
+
{#snippet child({ props })}
|
|
45
|
+
<button
|
|
46
|
+
{...props}
|
|
47
|
+
class={cn(
|
|
48
|
+
'flex h-10 w-full items-center justify-between rounded-lg border border-[var(--ui-input)] bg-[var(--ui-background)] px-3 py-2 text-sm text-[var(--ui-foreground)] transition-colors cursor-pointer',
|
|
49
|
+
'hover:border-[var(--ui-primary)]/50 focus:border-[var(--ui-primary)] focus:ring-2 focus:ring-[var(--ui-ring)]/20',
|
|
50
|
+
disabled && 'opacity-50 cursor-not-allowed',
|
|
51
|
+
className
|
|
52
|
+
)}
|
|
53
|
+
{disabled}
|
|
54
|
+
>
|
|
55
|
+
<span class={cn(!value && 'text-[var(--ui-muted-foreground)]')}>
|
|
56
|
+
{value ? formatDateDisplay(value) : placeholder}
|
|
57
|
+
</span>
|
|
58
|
+
<CalendarIcon class="size-4 text-[var(--ui-muted-foreground)]" />
|
|
59
|
+
</button>
|
|
60
|
+
{/snippet}
|
|
61
|
+
</Popover.Trigger>
|
|
62
|
+
|
|
63
|
+
<Popover.Content align="start" class="w-auto p-0">
|
|
64
|
+
<Calendar {value} onSelect={handleSelect} />
|
|
65
|
+
{#if value}
|
|
66
|
+
<div class="flex justify-end p-2 border-t border-[var(--ui-border)]">
|
|
67
|
+
<Button variant="ghost" size="sm" onclick={clear}>
|
|
68
|
+
<X class="size-3.5 mr-1" /> Clear
|
|
69
|
+
</Button>
|
|
70
|
+
</div>
|
|
71
|
+
{/if}
|
|
72
|
+
</Popover.Content>
|
|
73
|
+
</Popover.Root>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type CalendarDate } from '@intinyagroup/calendar';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
value?: CalendarDate | null;
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
class?: string;
|
|
7
|
+
onSelect?: (date: CalendarDate) => void;
|
|
8
|
+
};
|
|
9
|
+
declare const DatePicker: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
10
|
+
type DatePicker = ReturnType<typeof DatePicker>;
|
|
11
|
+
export default DatePicker;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@intinyagroup/calendar",
|
|
3
|
+
"version": "0.1.1-alpha.5",
|
|
4
|
+
"description": "Calendar and DatePicker components for intinyagroup UI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"svelte": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "svelte-package -o dist",
|
|
18
|
+
"check": "echo 'no check'",
|
|
19
|
+
"dev": "svelte-package -o dist -w"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@intinyagroup/ui": "0.1.1-alpha.5"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@sveltejs/package": "^2.5.8",
|
|
26
|
+
"svelte": "^5.55.2",
|
|
27
|
+
"typescript": "^6.0.2",
|
|
28
|
+
"@sveltejs/vite-plugin-svelte": "^5.1.1"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"svelte": "^5.0.0"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/intinyagroup/ui.git",
|
|
37
|
+
"directory": "packages/calendar"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|