minka-ds 0.3.10 → 0.3.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minka-ds",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Minka product design system — tokenized component library",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -29,32 +29,37 @@ export function DateTimeRangePicker({
29
29
  const range: DateRange | undefined =
30
30
  value?.from ? { from: value.from, to: value.to } : undefined
31
31
 
32
- // A range is "complete" once from and to differ (or a real end was picked).
33
- const isComplete = Boolean(value?.from && value?.to && value.from.getTime() !== value.to.getTime())
32
+ // `anchor` is the source of truth for selection phase:
33
+ // anchor === null no active pick (nothing selected, or a complete range)
34
+ // anchor !== null → first date is set, waiting for the second click
35
+ // This avoids the ambiguity of inferring phase from `from === to`.
36
+ const [anchor, setAnchor] = React.useState<Date | null>(null)
34
37
 
35
- function handleRangeSelect(
36
- selected: DateRange | undefined,
37
- selectedDay: Date,
38
- ) {
39
- // If a complete range already exists, any click starts a brand-new range
40
- // anchored on the clicked day — instead of extending the old one. This also
41
- // frees the user from the max-range cap that was anchored on the old `from`.
42
- if (isComplete) {
43
- onChange({
44
- from: selectedDay,
45
- to: selectedDay,
46
- startTime: value?.startTime ?? "",
47
- endTime: value?.endTime ?? "",
48
- })
38
+ function handleDay(day: Date) {
39
+ const startTime = value?.startTime ?? ""
40
+ const endTime = value?.endTime ?? ""
41
+
42
+ // Picking the second date.
43
+ if (anchor) {
44
+ const spanMs = Math.abs(day.getTime() - anchor.getTime())
45
+ const withinCap = maxRangeDays == null || spanMs <= maxRangeDays * 86_400_000
46
+ if (withinCap) {
47
+ // Complete the range (order endpoints; can extend backward or forward).
48
+ const from = day < anchor ? day : anchor
49
+ const to = day < anchor ? anchor : day
50
+ setAnchor(null)
51
+ onChange({ from, to, startTime, endTime })
52
+ } else {
53
+ // Outside the cap → treat as a fresh start anchored on the clicked day.
54
+ setAnchor(day)
55
+ onChange({ from: day, to: day, startTime, endTime })
56
+ }
49
57
  return
50
58
  }
51
- if (!selected?.from) { onChange(null); return }
52
- onChange({
53
- from: selected.from,
54
- to: selected.to ?? selected.from,
55
- startTime: value?.startTime ?? "",
56
- endTime: value?.endTime ?? "",
57
- })
59
+
60
+ // No active pick (fresh, or restarting from a complete range).
61
+ setAnchor(day)
62
+ onChange({ from: day, to: day, startTime, endTime })
58
63
  }
59
64
 
60
65
  function handleStartTime(e: React.ChangeEvent<HTMLInputElement>) {
@@ -67,14 +72,6 @@ export function DateTimeRangePicker({
67
72
  onChange({ ...value, endTime: e.target.value })
68
73
  }
69
74
 
70
- // Only cap the calendar while the user is picking the second date (from set,
71
- // range not yet complete). Once complete, all dates stay clickable so a fresh
72
- // click elsewhere can start a new range.
73
- const disabledAfter =
74
- maxRangeDays && range?.from && !isComplete
75
- ? { after: new Date(range.from.getTime() + maxRangeDays * 86_400_000) }
76
- : undefined
77
-
78
75
  return (
79
76
  <div className={cn(
80
77
  "[border-radius:var(--radius-card)] border border-[var(--color-border-default)] bg-[var(--color-bg-raised)] overflow-hidden w-fit",
@@ -85,8 +82,17 @@ export function DateTimeRangePicker({
85
82
  numberOfMonths={1}
86
83
  captionLayout="label"
87
84
  selected={range}
88
- onSelect={handleRangeSelect}
89
- disabled={disabledAfter}
85
+ onSelect={(_, selectedDay) => handleDay(selectedDay)}
86
+ // While picking the second date, soften days outside the ±maxRangeDays
87
+ // window — a visual hint of the recommended span. They stay clickable
88
+ // (clicking one re-anchors) and hover still works; this is a hint, not
89
+ // a block.
90
+ modifiers={
91
+ anchor && maxRangeDays != null
92
+ ? { outOfRange: (d: Date) => Math.abs(d.getTime() - anchor.getTime()) > maxRangeDays * 86_400_000 }
93
+ : undefined
94
+ }
95
+ modifiersClassNames={{ outOfRange: "text-[var(--color-text-hint)]" }}
90
96
  />
91
97
  <div className="border-t border-[var(--color-border-default)] px-4 py-3 flex flex-col gap-3">
92
98
  <div className="flex flex-col gap-1.5">