@intinyagroup/calendar 0.1.1 → 0.2.0

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.
@@ -170,7 +170,7 @@
170
170
  class="grid grid-cols-7 gap-1 mb-2 text-center"
171
171
  style="display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 0.25rem; margin-bottom: 0.5rem; text-align: center;"
172
172
  >
173
- {#each dayNames as d}
173
+ {#each dayNames as d (d)}
174
174
  <div
175
175
  class="text-center text-[11px] font-medium text-[var(--ui-muted-foreground)] py-1"
176
176
  style="text-align: center; font-size: 0.6875rem; padding: 0.25rem 0;"
@@ -14,31 +14,32 @@
14
14
  import Calendar, { type CalendarDate } from "./Calendar.svelte";
15
15
  import { Calendar as CalendarIcon, X } from "lucide-svelte";
16
16
  import { cn } from "@intinyagroup/ui/utils";
17
+ import { formatCalendarDate } from "./format-date";
17
18
 
18
19
  let {
19
20
  value = $bindable(null),
20
21
  placeholder = "Select date...",
22
+ locale = "en-GB",
23
+ format = "d MMM yyyy",
21
24
  disabled = false,
22
25
  class: className,
23
26
  onSelect,
24
27
  }: {
25
28
  value?: CalendarDate | null;
26
29
  placeholder?: string;
30
+ /** date-fns format pattern used for the selected date. */
31
+ format?: string;
32
+ locale?: string;
27
33
  disabled?: boolean;
28
34
  class?: string;
29
- onSelect?: (date: CalendarDate) => void;
35
+ onSelect?: (date: CalendarDate | null) => void;
30
36
  } = $props();
31
37
 
32
38
  let open = $state(false);
33
39
 
34
40
  function formatDateDisplay(date: CalendarDate | null): string {
35
41
  if (!date) return "";
36
- const d = new Date(date.year, date.month, date.day);
37
- return d.toLocaleDateString("en-GB", {
38
- day: "numeric",
39
- month: "short",
40
- year: "numeric",
41
- });
42
+ return formatCalendarDate(date, format, locale);
42
43
  }
43
44
 
44
45
  function handleSelect(date: CalendarDate) {
@@ -49,7 +50,7 @@
49
50
 
50
51
  function clear() {
51
52
  value = null;
52
- onSelect?.(null as any);
53
+ onSelect?.(null);
53
54
  }
54
55
  </script>
55
56
 
@@ -2,9 +2,12 @@ import { type CalendarDate } from "./Calendar.svelte";
2
2
  type $$ComponentProps = {
3
3
  value?: CalendarDate | null;
4
4
  placeholder?: string;
5
+ /** date-fns format pattern used for the selected date. */
6
+ format?: string;
7
+ locale?: string;
5
8
  disabled?: boolean;
6
9
  class?: string;
7
- onSelect?: (date: CalendarDate) => void;
10
+ onSelect?: (date: CalendarDate | null) => void;
8
11
  };
9
12
  declare const DatePicker: import("svelte").Component<$$ComponentProps, {}, "value">;
10
13
  type DatePicker = ReturnType<typeof DatePicker>;
@@ -21,9 +21,10 @@
21
21
  isSameMonth,
22
22
  isWithinInterval,
23
23
  isBefore,
24
- format,
24
+ format as formatDateFns,
25
25
  } from "date-fns";
26
26
  import { id as localeId, enUS as localeEn } from "date-fns/locale";
27
+ import { formatCalendarDate } from "./format-date";
27
28
 
28
29
  export type CalendarDate = { year: number; month: number; day: number };
29
30
  export type DateRange = {
@@ -43,6 +44,7 @@
43
44
  disabled = false,
44
45
  placeholder = "Select date range...",
45
46
  locale = "en-US",
47
+ format = "dd/MM/yyyy",
46
48
  firstDayOfWeek = 0, // 0 = Sunday, 1 = Monday
47
49
  presets,
48
50
  class: className,
@@ -54,6 +56,8 @@
54
56
  disabled?: boolean;
55
57
  placeholder?: string;
56
58
  locale?: string;
59
+ /** date-fns format pattern used for each endpoint. */
60
+ format?: string;
57
61
  firstDayOfWeek?: number;
58
62
  presets?: DateRangePreset[];
59
63
  class?: string;
@@ -92,7 +96,9 @@
92
96
  const dayNames = $derived.by(() => {
93
97
  const start = startOfWeek(viewDate, { weekStartsOn });
94
98
  return Array.from({ length: 7 }, (_, i) => {
95
- return format(addDays(start, i), "EEE", { locale: activeDateFnsLocale });
99
+ return formatDateFns(addDays(start, i), "EEE", {
100
+ locale: activeDateFnsLocale,
101
+ });
96
102
  });
97
103
  });
98
104
 
@@ -145,9 +151,8 @@
145
151
  }
146
152
 
147
153
  function formatDisplayDate(date: CalendarDate | null): string {
148
- const d = toDate(date);
149
- if (!d) return "";
150
- return format(d, "dd/MM/yyyy", { locale: activeDateFnsLocale });
154
+ if (!date) return "";
155
+ return formatCalendarDate(date, format, locale);
151
156
  }
152
157
 
153
158
  const label = $derived.by(() => {
@@ -210,7 +215,6 @@
210
215
  </script>
211
216
 
212
217
  <div class={cn("relative inline-block w-full max-w-sm", className)}>
213
- <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
214
218
  <div
215
219
  role="button"
216
220
  tabindex={disabled ? -1 : 0}
@@ -276,7 +280,9 @@
276
280
  <span
277
281
  class="text-xs font-semibold text-[var(--ui-foreground)] capitalize"
278
282
  >
279
- {format(viewDate, "MMMM yyyy", { locale: activeDateFnsLocale })}
283
+ {formatDateFns(viewDate, "MMMM yyyy", {
284
+ locale: activeDateFnsLocale,
285
+ })}
280
286
  </span>
281
287
  <Button
282
288
  variant="ghost"
@@ -293,7 +299,7 @@
293
299
  class="grid grid-cols-7 mb-1 text-center"
294
300
  style="display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); text-align: center; margin-bottom: 0.25rem;"
295
301
  >
296
- {#each dayNames as day}
302
+ {#each dayNames as day (day)}
297
303
  <span
298
304
  class="text-[11px] font-semibold text-[var(--ui-muted-foreground)] py-1 capitalize"
299
305
  style="text-align: center; font-size: 0.6875rem; padding: 0.25rem 0;"
@@ -308,7 +314,7 @@
308
314
  class="grid grid-cols-7 gap-y-1"
309
315
  style="display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); row-gap: 0.25rem;"
310
316
  >
311
- {#each calendarDays as { date, calendarDate, currentMonth }}
317
+ {#each calendarDays as { date, calendarDate, currentMonth } (date.toISOString())}
312
318
  {@const isStart = startDateObj
313
319
  ? isSameDay(startDateObj, date)
314
320
  : false}
@@ -373,7 +379,7 @@
373
379
  <div
374
380
  class="flex flex-wrap items-center gap-1.5 border-t border-[var(--ui-border)] mt-3 pt-2.5"
375
381
  >
376
- {#each activePresets as preset}
382
+ {#each activePresets as preset (preset.label)}
377
383
  <button
378
384
  type="button"
379
385
  onclick={() => {
@@ -18,6 +18,8 @@ type $$ComponentProps = {
18
18
  disabled?: boolean;
19
19
  placeholder?: string;
20
20
  locale?: string;
21
+ /** date-fns format pattern used for each endpoint. */
22
+ format?: string;
21
23
  firstDayOfWeek?: number;
22
24
  presets?: DateRangePreset[];
23
25
  class?: string;
@@ -32,6 +32,7 @@
32
32
  type CalendarRecurrence,
33
33
  type CalendarResource,
34
34
  } from "./recurrence";
35
+ import { SvelteDate, SvelteMap } from "svelte/reactivity";
35
36
 
36
37
  export type CalendarView = "month" | "week" | "day" | "resourceTimeline";
37
38
 
@@ -153,7 +154,7 @@
153
154
 
154
155
  const origDurationMs =
155
156
  new Date(ev.end).getTime() - new Date(ev.start).getTime();
156
- const newStart = new Date(targetDate);
157
+ const newStart = new SvelteDate(targetDate);
157
158
  if (targetHour !== undefined) {
158
159
  newStart.setHours(targetHour, 0, 0, 0);
159
160
  } else {
@@ -283,14 +284,14 @@
283
284
  const endMinutes = dragCreateEnd.hour * 60 + dragCreateEnd.minute;
284
285
 
285
286
  if (endMinutes - startMinutes >= 15) {
286
- const startDate = new Date(date);
287
+ const startDate = new SvelteDate(date);
287
288
  startDate.setHours(
288
289
  dragCreateStart.hour,
289
290
  dragCreateStart.minute,
290
291
  0,
291
292
  0,
292
293
  );
293
- const endDate = new Date(date);
294
+ const endDate = new SvelteDate(date);
294
295
  endDate.setHours(dragCreateEnd.hour, dragCreateEnd.minute, 0, 0);
295
296
 
296
297
  onDragCreate?.({ start: startDate, end: endDate });
@@ -406,7 +407,7 @@
406
407
  });
407
408
 
408
409
  const columns: CalendarEvent[][] = [];
409
- const layoutMap = new Map<string, { colIndex: number }>();
410
+ const layoutMap = new SvelteMap<string, { colIndex: number }>();
410
411
 
411
412
  for (const ev of sorted) {
412
413
  let placed = false;
@@ -610,10 +611,10 @@
610
611
  const [startH, startM] = newEventStartTime.split(":").map(Number);
611
612
  const [endH, endM] = newEventEndTime.split(":").map(Number);
612
613
 
613
- const startDate = new Date(newEventDate);
614
+ const startDate = new SvelteDate(newEventDate);
614
615
  startDate.setHours(startH || 0, startM || 0, 0, 0);
615
616
 
616
- const endDate = new Date(newEventDate);
617
+ const endDate = new SvelteDate(newEventDate);
617
618
  endDate.setHours(endH || 0, endM || 0, 0, 0);
618
619
 
619
620
  const newEv: CalendarEvent = {
@@ -747,7 +748,7 @@
747
748
  class="grid grid-cols-7 border-b border-[var(--ui-border)] bg-[var(--ui-secondary)]/20 text-center text-xs font-semibold text-[var(--ui-muted-foreground)] py-2"
748
749
  style="display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); text-align: center;"
749
750
  >
750
- {#each dayNames as day}
751
+ {#each dayNames as day (day)}
751
752
  <div class="capitalize">{day}</div>
752
753
  {/each}
753
754
  </div>
@@ -756,7 +757,7 @@
756
757
  class="grid grid-cols-7 divide-x divide-y divide-[var(--ui-border)]"
757
758
  style="display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));"
758
759
  >
759
- {#each monthDays as { date, currentMonth }}
760
+ {#each monthDays as { date, currentMonth } (date.toISOString())}
760
761
  {@const dayEvents = getEventsForDay(date)}
761
762
  {@const todayActive = isToday(date)}
762
763
 
@@ -856,7 +857,7 @@
856
857
  class="grid grid-cols-4 md:grid-cols-8 border-b border-[var(--ui-border)] bg-[var(--ui-secondary)]/20 text-center text-xs font-semibold text-[var(--ui-muted-foreground)] py-2"
857
858
  >
858
859
  <div class="col-span-1">Time</div>
859
- {#each isMobile ? mobileWeekDays : weekDays as d}
860
+ {#each isMobile ? mobileWeekDays : weekDays as d (d.toISOString())}
860
861
  <div class="col-span-1 flex flex-col items-center">
861
862
  <span class="capitalize"
862
863
  >{format(d, "EEE", { locale: activeDateFnsLocale })}</span
@@ -914,13 +915,13 @@
914
915
  <div
915
916
  class="col-span-1 divide-y divide-[var(--ui-border)]/50 text-right pr-2 text-[11px] font-medium text-[var(--ui-muted-foreground)]"
916
917
  >
917
- {#each hours as hour}
918
+ {#each hours as hour (hour)}
918
919
  <div class="h-14 pt-1">{String(hour).padStart(2, "0")}:00</div>
919
920
  {/each}
920
921
  </div>
921
922
 
922
923
  <!-- Day Grid Columns -->
923
- {#each isMobile ? mobileWeekDays : weekDays as d}
924
+ {#each isMobile ? mobileWeekDays : weekDays as d (d.toISOString())}
924
925
  {@const dayEvents = getTimedEventsForDay(d)}
925
926
  {@const layoutEvents = computeOverlappingLayout(dayEvents)}
926
927
  {@const isCurrentDay = isToday(d)}
@@ -935,7 +936,7 @@
935
936
  onclick={() => openCreateModal(d)}
936
937
  class="col-span-1 divide-y divide-[var(--ui-border)]/50 relative hover:bg-[var(--ui-secondary)]/10 cursor-pointer select-none"
937
938
  >
938
- {#each hours as hour}
939
+ {#each hours as hour (hour)}
939
940
  <!-- svelte-ignore a11y_no_static_element_interactions -->
940
941
  <div
941
942
  class="h-14 transition-colors hover:bg-[var(--ui-primary)]/10 pointer-events-auto"
@@ -981,7 +982,6 @@
981
982
  {@const widthPercent = 100 / ev.colTotal}
982
983
  {@const leftPercent = ev.colIndex * widthPercent}
983
984
 
984
- <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
985
985
  <div
986
986
  role="button"
987
987
  tabindex="0"
@@ -1076,7 +1076,7 @@
1076
1076
  <div
1077
1077
  class="col-span-2 divide-y divide-[var(--ui-border)]/50 text-right pr-3 text-xs font-medium text-[var(--ui-muted-foreground)]"
1078
1078
  >
1079
- {#each hours as hour}
1079
+ {#each hours as hour (hour)}
1080
1080
  <div class="h-16 pt-2">{String(hour).padStart(2, "0")}:00</div>
1081
1081
  {/each}
1082
1082
  </div>
@@ -1088,7 +1088,7 @@
1088
1088
  onclick={() => openCreateModal(currentDate)}
1089
1089
  class="col-span-10 divide-y divide-[var(--ui-border)]/50 relative p-1 hover:bg-[var(--ui-secondary)]/10 cursor-pointer select-none"
1090
1090
  >
1091
- {#each hours as hour}
1091
+ {#each hours as hour (hour)}
1092
1092
  <div
1093
1093
  class="h-16 pointer-events-auto"
1094
1094
  onmousedown={(e) =>
@@ -1129,7 +1129,6 @@
1129
1129
  {@const widthPercent = 100 / ev.colTotal}
1130
1130
  {@const leftPercent = ev.colIndex * widthPercent}
1131
1131
 
1132
- <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
1133
1132
  <div
1134
1133
  role="button"
1135
1134
  tabindex="0"
@@ -1222,7 +1221,7 @@
1222
1221
  class="relative grid"
1223
1222
  style="grid-template-columns: repeat({timelineHours.length}, minmax(0, 1fr));"
1224
1223
  >
1225
- {#each timelineHours as hour}
1224
+ {#each timelineHours as hour (hour)}
1226
1225
  <div
1227
1226
  class="border-r border-[var(--ui-border)] px-1 py-2 text-center"
1228
1227
  >
@@ -1262,7 +1261,7 @@
1262
1261
  class="pointer-events-none absolute inset-0 grid"
1263
1262
  style="grid-template-columns: repeat({timelineHours.length}, minmax(0, 1fr));"
1264
1263
  >
1265
- {#each timelineHours as _hour}<div
1264
+ {#each timelineHours as _hour (_hour)}<div
1266
1265
  class="border-r border-[var(--ui-border)]/50"
1267
1266
  ></div>{/each}
1268
1267
  </div>
@@ -1372,7 +1371,7 @@
1372
1371
  >Tag Color</span
1373
1372
  >
1374
1373
  <div class="flex items-center gap-2">
1375
- {#each presetColors as col}
1374
+ {#each presetColors as col (col)}
1376
1375
  <button
1377
1376
  type="button"
1378
1377
  onclick={() => (newEventColor = col)}
@@ -0,0 +1,6 @@
1
+ export type CalendarDateParts = {
2
+ year: number;
3
+ month: number;
4
+ day: number;
5
+ };
6
+ export declare function formatCalendarDate(value: CalendarDateParts, pattern: string, locale: string): string;
@@ -0,0 +1,13 @@
1
+ import { format } from "date-fns";
2
+ import { enGB, enUS, id } from "date-fns/locale";
3
+ function resolveLocale(locale) {
4
+ if (locale.toLowerCase().startsWith("id"))
5
+ return id;
6
+ if (locale.toLowerCase() === "en-gb")
7
+ return enGB;
8
+ return enUS;
9
+ }
10
+ export function formatCalendarDate(value, pattern, locale) {
11
+ const date = new Date(value.year, value.month, value.day);
12
+ return format(date, pattern, { locale: resolveLocale(locale) });
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { formatCalendarDate } from "./format-date";
3
+ const sampleDate = { year: 2026, month: 10, day: 7 };
4
+ describe("formatCalendarDate", () => {
5
+ it("supports day-first and month-first numeric formats", () => {
6
+ expect(formatCalendarDate(sampleDate, "dd/MM/yyyy", "id-ID")).toBe("07/11/2026");
7
+ expect(formatCalendarDate(sampleDate, "MM/dd/yyyy", "en-US")).toBe("11/07/2026");
8
+ });
9
+ it("formats month names using the requested locale", () => {
10
+ expect(formatCalendarDate(sampleDate, "d MMMM yyyy", "id-ID")).toBe("7 November 2026");
11
+ expect(formatCalendarDate(sampleDate, "d MMM yyyy", "en-GB")).toBe("7 Nov 2026");
12
+ });
13
+ });
@@ -1,129 +1,153 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { render, screen, fireEvent } from '@testing-library/svelte';
3
- import EventCalendar from './EventCalendar.svelte';
4
- describe('EventCalendar', () => {
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { render, screen, fireEvent } from "@testing-library/svelte";
3
+ import EventCalendar from "./EventCalendar.svelte";
4
+ describe("EventCalendar", () => {
5
5
  beforeEach(() => {
6
6
  vi.useFakeTimers();
7
- vi.setSystemTime(new Date('2025-06-15T12:00:00'));
7
+ vi.setSystemTime(new Date("2025-06-15T12:00:00"));
8
8
  });
9
9
  afterEach(() => {
10
10
  vi.useRealTimers();
11
11
  });
12
- it('renders the calendar component with correct month title', () => {
12
+ it("renders the calendar component with correct month title", () => {
13
13
  render(EventCalendar);
14
14
  // Title uses full month name: "MMMM yyyy"
15
- expect(screen.getByText('June 2025')).toBeInTheDocument();
15
+ expect(screen.getByText("June 2025")).toBeInTheDocument();
16
16
  });
17
- it('displays day-of-week headers', () => {
17
+ it("displays day-of-week headers", () => {
18
18
  render(EventCalendar);
19
- expect(screen.getByText('Sun')).toBeInTheDocument();
20
- expect(screen.getByText('Mon')).toBeInTheDocument();
21
- expect(screen.getByText('Tue')).toBeInTheDocument();
22
- expect(screen.getByText('Wed')).toBeInTheDocument();
23
- expect(screen.getByText('Thu')).toBeInTheDocument();
24
- expect(screen.getByText('Fri')).toBeInTheDocument();
25
- expect(screen.getByText('Sat')).toBeInTheDocument();
19
+ expect(screen.getByText("Sun")).toBeInTheDocument();
20
+ expect(screen.getByText("Mon")).toBeInTheDocument();
21
+ expect(screen.getByText("Tue")).toBeInTheDocument();
22
+ expect(screen.getByText("Wed")).toBeInTheDocument();
23
+ expect(screen.getByText("Thu")).toBeInTheDocument();
24
+ expect(screen.getByText("Fri")).toBeInTheDocument();
25
+ expect(screen.getByText("Sat")).toBeInTheDocument();
26
26
  });
27
- it('renders month view with day grid', () => {
27
+ it("renders month view with day grid", () => {
28
28
  render(EventCalendar);
29
29
  // Should show days of June 2025. Use getAllByText for numbers that appear
30
30
  // multiple times in the month grid (e.g. from previous/next month overflow).
31
- expect(screen.getAllByText('15').length).toBeGreaterThanOrEqual(1);
32
- expect(screen.getAllByText('30').length).toBeGreaterThanOrEqual(1);
33
- expect(screen.getAllByText('1').length).toBeGreaterThanOrEqual(1);
31
+ expect(screen.getAllByText("15").length).toBeGreaterThanOrEqual(1);
32
+ expect(screen.getAllByText("30").length).toBeGreaterThanOrEqual(1);
33
+ expect(screen.getAllByText("1").length).toBeGreaterThanOrEqual(1);
34
34
  });
35
- it('renders events in the calendar', () => {
35
+ it("renders events in the calendar", () => {
36
36
  const events = [
37
37
  {
38
- id: '1',
39
- title: 'Team Meeting',
40
- start: new Date('2025-06-15T09:00:00'),
41
- end: new Date('2025-06-15T10:00:00'),
38
+ id: "1",
39
+ title: "Team Meeting",
40
+ start: new Date("2025-06-15T09:00:00"),
41
+ end: new Date("2025-06-15T10:00:00"),
42
42
  },
43
43
  ];
44
44
  render(EventCalendar, { props: { events } });
45
- expect(screen.getByText('Team Meeting')).toBeInTheDocument();
45
+ expect(screen.getByText("Team Meeting")).toBeInTheDocument();
46
46
  });
47
- it('renders multiple events on the same day', () => {
47
+ it("renders multiple events on the same day", () => {
48
48
  const events = [
49
49
  {
50
- id: '1',
51
- title: 'Morning Standup',
52
- start: new Date('2025-06-15T09:00:00'),
53
- end: new Date('2025-06-15T09:30:00'),
50
+ id: "1",
51
+ title: "Morning Standup",
52
+ start: new Date("2025-06-15T09:00:00"),
53
+ end: new Date("2025-06-15T09:30:00"),
54
54
  },
55
55
  {
56
- id: '2',
57
- title: 'Lunch Break',
58
- start: new Date('2025-06-15T12:00:00'),
59
- end: new Date('2025-06-15T13:00:00'),
56
+ id: "2",
57
+ title: "Lunch Break",
58
+ start: new Date("2025-06-15T12:00:00"),
59
+ end: new Date("2025-06-15T13:00:00"),
60
60
  },
61
61
  ];
62
62
  render(EventCalendar, { props: { events } });
63
- expect(screen.getByText('Morning Standup')).toBeInTheDocument();
64
- expect(screen.getByText('Lunch Break')).toBeInTheDocument();
63
+ expect(screen.getByText("Morning Standup")).toBeInTheDocument();
64
+ expect(screen.getByText("Lunch Break")).toBeInTheDocument();
65
65
  });
66
- it('navigates to the previous month', async () => {
66
+ it("navigates to the previous month", async () => {
67
67
  render(EventCalendar);
68
68
  // Prev button is a Button with ChevronLeft icon; find by role
69
- const prevBtn = screen.getAllByRole('button')[0];
69
+ const prevBtn = screen.getAllByRole("button")[0];
70
70
  await fireEvent.click(prevBtn);
71
- expect(screen.getByText('May 2025')).toBeInTheDocument();
71
+ expect(screen.getByText("May 2025")).toBeInTheDocument();
72
72
  });
73
- it('navigates to the next month', async () => {
73
+ it("navigates to the next month", async () => {
74
74
  render(EventCalendar);
75
75
  // Next button is the third button (prev=0, today=1, next=2)
76
- const nextBtn = screen.getAllByRole('button')[2];
76
+ const nextBtn = screen.getAllByRole("button")[2];
77
77
  await fireEvent.click(nextBtn);
78
- expect(screen.getByText('July 2025')).toBeInTheDocument();
78
+ expect(screen.getByText("July 2025")).toBeInTheDocument();
79
79
  });
80
- it('navigates back to today', async () => {
80
+ it("navigates back to today", async () => {
81
81
  render(EventCalendar);
82
82
  // Navigate to next month first
83
- const nextBtn = screen.getAllByRole('button')[2];
83
+ const nextBtn = screen.getAllByRole("button")[2];
84
84
  await fireEvent.click(nextBtn);
85
- expect(screen.getByText('July 2025')).toBeInTheDocument();
85
+ expect(screen.getByText("July 2025")).toBeInTheDocument();
86
86
  // Click Today button (text content is "Today")
87
- const todayBtn = screen.getByText('Today');
87
+ const todayBtn = screen.getByText("Today");
88
88
  await fireEvent.click(todayBtn);
89
- expect(screen.getByText('June 2025')).toBeInTheDocument();
89
+ expect(screen.getByText("June 2025")).toBeInTheDocument();
90
90
  });
91
- it('displays view switcher buttons', () => {
91
+ it("displays view switcher buttons", () => {
92
92
  render(EventCalendar);
93
- expect(screen.getByText('Month')).toBeInTheDocument();
94
- expect(screen.getByText('Week')).toBeInTheDocument();
95
- expect(screen.getByText('Day')).toBeInTheDocument();
93
+ expect(screen.getByText("Month")).toBeInTheDocument();
94
+ expect(screen.getByText("Week")).toBeInTheDocument();
95
+ expect(screen.getByText("Day")).toBeInTheDocument();
96
96
  });
97
- it('shows event with color indicator', () => {
97
+ it("shows event with color indicator", () => {
98
98
  const events = [
99
99
  {
100
- id: '1',
101
- title: 'Important Call',
102
- start: new Date('2025-06-15T14:00:00'),
103
- end: new Date('2025-06-15T15:00:00'),
104
- color: '#dc2626',
100
+ id: "1",
101
+ title: "Important Call",
102
+ start: new Date("2025-06-15T14:00:00"),
103
+ end: new Date("2025-06-15T15:00:00"),
104
+ color: "#dc2626",
105
105
  },
106
106
  ];
107
107
  render(EventCalendar, { props: { events } });
108
- expect(screen.getByText('Important Call')).toBeInTheDocument();
108
+ expect(screen.getByText("Important Call")).toBeInTheDocument();
109
109
  });
110
- it('renders empty state without crashing', () => {
110
+ it("renders empty state without crashing", () => {
111
111
  render(EventCalendar, { props: { events: [] } });
112
- expect(screen.getByText('June 2025')).toBeInTheDocument();
112
+ expect(screen.getByText("June 2025")).toBeInTheDocument();
113
113
  });
114
- it('renders all-day events (may appear in both grid and all-day header)', () => {
114
+ it("renders all-day events (may appear in both grid and all-day header)", () => {
115
115
  const events = [
116
116
  {
117
- id: '1',
118
- title: 'Company Holiday',
119
- start: new Date('2025-06-15T00:00:00'),
120
- end: new Date('2025-06-16T00:00:00'),
117
+ id: "1",
118
+ title: "Company Holiday",
119
+ start: new Date("2025-06-15T00:00:00"),
120
+ end: new Date("2025-06-16T00:00:00"),
121
121
  allDay: true,
122
122
  },
123
123
  ];
124
124
  render(EventCalendar, { props: { events } });
125
125
  // All-day events appear in both the month grid and the all-day section
126
- const matches = screen.getAllByText('Company Holiday');
126
+ const matches = screen.getAllByText("Company Holiday");
127
127
  expect(matches.length).toBeGreaterThanOrEqual(1);
128
128
  });
129
+ it("renders resource timeline rows and assigned events", () => {
130
+ const events = [
131
+ {
132
+ id: "resource-event",
133
+ title: "Room Booking",
134
+ start: new Date("2025-06-15T09:00:00"),
135
+ end: new Date("2025-06-15T10:00:00"),
136
+ resourceId: "room-a",
137
+ },
138
+ ];
139
+ render(EventCalendar, {
140
+ props: {
141
+ view: "resourceTimeline",
142
+ events,
143
+ resources: [{ id: "room-a", label: "Room A" }],
144
+ resourceTimelineStartHour: 8,
145
+ resourceTimelineEndHour: 18,
146
+ },
147
+ });
148
+ expect(screen.getByText("Room A")).toBeInTheDocument();
149
+ expect(screen.getByText("Room Booking")).toBeInTheDocument();
150
+ expect(screen.getByText("08:00")).toBeInTheDocument();
151
+ expect(screen.getByText("17:00")).toBeInTheDocument();
152
+ });
129
153
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intinyagroup/calendar",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Calendar and DatePicker components for intinyagroup UI",
5
5
  "type": "module",
6
6
  "exports": {
@@ -22,7 +22,7 @@
22
22
  "test": "vitest run"
23
23
  },
24
24
  "dependencies": {
25
- "@intinyagroup/ui": "0.2.0",
25
+ "@intinyagroup/ui": "0.2.10",
26
26
  "date-fns": "^4.4.0",
27
27
  "lucide-svelte": "^1.0.1"
28
28
  },
@@ -30,7 +30,8 @@
30
30
  "@sveltejs/package": "^2.5.8",
31
31
  "@sveltejs/vite-plugin-svelte": "^5.1.1",
32
32
  "svelte": "^5.55.2",
33
- "typescript": "^6.0.2"
33
+ "typescript": "^6.0.2",
34
+ "@intinyagroup/vitest-preset": "0.1.0"
34
35
  },
35
36
  "peerDependencies": {
36
37
  "svelte": "^5.0.0"