@intinyagroup/calendar 0.1.1-alpha.11 → 0.1.1-alpha.12

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.
@@ -1,9 +1,9 @@
1
1
  <script lang="ts">
2
2
  import * as Popover from '@intinyagroup/ui';
3
3
  import { Button, Input } from '@intinyagroup/ui';
4
- import { Calendar, type CalendarDate } from '@intinyagroup/calendar';
4
+ import Calendar, { type CalendarDate } from './Calendar.svelte';
5
5
  import { Calendar as CalendarIcon, X } from 'lucide-svelte';
6
- import { cn } from '@intinyagroup/grid-core/utils';
6
+ import { cn } from '@intinyagroup/ui/utils';
7
7
 
8
8
  let {
9
9
  value = $bindable(null),
@@ -1,4 +1,4 @@
1
- import { type CalendarDate } from '@intinyagroup/calendar';
1
+ import { type CalendarDate } from './Calendar.svelte';
2
2
  type $$ComponentProps = {
3
3
  value?: CalendarDate | null;
4
4
  placeholder?: string;
@@ -186,14 +186,23 @@
186
186
  </script>
187
187
 
188
188
  <div class={cn('relative inline-block w-full max-w-sm', className)}>
189
- <button
190
- type="button"
191
- {disabled}
192
- onclick={() => (isOpen = !isOpen)}
189
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
190
+ <div
191
+ role="button"
192
+ tabindex={disabled ? -1 : 0}
193
+ aria-disabled={disabled}
194
+ onclick={() => { if (!disabled) isOpen = !isOpen; }}
195
+ onkeydown={(e) => {
196
+ if (disabled) return;
197
+ if (e.key === 'Enter' || e.key === ' ') {
198
+ e.preventDefault();
199
+ isOpen = !isOpen;
200
+ }
201
+ }}
193
202
  class={cn(
194
- 'flex h-10 w-full items-center justify-between rounded-lg border border-[var(--ui-input)] bg-[var(--ui-background)] px-3 text-sm transition-colors text-left',
203
+ 'flex h-10 w-full items-center justify-between rounded-lg border border-[var(--ui-input)] bg-[var(--ui-background)] px-3 text-sm transition-colors text-left cursor-pointer select-none',
195
204
  'hover:border-[var(--ui-border)] focus:outline-none focus:ring-2 focus:ring-[var(--ui-ring)]/20 focus:border-[var(--ui-primary)]',
196
- disabled && 'opacity-50 cursor-not-allowed',
205
+ disabled && 'opacity-50 cursor-not-allowed pointer-events-none',
197
206
  value.start ? 'text-[var(--ui-foreground)]' : 'text-[var(--ui-muted-foreground)]'
198
207
  )}
199
208
  >
@@ -205,14 +214,17 @@
205
214
  {#if value.start && !disabled}
206
215
  <button
207
216
  type="button"
208
- onclick={clearRange}
217
+ onclick={(e) => {
218
+ e.stopPropagation();
219
+ clearRange();
220
+ }}
209
221
  class="p-1 rounded-full hover:bg-[var(--ui-secondary)] text-[var(--ui-muted-foreground)] hover:text-[var(--ui-foreground)]"
210
222
  aria-label="Clear date range"
211
223
  >
212
224
  <X class="size-3.5" />
213
225
  </button>
214
226
  {/if}
215
- </button>
227
+ </div>
216
228
 
217
229
  {#if isOpen}
218
230
  <!-- Backdrop for click outside -->
@@ -44,6 +44,7 @@
44
44
  onEventClick,
45
45
  onDateClick,
46
46
  onAddEvent,
47
+ onEventReschedule,
47
48
  }: {
48
49
  events?: CalendarEvent[];
49
50
  view?: CalendarView;
@@ -56,8 +57,28 @@
56
57
  onEventClick?: (event: CalendarEvent) => void;
57
58
  onDateClick?: (date: Date) => void;
58
59
  onAddEvent?: (newEvent: CalendarEvent) => void;
60
+ onEventReschedule?: (detail: { event: CalendarEvent; newStart: Date; newEnd: Date }) => void;
59
61
  } = $props();
60
62
 
63
+ let draggedEventId = $state<string | null>(null);
64
+
65
+ function handleEventDrop(targetDate: Date, targetHour: number) {
66
+ if (!draggedEventId) return;
67
+ const ev = events.find((e) => e.id === draggedEventId);
68
+ if (!ev) return;
69
+
70
+ const origDurationMs = new Date(ev.end).getTime() - new Date(ev.start).getTime();
71
+ const newStart = new Date(targetDate);
72
+ newStart.setHours(targetHour, 0, 0, 0);
73
+ const newEnd = new Date(newStart.getTime() + origDurationMs);
74
+
75
+ events = events.map((e) =>
76
+ e.id === draggedEventId ? { ...e, start: newStart, end: newEnd } : e
77
+ );
78
+
79
+ onEventReschedule?.({ event: ev, newStart, newEnd });
80
+ draggedEventId = null;
81
+ }
61
82
  const activeDateFnsLocale = $derived(locale.startsWith('id') ? localeId : localeEn);
62
83
  const weekStartsOn = $derived(firstDayOfWeek as 0 | 1 | 2 | 3 | 4 | 5 | 6);
63
84
 
@@ -378,8 +399,17 @@
378
399
  onclick={() => openCreateModal(d)}
379
400
  class="col-span-1 divide-y divide-[var(--ui-border)]/50 relative hover:bg-[var(--ui-secondary)]/10 cursor-pointer"
380
401
  >
381
- {#each hours as _}
382
- <div class="h-14"></div>
402
+ {#each hours as hour}
403
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
404
+ <div
405
+ class="h-14 transition-colors hover:bg-[var(--ui-primary)]/10"
406
+ ondragover={(e) => e.preventDefault()}
407
+ ondrop={(e) => {
408
+ e.preventDefault();
409
+ e.stopPropagation();
410
+ handleEventDrop(d, hour);
411
+ }}
412
+ ></div>
383
413
  {/each}
384
414
 
385
415
  <!-- Google Calendar style Current Time Red Line -->
@@ -399,11 +429,16 @@
399
429
  {@const topPos = (evTime.hours + evTime.minutes / 60) * 56}
400
430
  <button
401
431
  type="button"
432
+ draggable="true"
433
+ ondragstart={(e) => {
434
+ draggedEventId = ev.id;
435
+ e.dataTransfer?.setData('text/plain', ev.id);
436
+ }}
402
437
  onclick={(e) => {
403
438
  e.stopPropagation();
404
439
  onEventClick?.(ev);
405
440
  }}
406
- class="absolute inset-x-1 rounded p-1 text-left text-[11px] text-white shadow-xs transition-opacity hover:opacity-90 overflow-hidden z-10"
441
+ class="absolute inset-x-1 rounded p-1 text-left text-[11px] text-white shadow-xs transition-opacity hover:opacity-90 overflow-hidden z-10 cursor-grab active:cursor-grabbing {draggedEventId === ev.id ? 'opacity-40 ring-2 ring-white' : ''}"
407
442
  style="top: {topPos}px; height: 50px; background-color: {ev.color || 'var(--ui-primary)'};"
408
443
  >
409
444
  <div class="font-bold truncate">{ev.title}</div>
@@ -432,11 +467,6 @@
432
467
  {/each}
433
468
  </div>
434
469
 
435
- {@const isCurrentDay = isToday(currentDate)}
436
- {@const nowTime = getTimeInZone(new Date())}
437
- {@const nowMinutes = nowTime.hours * 60 + nowTime.minutes}
438
- {@const currentTimeTop = (nowMinutes / 60) * 64}
439
-
440
470
  <!-- svelte-ignore a11y_no_static_element_interactions -->
441
471
  <!-- svelte-ignore a11y_click_events_have_key_events -->
442
472
  <div
@@ -448,7 +478,9 @@
448
478
  {/each}
449
479
 
450
480
  <!-- Google Calendar style Current Time Red Line -->
451
- {#if isCurrentDay}
481
+ {#if isToday(currentDate)}
482
+ {@const nowTime = getTimeInZone(new Date())}
483
+ {@const currentTimeTop = ((nowTime.hours * 60 + nowTime.minutes) / 60) * 64}
452
484
  <div
453
485
  class="absolute inset-x-0 z-30 pointer-events-none flex items-center"
454
486
  style="top: {currentTimeTop}px;"
@@ -500,28 +532,30 @@
500
532
 
501
533
  <div class="space-y-3.5 text-xs">
502
534
  <div>
503
- <label class="block mb-1 font-semibold text-[var(--ui-muted-foreground)]">Title</label>
535
+ <label for="event-cal-title" class="block mb-1 font-semibold text-[var(--ui-muted-foreground)]">Title</label>
504
536
  <input
537
+ id="event-cal-title"
505
538
  type="text"
506
539
  bind:value={newEventTitle}
507
540
  placeholder="Event title (e.g. Sprint Review)..."
508
541
  class="h-9 w-full rounded-lg border border-[var(--ui-input)] bg-[var(--ui-background)] px-3 text-xs outline-none focus:border-[var(--ui-primary)]"
509
- autofocus
510
542
  />
511
543
  </div>
512
544
 
513
545
  <div class="grid grid-cols-2 gap-3">
514
546
  <div>
515
- <label class="block mb-1 font-semibold text-[var(--ui-muted-foreground)]">Start Time</label>
547
+ <label for="event-cal-start-time" class="block mb-1 font-semibold text-[var(--ui-muted-foreground)]">Start Time</label>
516
548
  <input
549
+ id="event-cal-start-time"
517
550
  type="time"
518
551
  bind:value={newEventStartTime}
519
552
  class="h-9 w-full rounded-lg border border-[var(--ui-input)] bg-[var(--ui-background)] px-2.5 text-xs outline-none focus:border-[var(--ui-primary)]"
520
553
  />
521
554
  </div>
522
555
  <div>
523
- <label class="block mb-1 font-semibold text-[var(--ui-muted-foreground)]">End Time</label>
556
+ <label for="event-cal-end-time" class="block mb-1 font-semibold text-[var(--ui-muted-foreground)]">End Time</label>
524
557
  <input
558
+ id="event-cal-end-time"
525
559
  type="time"
526
560
  bind:value={newEventEndTime}
527
561
  class="h-9 w-full rounded-lg border border-[var(--ui-input)] bg-[var(--ui-background)] px-2.5 text-xs outline-none focus:border-[var(--ui-primary)]"
@@ -530,7 +564,7 @@
530
564
  </div>
531
565
 
532
566
  <div>
533
- <label class="block mb-1.5 font-semibold text-[var(--ui-muted-foreground)]">Tag Color</label>
567
+ <span class="block mb-1.5 font-semibold text-[var(--ui-muted-foreground)]">Tag Color</span>
534
568
  <div class="flex items-center gap-2">
535
569
  {#each presetColors as col}
536
570
  <button
@@ -548,11 +582,10 @@
548
582
  </div>
549
583
 
550
584
  <div>
551
- <label class="block mb-1 font-semibold text-[var(--ui-muted-foreground)]">Description (optional)</label>
585
+ <label for="event-cal-desc" class="block mb-1 font-semibold text-[var(--ui-muted-foreground)]">Description (optional)</label>
552
586
  <textarea
587
+ id="event-cal-desc"
553
588
  bind:value={newEventDescription}
554
- rows={2}
555
- placeholder="Notes, agenda, or link..."
556
589
  class="w-full rounded-lg border border-[var(--ui-input)] bg-[var(--ui-background)] p-2.5 text-xs outline-none focus:border-[var(--ui-primary)]"
557
590
  ></textarea>
558
591
  </div>
@@ -20,6 +20,11 @@ type $$ComponentProps = {
20
20
  onEventClick?: (event: CalendarEvent) => void;
21
21
  onDateClick?: (date: Date) => void;
22
22
  onAddEvent?: (newEvent: CalendarEvent) => void;
23
+ onEventReschedule?: (detail: {
24
+ event: CalendarEvent;
25
+ newStart: Date;
26
+ newEnd: Date;
27
+ }) => void;
23
28
  };
24
29
  declare const EventCalendar: import("svelte").Component<$$ComponentProps, {}, "events" | "view" | "currentDate">;
25
30
  type EventCalendar = ReturnType<typeof EventCalendar>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intinyagroup/calendar",
3
- "version": "0.1.1-alpha.11",
3
+ "version": "0.1.1-alpha.12",
4
4
  "description": "Calendar and DatePicker components for intinyagroup UI",
5
5
  "type": "module",
6
6
  "exports": {
@@ -19,8 +19,9 @@
19
19
  "dev": "svelte-package -o dist -w"
20
20
  },
21
21
  "dependencies": {
22
- "@intinyagroup/ui": "0.1.1-alpha.9",
23
- "date-fns": "^4.4.0"
22
+ "@intinyagroup/ui": "0.1.1-alpha.11",
23
+ "date-fns": "^4.4.0",
24
+ "lucide-svelte": "^1.0.1"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@sveltejs/package": "^2.5.8",