@teja-app/ui 0.0.15 → 0.0.17

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.
@@ -12438,7 +12438,7 @@ const MONTHS_SHORT = [
12438
12438
  "Nov",
12439
12439
  "Dec"
12440
12440
  ];
12441
- const pad2 = (n2) => String(n2).padStart(2, "0");
12441
+ const pad2$1 = (n2) => String(n2).padStart(2, "0");
12442
12442
  function parseDate(value) {
12443
12443
  if (!value) return void 0;
12444
12444
  const [y3, m2, d2] = value.split("-").map(Number);
@@ -12450,13 +12450,13 @@ function parseDate(value) {
12450
12450
  return date;
12451
12451
  }
12452
12452
  function formatDate(date) {
12453
- return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
12453
+ return `${date.getFullYear()}-${pad2$1(date.getMonth() + 1)}-${pad2$1(date.getDate())}`;
12454
12454
  }
12455
12455
  function isValidDateString(value) {
12456
12456
  return /^\d{4}-\d{2}-\d{2}$/.test(value) && parseDate(value) !== void 0;
12457
12457
  }
12458
12458
  function displayDate(date) {
12459
- return `${MONTHS_SHORT[date.getMonth()]} ${pad2(date.getDate())}, ${date.getFullYear()}`;
12459
+ return `${MONTHS_SHORT[date.getMonth()]} ${pad2$1(date.getDate())}, ${date.getFullYear()}`;
12460
12460
  }
12461
12461
  function startOfDay(date) {
12462
12462
  return new Date(date.getFullYear(), date.getMonth(), date.getDate());
@@ -12513,6 +12513,8 @@ function DateInput({
12513
12513
  const selected = parseDate(value);
12514
12514
  const minDate = parseDate(min2 ?? null);
12515
12515
  const maxDate = parseDate(max2 ?? null);
12516
+ const captionEnd = maxDate ?? /* @__PURE__ */ new Date();
12517
+ const captionStart = minDate ?? new Date(captionEnd.getFullYear() - 120, 0, 1);
12516
12518
  const [month, setMonth2] = useState(() => parseDate(value) ?? /* @__PURE__ */ new Date());
12517
12519
  const [lastValue, setLastValue] = useState(value);
12518
12520
  if (value !== lastValue) {
@@ -12614,6 +12616,9 @@ function DateInput({
12614
12616
  onSelect: handleSelect,
12615
12617
  disabled: disabledMatcher,
12616
12618
  showOutsideDays: true,
12619
+ captionLayout: "dropdown",
12620
+ startMonth: captionStart,
12621
+ endMonth: captionEnd,
12617
12622
  style: RDP_STYLE,
12618
12623
  components: {
12619
12624
  Chevron: ({ orientation }) => /* @__PURE__ */ jsx(
@@ -19122,6 +19127,464 @@ const AccordionItem = forwardRef(
19122
19127
  ] });
19123
19128
  }
19124
19129
  );
19130
+ const DOW = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
19131
+ const MONTHS = [
19132
+ "January",
19133
+ "February",
19134
+ "March",
19135
+ "April",
19136
+ "May",
19137
+ "June",
19138
+ "July",
19139
+ "August",
19140
+ "September",
19141
+ "October",
19142
+ "November",
19143
+ "December"
19144
+ ];
19145
+ const pad2 = (n2) => String(n2).padStart(2, "0");
19146
+ function dateKeyOf(year, month0, day) {
19147
+ return `${year}-${pad2(month0 + 1)}-${pad2(day)}`;
19148
+ }
19149
+ function realTodayKey() {
19150
+ const d2 = /* @__PURE__ */ new Date();
19151
+ return dateKeyOf(d2.getFullYear(), d2.getMonth(), d2.getDate());
19152
+ }
19153
+ function prettyDate(dateKey) {
19154
+ if (!dateKey) return null;
19155
+ const [y3, m2, d2] = dateKey.split("-").map(Number);
19156
+ if (!y3 || !m2 || !d2) return null;
19157
+ const dow = DOW[(new Date(y3, m2 - 1, d2).getDay() + 6) % 7];
19158
+ return `${dow}, ${MONTHS[m2 - 1]} ${d2}`;
19159
+ }
19160
+ function monthGrid(year, month0, todayKey) {
19161
+ const first = new Date(year, month0, 1);
19162
+ const lead = (first.getDay() + 6) % 7;
19163
+ const daysInMonth = new Date(year, month0 + 1, 0).getDate();
19164
+ const cells = [];
19165
+ for (let i2 = 0; i2 < lead; i2++) cells.push(null);
19166
+ for (let d2 = 1; d2 <= daysInMonth; d2++) {
19167
+ const key = dateKeyOf(year, month0, d2);
19168
+ cells.push({ day: d2, key, past: key < todayKey, today: key === todayKey });
19169
+ }
19170
+ while (cells.length % 7 !== 0) cells.push(null);
19171
+ return cells;
19172
+ }
19173
+ const PERIODS = [
19174
+ { id: "morning", label: "Morning", sub: "Before noon" },
19175
+ { id: "afternoon", label: "Afternoon", sub: "12:00 – 5:00 PM" },
19176
+ { id: "evening", label: "Evening", sub: "After 5:00 PM" }
19177
+ ];
19178
+ function NavButton({
19179
+ icon,
19180
+ disabled,
19181
+ onClick,
19182
+ ariaLabel
19183
+ }) {
19184
+ return /* @__PURE__ */ jsx(
19185
+ "button",
19186
+ {
19187
+ "aria-label": ariaLabel,
19188
+ disabled,
19189
+ type: "button",
19190
+ onClick,
19191
+ style: {
19192
+ width: 26,
19193
+ height: 26,
19194
+ borderRadius: "var(--r-sm)",
19195
+ border: "1px solid var(--border)",
19196
+ background: "var(--surface-0)",
19197
+ color: "var(--ink-2)",
19198
+ boxShadow: "var(--shadow-xs)",
19199
+ display: "inline-flex",
19200
+ alignItems: "center",
19201
+ justifyContent: "center",
19202
+ cursor: disabled ? "not-allowed" : "pointer",
19203
+ opacity: disabled ? 0.4 : 1,
19204
+ flexShrink: 0
19205
+ },
19206
+ children: /* @__PURE__ */ jsx(Icon, { color: "var(--ink-2)", name: icon, size: 13 })
19207
+ }
19208
+ );
19209
+ }
19210
+ function CalendarPane({
19211
+ value,
19212
+ todayKey,
19213
+ isDateDisabled,
19214
+ onPick,
19215
+ note
19216
+ }) {
19217
+ const initial = (value ?? todayKey).split("-").map(Number);
19218
+ const [view, setView] = useState({
19219
+ year: initial[0],
19220
+ month0: (initial[1] ?? 1) - 1
19221
+ });
19222
+ const grid = useMemo(
19223
+ () => monthGrid(view.year, view.month0, todayKey),
19224
+ [view.year, view.month0, todayKey]
19225
+ );
19226
+ const [ty, tm] = todayKey.split("-").map(Number);
19227
+ const canGoPrev = view.year > (ty ?? 0) || view.month0 > (tm ?? 1) - 1;
19228
+ return /* @__PURE__ */ jsxs("div", { style: { width: "100%" }, children: [
19229
+ /* @__PURE__ */ jsxs(
19230
+ "div",
19231
+ {
19232
+ style: {
19233
+ display: "flex",
19234
+ alignItems: "center",
19235
+ justifyContent: "space-between",
19236
+ marginBottom: 12,
19237
+ padding: "2px 2px 0"
19238
+ },
19239
+ children: [
19240
+ /* @__PURE__ */ jsx(
19241
+ NavButton,
19242
+ {
19243
+ ariaLabel: "Previous month",
19244
+ disabled: !canGoPrev,
19245
+ icon: "chevronLeft",
19246
+ onClick: () => {
19247
+ if (!canGoPrev) return;
19248
+ setView((v2) => v2.month0 === 0 ? { year: v2.year - 1, month0: 11 } : { ...v2, month0: v2.month0 - 1 });
19249
+ }
19250
+ }
19251
+ ),
19252
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 13, fontWeight: 600, color: "var(--ink-1)" }, children: [
19253
+ MONTHS[view.month0],
19254
+ " ",
19255
+ view.year
19256
+ ] }),
19257
+ /* @__PURE__ */ jsx(
19258
+ NavButton,
19259
+ {
19260
+ ariaLabel: "Next month",
19261
+ icon: "chevronRight",
19262
+ onClick: () => {
19263
+ setView((v2) => v2.month0 === 11 ? { year: v2.year + 1, month0: 0 } : { ...v2, month0: v2.month0 + 1 });
19264
+ }
19265
+ }
19266
+ )
19267
+ ]
19268
+ }
19269
+ ),
19270
+ /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 2, marginBottom: 4 }, children: DOW.map((d2, i2) => /* @__PURE__ */ jsx(
19271
+ "span",
19272
+ {
19273
+ style: {
19274
+ textAlign: "center",
19275
+ fontSize: 10,
19276
+ fontWeight: 600,
19277
+ color: "var(--ink-3)",
19278
+ textTransform: "uppercase",
19279
+ letterSpacing: "0.04em"
19280
+ },
19281
+ children: d2[0]
19282
+ },
19283
+ i2
19284
+ )) }),
19285
+ /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 2, justifyItems: "center" }, children: grid.map((c2, i2) => {
19286
+ if (!c2) return /* @__PURE__ */ jsx("span", { style: { width: 34, height: 34 } }, `e${i2}`);
19287
+ const disabled = c2.past || isDateDisabled(c2.key);
19288
+ const selected = c2.key === value;
19289
+ return /* @__PURE__ */ jsx(
19290
+ "button",
19291
+ {
19292
+ "data-testid": `dtp-day-${c2.key}`,
19293
+ disabled,
19294
+ type: "button",
19295
+ onClick: () => onPick(c2.key),
19296
+ style: {
19297
+ width: 34,
19298
+ height: 34,
19299
+ borderRadius: 999,
19300
+ border: 0,
19301
+ fontSize: 12.5,
19302
+ fontWeight: selected ? 700 : c2.today ? 600 : 500,
19303
+ cursor: disabled ? "not-allowed" : "pointer",
19304
+ background: selected ? "var(--primary)" : "transparent",
19305
+ color: disabled ? "var(--ink-4)" : selected ? "white" : c2.today ? "var(--primary)" : "var(--ink-1)",
19306
+ opacity: disabled ? 0.45 : 1,
19307
+ textDecoration: disabled ? "line-through" : "none",
19308
+ outline: c2.today && !selected ? "1px solid var(--primary-soft)" : "none",
19309
+ display: "inline-flex",
19310
+ alignItems: "center",
19311
+ justifyContent: "center"
19312
+ },
19313
+ children: c2.day
19314
+ },
19315
+ c2.key
19316
+ );
19317
+ }) }),
19318
+ note ? /* @__PURE__ */ jsxs(
19319
+ "div",
19320
+ {
19321
+ style: {
19322
+ marginTop: 10,
19323
+ fontSize: 10.5,
19324
+ color: "var(--ink-3)",
19325
+ display: "flex",
19326
+ alignItems: "center",
19327
+ gap: 5
19328
+ },
19329
+ children: [
19330
+ /* @__PURE__ */ jsx(Icon, { color: "var(--ink-4)", name: "calendar", size: 11 }),
19331
+ note
19332
+ ]
19333
+ }
19334
+ ) : null
19335
+ ] });
19336
+ }
19337
+ function SlotsPane({
19338
+ state,
19339
+ value,
19340
+ hint,
19341
+ onPick,
19342
+ onRetry
19343
+ }) {
19344
+ if (state.status === "loading") {
19345
+ return /* @__PURE__ */ jsx(
19346
+ "div",
19347
+ {
19348
+ "data-testid": "dtp-slots-loading",
19349
+ style: { minHeight: 120, display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink-3)", fontSize: 12.5 },
19350
+ children: "Loading times…"
19351
+ }
19352
+ );
19353
+ }
19354
+ if (state.status === "error") {
19355
+ return /* @__PURE__ */ jsxs(
19356
+ "div",
19357
+ {
19358
+ "data-testid": "dtp-slots-error",
19359
+ style: { minHeight: 120, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 8, color: "var(--ink-3)", textAlign: "center", padding: 16 },
19360
+ children: [
19361
+ /* @__PURE__ */ jsx(Icon, { color: "var(--danger)", name: "alert-circle", size: 18 }),
19362
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 12.5 }, children: "Couldn't load times." }),
19363
+ /* @__PURE__ */ jsx(Button$1, { size: "sm", variant: "secondary", onClick: onRetry, children: "Try again" })
19364
+ ]
19365
+ }
19366
+ );
19367
+ }
19368
+ const { slots } = state;
19369
+ const anyAvailable = slots.some((s2) => s2.available !== false);
19370
+ if (slots.length === 0 || !anyAvailable) {
19371
+ return /* @__PURE__ */ jsxs(
19372
+ "div",
19373
+ {
19374
+ "data-testid": "dtp-slots-empty",
19375
+ style: { minHeight: 120, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 8, color: "var(--ink-3)", textAlign: "center", padding: 16 },
19376
+ children: [
19377
+ /* @__PURE__ */ jsx(Icon, { color: "var(--ink-4)", name: "clock", size: 18 }),
19378
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 12.5, fontWeight: 500 }, children: "No times available" }),
19379
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, maxWidth: 200 }, children: "Try another day, or adjust the session length." })
19380
+ ]
19381
+ }
19382
+ );
19383
+ }
19384
+ return /* @__PURE__ */ jsxs("div", { "data-testid": "dtp-slots", style: { display: "flex", flexDirection: "column", gap: 14 }, children: [
19385
+ hint ? /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "var(--ink-2)" }, children: hint }) : null,
19386
+ PERIODS.map((p2) => {
19387
+ const inPeriod = slots.filter((s2) => s2.period === p2.id);
19388
+ if (inPeriod.length === 0) return null;
19389
+ return /* @__PURE__ */ jsxs("div", { children: [
19390
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: 8, marginBottom: 8 }, children: [
19391
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 11.5, fontWeight: 700, color: "var(--ink-1)" }, children: p2.label }),
19392
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 10.5, color: "var(--ink-3)" }, children: p2.sub })
19393
+ ] }),
19394
+ /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(84px, 1fr))", gap: 8 }, children: inPeriod.map((s2) => {
19395
+ const blocked = s2.available === false;
19396
+ const on = s2.id === value;
19397
+ return /* @__PURE__ */ jsx(
19398
+ "button",
19399
+ {
19400
+ "data-testid": `dtp-slot-${s2.id}`,
19401
+ disabled: blocked,
19402
+ title: blocked ? s2.reason : void 0,
19403
+ type: "button",
19404
+ onClick: () => onPick(s2.id),
19405
+ style: {
19406
+ height: 38,
19407
+ borderRadius: "var(--r-md)",
19408
+ border: `1px solid ${on ? "var(--primary)" : "var(--border-strong)"}`,
19409
+ background: on ? "var(--primary)" : "var(--surface-0)",
19410
+ color: blocked ? "var(--ink-4)" : on ? "white" : "var(--ink-1)",
19411
+ fontSize: 12.5,
19412
+ fontWeight: on ? 600 : 500,
19413
+ cursor: blocked ? "not-allowed" : "pointer",
19414
+ opacity: blocked ? 0.5 : 1,
19415
+ textDecoration: blocked ? "line-through" : "none"
19416
+ },
19417
+ children: s2.label
19418
+ },
19419
+ s2.id
19420
+ );
19421
+ }) })
19422
+ ] }, p2.id);
19423
+ })
19424
+ ] });
19425
+ }
19426
+ function DateTimePicker({
19427
+ value,
19428
+ onChange,
19429
+ loadSlots,
19430
+ isDateDisabled,
19431
+ reloadKey,
19432
+ slotsHint,
19433
+ calendarNote,
19434
+ initialStep = "date",
19435
+ todayKey,
19436
+ testId = "date-time-picker"
19437
+ }) {
19438
+ const today = todayKey ?? realTodayKey();
19439
+ const dateDisabled = useCallback(
19440
+ (k2) => isDateDisabled ? isDateDisabled(k2) : k2 < today,
19441
+ [isDateDisabled, today]
19442
+ );
19443
+ const [step, setStep] = useState(initialStep);
19444
+ const [slotsState, setSlotsState] = useState(
19445
+ value.dateKey ? { status: "loading" } : { status: "ready", slots: [] }
19446
+ );
19447
+ const reqIdRef = useRef(0);
19448
+ const runLoad = useCallback(
19449
+ (dateKey) => {
19450
+ const id = ++reqIdRef.current;
19451
+ setSlotsState({ status: "loading" });
19452
+ loadSlots(dateKey).then(
19453
+ (slots) => {
19454
+ if (reqIdRef.current === id) setSlotsState({ status: "ready", slots });
19455
+ },
19456
+ () => {
19457
+ if (reqIdRef.current === id) setSlotsState({ status: "error" });
19458
+ }
19459
+ );
19460
+ },
19461
+ [loadSlots]
19462
+ );
19463
+ const dateKeyRef = useRef(value.dateKey);
19464
+ useEffect(() => {
19465
+ dateKeyRef.current = value.dateKey;
19466
+ }, [value.dateKey]);
19467
+ useEffect(() => {
19468
+ const dateKey = dateKeyRef.current;
19469
+ if (dateKey) runLoad(dateKey);
19470
+ }, [reloadKey, runLoad]);
19471
+ const handlePickDate = (dateKey) => {
19472
+ onChange({ dateKey, slotId: null });
19473
+ setStep("time");
19474
+ runLoad(dateKey);
19475
+ };
19476
+ const handlePickSlot = (slotId) => {
19477
+ onChange({ ...value, slotId });
19478
+ };
19479
+ const pretty = prettyDate(value.dateKey);
19480
+ const selectedLabel = slotsState.status === "ready" ? slotsState.slots.find((s2) => s2.id === value.slotId)?.label : void 0;
19481
+ const note = calendarNote === void 0 ? "Past dates are off — sessions can't be created in the past." : calendarNote;
19482
+ return /* @__PURE__ */ jsxs(
19483
+ "div",
19484
+ {
19485
+ "data-testid": testId,
19486
+ style: {
19487
+ width: 340,
19488
+ maxWidth: "calc(100vw - 32px)",
19489
+ background: "var(--surface-0)",
19490
+ borderRadius: "var(--r-lg)",
19491
+ border: "1px solid var(--border-strong)",
19492
+ boxShadow: "var(--shadow-lg)",
19493
+ overflow: "hidden",
19494
+ display: "flex",
19495
+ flexDirection: "column",
19496
+ maxHeight: "min(440px, 80vh)"
19497
+ },
19498
+ children: [
19499
+ step === "date" ? /* @__PURE__ */ jsx("div", { style: { padding: 16 }, children: /* @__PURE__ */ jsx(
19500
+ CalendarPane,
19501
+ {
19502
+ isDateDisabled: dateDisabled,
19503
+ note,
19504
+ todayKey: today,
19505
+ value: value.dateKey,
19506
+ onPick: handlePickDate
19507
+ }
19508
+ ) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
19509
+ /* @__PURE__ */ jsxs(
19510
+ "button",
19511
+ {
19512
+ "data-testid": "dtp-back-to-date",
19513
+ type: "button",
19514
+ onClick: () => setStep("date"),
19515
+ style: {
19516
+ display: "flex",
19517
+ alignItems: "center",
19518
+ gap: 8,
19519
+ width: "100%",
19520
+ padding: "12px 16px",
19521
+ border: 0,
19522
+ borderBottom: "1px solid var(--divider)",
19523
+ background: "var(--surface-1)",
19524
+ cursor: "pointer",
19525
+ textAlign: "left"
19526
+ },
19527
+ children: [
19528
+ /* @__PURE__ */ jsx(Icon, { color: "var(--ink-3)", name: "chevronLeft", size: 13 }),
19529
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "var(--ink-3)" }, children: "Date" }),
19530
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 13, fontWeight: 600, color: "var(--ink-1)", marginLeft: "auto" }, children: pretty })
19531
+ ]
19532
+ }
19533
+ ),
19534
+ /* @__PURE__ */ jsx("div", { style: { padding: 16, overflowY: "auto", flex: 1 }, children: /* @__PURE__ */ jsx(
19535
+ SlotsPane,
19536
+ {
19537
+ hint: slotsHint,
19538
+ state: slotsState,
19539
+ value: value.slotId,
19540
+ onPick: handlePickSlot,
19541
+ onRetry: () => {
19542
+ if (value.dateKey) runLoad(value.dateKey);
19543
+ }
19544
+ }
19545
+ ) })
19546
+ ] }),
19547
+ /* @__PURE__ */ jsx(
19548
+ "div",
19549
+ {
19550
+ style: {
19551
+ padding: "10px 16px",
19552
+ borderTop: "1px solid var(--divider)",
19553
+ background: "var(--surface-1)",
19554
+ display: "flex",
19555
+ alignItems: "center",
19556
+ justifyContent: "space-between",
19557
+ gap: 8,
19558
+ flexShrink: 0
19559
+ },
19560
+ children: /* @__PURE__ */ jsx(
19561
+ "span",
19562
+ {
19563
+ style: {
19564
+ fontSize: 12,
19565
+ color: "var(--ink-2)",
19566
+ minWidth: 0,
19567
+ overflow: "hidden",
19568
+ textOverflow: "ellipsis",
19569
+ whiteSpace: "nowrap"
19570
+ },
19571
+ children: value.slotId && selectedLabel ? /* @__PURE__ */ jsxs("span", { children: [
19572
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--ink-3)" }, children: "Selected:" }),
19573
+ " ",
19574
+ /* @__PURE__ */ jsxs("b", { children: [
19575
+ pretty,
19576
+ " · ",
19577
+ selectedLabel
19578
+ ] })
19579
+ ] }) : step === "date" ? "Pick a date" : "Pick a time"
19580
+ }
19581
+ )
19582
+ }
19583
+ )
19584
+ ]
19585
+ }
19586
+ );
19587
+ }
19125
19588
  export {
19126
19589
  AIBadge,
19127
19590
  AICard,
@@ -19155,6 +19618,7 @@ export {
19155
19618
  DISPLAY_OPTIONS,
19156
19619
  DarkScope,
19157
19620
  DateInput,
19621
+ DateTimePicker,
19158
19622
  Divider,
19159
19623
  Drawer,
19160
19624
  DrawerFooter,