@apotech-os/ui-sdk 2.5.0 → 2.6.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.
package/dist/index.cjs CHANGED
@@ -1100,8 +1100,15 @@ function DataTable({
1100
1100
  TableRow,
1101
1101
  {
1102
1102
  "data-state": selected.has(row.id) ? "selected" : void 0,
1103
- className: onRowClick ? "cursor-pointer" : "hover:bg-transparent",
1103
+ className: onRowClick ? "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset" : "hover:bg-transparent",
1104
+ tabIndex: onRowClick ? 0 : void 0,
1104
1105
  onClick: onRowClick ? () => onRowClick(row.original) : void 0,
1106
+ onKeyDown: onRowClick ? (event) => {
1107
+ if (event.target !== event.currentTarget) return;
1108
+ if (event.key !== "Enter" && event.key !== " ") return;
1109
+ event.preventDefault();
1110
+ onRowClick(row.original);
1111
+ } : void 0,
1105
1112
  children: [
1106
1113
  selectable && /* @__PURE__ */ jsxRuntime.jsx(TableCell, { onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsxRuntime.jsx(
1107
1114
  Checkbox,
@@ -1375,20 +1382,20 @@ var PERIOD_PRESETS = [
1375
1382
  { key: "all", label: "Tout l'historique" }
1376
1383
  ];
1377
1384
  var COMPARE_LABELS = {
1378
- none: "Aucune",
1385
+ none: "Ne pas comparer",
1379
1386
  previous_period: "P\xE9riode pr\xE9c\xE9dente",
1380
1387
  previous_year: "Ann\xE9e pr\xE9c\xE9dente",
1381
- custom: "Personnalis\xE9e"
1388
+ custom: "P\xE9riode personnalis\xE9e"
1389
+ };
1390
+ var COMPARE_SHORT_LABELS = {
1391
+ none: "",
1392
+ previous_period: "p\xE9riode pr\xE9c.",
1393
+ previous_year: "N-1",
1394
+ custom: "p\xE9riode choisie"
1382
1395
  };
1383
1396
  var DAY_FORMAT = "yyyy-MM-dd";
1384
1397
  var toDay = (date) => dateFns.format(date, DAY_FORMAT);
1385
1398
  var fromDay = (day) => dateFns.parse(day, DAY_FORMAT, /* @__PURE__ */ new Date());
1386
- var CALENDAR_UNIT = {
1387
- thisMonth: "month",
1388
- lastMonth: "month",
1389
- thisQuarter: "quarter",
1390
- thisYear: "year"
1391
- };
1392
1399
  function presetRange(preset, today) {
1393
1400
  const day = toDay(today);
1394
1401
  switch (preset) {
@@ -1405,17 +1412,17 @@ function presetRange(preset, today) {
1405
1412
  case "last90":
1406
1413
  return { from: toDay(dateFns.subDays(today, 89)), to: day };
1407
1414
  case "thisMonth":
1408
- return { from: toDay(dateFns.startOfMonth(today)), to: toDay(dateFns.endOfMonth(today)) };
1415
+ return { from: toDay(dateFns.startOfMonth(today)), to: day };
1409
1416
  case "lastMonth": {
1410
1417
  const previous = dateFns.subMonths(today, 1);
1411
1418
  return { from: toDay(dateFns.startOfMonth(previous)), to: toDay(dateFns.endOfMonth(previous)) };
1412
1419
  }
1413
1420
  case "thisQuarter":
1414
- return { from: toDay(dateFns.startOfQuarter(today)), to: toDay(dateFns.endOfQuarter(today)) };
1421
+ return { from: toDay(dateFns.startOfQuarter(today)), to: day };
1415
1422
  case "last12Months":
1416
- return { from: toDay(dateFns.addDays(dateFns.subYears(today, 1), 1)), to: day };
1423
+ return { from: toDay(dateFns.subMonths(today, 12)), to: day };
1417
1424
  case "thisYear":
1418
- return { from: toDay(dateFns.startOfYear(today)), to: toDay(dateFns.endOfYear(today)) };
1425
+ return { from: toDay(dateFns.startOfYear(today)), to: day };
1419
1426
  case "all":
1420
1427
  return { to: day };
1421
1428
  case "custom":
@@ -1429,19 +1436,6 @@ function comparisonRange(preset, range, compare2) {
1429
1436
  if (compare2 === "previous_year") {
1430
1437
  return { from: toDay(dateFns.subYears(from, 1)), to: toDay(dateFns.subYears(to, 1)) };
1431
1438
  }
1432
- const unit = CALENDAR_UNIT[preset];
1433
- if (unit === "month") {
1434
- const previous = dateFns.subMonths(from, 1);
1435
- return { from: toDay(dateFns.startOfMonth(previous)), to: toDay(dateFns.endOfMonth(previous)) };
1436
- }
1437
- if (unit === "quarter") {
1438
- const previous = dateFns.addQuarters(from, -1);
1439
- return { from: toDay(dateFns.startOfQuarter(previous)), to: toDay(dateFns.endOfQuarter(previous)) };
1440
- }
1441
- if (unit === "year") {
1442
- const previous = dateFns.subYears(from, 1);
1443
- return { from: toDay(dateFns.startOfYear(previous)), to: toDay(dateFns.endOfYear(previous)) };
1444
- }
1445
1439
  const length = dateFns.differenceInCalendarDays(to, from) + 1;
1446
1440
  return { from: toDay(dateFns.subDays(from, length)), to: toDay(dateFns.subDays(to, length)) };
1447
1441
  }
@@ -1531,12 +1525,20 @@ var DAY_PICKER_CLASS_NAMES = {
1531
1525
  today: "font-semibold text-primary",
1532
1526
  outside: "text-muted-foreground/50",
1533
1527
  disabled: "opacity-40",
1534
- selected: "bg-primary/15",
1535
- range_start: "rounded-l-md bg-primary [&_button]:bg-primary [&_button]:text-primary-foreground",
1536
- range_end: "rounded-r-md bg-primary [&_button]:bg-primary [&_button]:text-primary-foreground",
1537
- range_middle: "bg-primary/15 [&_button]:hover:bg-primary/25"
1528
+ // A cell DayPicker decided not to fill still occupies the grid, and still
1529
+ // matches the range modifiers below — without this it would show a stretch of
1530
+ // painted band with no number in it. `invisible`, not `hidden`: the six-week
1531
+ // grid keeps its height either way.
1532
+ hidden: "invisible"
1533
+ };
1534
+ var DAY_MODIFIER_CLASS_NAMES = {
1535
+ period_middle: "bg-primary/15",
1536
+ period_start: "rounded-l-md [&_button]:bg-primary [&_button]:text-primary-foreground [&_button]:hover:bg-primary",
1537
+ period_end: "rounded-r-md [&_button]:bg-primary [&_button]:text-primary-foreground [&_button]:hover:bg-primary",
1538
+ compare_middle: "bg-chart-2/30",
1539
+ compare_start: "rounded-l-md bg-chart-2/30 [&_button]:ring-2 [&_button]:ring-inset [&_button]:ring-chart-2 [&_button]:font-semibold [&_button]:text-chart-2",
1540
+ compare_end: "rounded-r-md bg-chart-2/30 [&_button]:ring-2 [&_button]:ring-inset [&_button]:ring-chart-2 [&_button]:font-semibold [&_button]:text-chart-2"
1538
1541
  };
1539
- var COMPARE_MODIFIER_CLASS = "bg-chart-2/25 ring-1 ring-inset ring-chart-2/40";
1540
1542
  function draftOf(value) {
1541
1543
  return {
1542
1544
  preset: value.preset,
@@ -1568,16 +1570,38 @@ function previewCompare(draft) {
1568
1570
  function activeCompare(draft) {
1569
1571
  return draft.preset === "all" ? "none" : draft.compare;
1570
1572
  }
1571
- function anchorMonth(range) {
1572
- if (!range) return void 0;
1573
- if (isDay(range.from)) return fromDay(range.from);
1574
- return isDay(range.to) ? fromDay(range.to) : void 0;
1573
+ function bandOf(from, to) {
1574
+ if (!isDay(from)) return void 0;
1575
+ const start = fromDay(from);
1576
+ return { from: start, to: isDay(to) ? fromDay(to) : start };
1577
+ }
1578
+ var inBand = (band, date) => band !== void 0 && date >= band.from && date <= band.to;
1579
+ var isBound = (date, bound) => bound !== void 0 && date.getTime() === bound.getTime();
1580
+ function compareBandOf(draft) {
1581
+ const active = activeCompare(draft);
1582
+ if (active === "none") return void 0;
1583
+ if (active === "custom") return bandOf(draft.compareFrom, draft.compareTo);
1584
+ const derived = previewCompare(draft);
1585
+ return derived?.from ? { from: fromDay(derived.from), to: fromDay(derived.to) } : void 0;
1586
+ }
1587
+ function anchorMonth(range, visible, today) {
1588
+ const from = isDay(range.from) ? fromDay(range.from) : void 0;
1589
+ const to = isDay(range.to) ? fromDay(range.to) : void 0;
1590
+ if (visible === 2) return from ?? to;
1591
+ if (!to) return from;
1592
+ if (to <= today) return to;
1593
+ return from && from <= today ? today : from ?? today;
1594
+ }
1595
+ function selectionOf(from, to) {
1596
+ if (!isDay(from)) return void 0;
1597
+ return { from: fromDay(from), to: isDay(to) ? fromDay(to) : void 0 };
1575
1598
  }
1576
1599
  function DateRangeCompare({
1577
1600
  value,
1578
1601
  onChange,
1579
1602
  comparable = true,
1580
1603
  presets = PERIOD_PRESETS,
1604
+ months = 1,
1581
1605
  align = "start",
1582
1606
  disabled,
1583
1607
  className
@@ -1585,44 +1609,76 @@ function DateRangeCompare({
1585
1609
  const [open, setOpen] = React2__namespace.useState(false);
1586
1610
  const [draft, setDraft] = React2__namespace.useState(() => draftOf(value));
1587
1611
  const [month, setMonth] = React2__namespace.useState(void 0);
1588
- const [months, setMonths] = React2__namespace.useState(1);
1589
- const showRange = (range2) => {
1590
- setMonth(anchorMonth(range2));
1591
- setMonths(spansTwoMonths(range2) ? 2 : 1);
1612
+ const [autoMonths, setAutoMonths] = React2__namespace.useState(1);
1613
+ const [target, setTarget] = React2__namespace.useState("period");
1614
+ const [today, setToday] = React2__namespace.useState(() => /* @__PURE__ */ new Date());
1615
+ const visibleMonths = months === "auto" ? autoMonths : months;
1616
+ const showRange = (range2, at = today) => {
1617
+ const next = months === "auto" ? spansTwoMonths(range2) ? 2 : 1 : months;
1618
+ setAutoMonths(next);
1619
+ setMonth(anchorMonth(range2, next, at));
1592
1620
  };
1593
1621
  const openPanel = () => {
1622
+ const now = /* @__PURE__ */ new Date();
1623
+ setToday(now);
1594
1624
  setDraft(draftOf(value));
1595
- showRange(value.range);
1625
+ setTarget("period");
1626
+ showRange(value.range, now);
1596
1627
  setOpen(true);
1597
1628
  };
1598
- const pickPreset = (preset) => {
1599
- const resolved = periodFromPreset(preset, /* @__PURE__ */ new Date(), activeCompare(draft));
1600
- setDraft({
1629
+ const range = rangeOf(draft);
1630
+ const compareRange = previewCompare(draft);
1631
+ const compareEditable = comparable && activeCompare(draft) === "custom";
1632
+ const activeTarget = compareEditable ? target : "period";
1633
+ const periodBand = bandOf(draft.from, draft.to);
1634
+ const compareBand = compareBandOf(draft);
1635
+ const emit = (candidate) => {
1636
+ const complete = rangeOf(candidate);
1637
+ if (!complete) return false;
1638
+ onChange(
1639
+ makePeriod(candidate.preset, complete, activeCompare(candidate), customCompareOf(candidate))
1640
+ );
1641
+ setOpen(false);
1642
+ return true;
1643
+ };
1644
+ const apply = () => emit(draft);
1645
+ const pickPreset = (preset, commit) => {
1646
+ const resolved = periodFromPreset(preset, today, activeCompare(draft));
1647
+ const next = {
1601
1648
  ...draftOf(resolved),
1602
1649
  // The rail changes the period, not the comparison: the mode the user chose
1603
1650
  // survives, and `activeCompare` decides whether it can apply here.
1604
1651
  compare: draft.compare,
1605
1652
  compareFrom: draft.compareFrom,
1606
1653
  compareTo: draft.compareTo
1607
- });
1654
+ };
1655
+ setDraft(next);
1656
+ setTarget("period");
1608
1657
  showRange(resolved.range);
1658
+ if (!comparable && commit) emit(next);
1609
1659
  };
1610
1660
  const pickDays = (selected) => {
1611
- setDraft({
1612
- ...draft,
1613
- preset: "custom",
1614
- from: selected?.from ? toDay(selected.from) : void 0,
1615
- to: selected?.to ? toDay(selected.to) : void 0
1616
- });
1661
+ const from = selected?.from ? toDay(selected.from) : void 0;
1662
+ const to = selected?.to ? toDay(selected.to) : void 0;
1663
+ if (activeTarget === "compare") {
1664
+ setDraft({ ...draft, compare: "custom", compareFrom: from, compareTo: to });
1665
+ return;
1666
+ }
1667
+ setDraft({ ...draft, preset: "custom", from, to });
1617
1668
  };
1618
- const range = rangeOf(draft);
1619
- const compareRange = previewCompare(draft);
1620
- const apply = () => {
1621
- if (!range) return;
1622
- onChange(makePeriod(draft.preset, range, activeCompare(draft), customCompareOf(draft)));
1623
- setOpen(false);
1669
+ const chooseCompare = (mode) => {
1670
+ const next = { ...draft, compare: mode };
1671
+ setDraft(next);
1672
+ setTarget(mode === "custom" ? "compare" : "period");
1673
+ const shown = mode === "custom" ? customCompareOf(next) : range;
1674
+ if (shown) showRange(shown);
1675
+ };
1676
+ const focusTarget = (next) => {
1677
+ setTarget(next);
1678
+ const shown = next === "compare" ? customCompareOf(draft) : range;
1679
+ if (shown) showRange(shown);
1624
1680
  };
1625
- const selectedDays = isDay(draft.from) ? { from: fromDay(draft.from), to: isDay(draft.to) ? fromDay(draft.to) : void 0 } : void 0;
1681
+ const footer = activeTarget === "compare" ? compareRange ? formatRange(compareRange) : "P\xE9riode de comparaison \xE0 choisir" : range ? `${formatRange(range)}${compareRange ? ` vs ${formatRange(compareRange)}` : ""}` : "Date de fin \xE0 choisir";
1626
1682
  return /* @__PURE__ */ jsxRuntime.jsx(
1627
1683
  Popover,
1628
1684
  {
@@ -1630,42 +1686,81 @@ function DateRangeCompare({
1630
1686
  onOpenChange: (next) => next ? openPanel() : setOpen(false),
1631
1687
  align,
1632
1688
  className: cn("w-auto p-0", className),
1633
- trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", disabled, children: formatPeriod(value) }),
1634
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex", children: [
1635
- presets.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(PresetRail, { presets, selected: draft.preset, onSelect: pickPreset }),
1689
+ trigger: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "outline", disabled, children: [
1690
+ /* @__PURE__ */ jsxRuntime.jsx(IconCalendar, { className: "size-4 text-muted-foreground" }),
1691
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatPeriod(value) }),
1692
+ value.compare !== "none" && value.compareRange !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "rounded-md bg-chart-2/15 px-1.5 py-0.5 text-xs font-medium text-chart-2", children: [
1693
+ "vs ",
1694
+ COMPARE_SHORT_LABELS[value.compare]
1695
+ ] }),
1696
+ /* @__PURE__ */ jsxRuntime.jsx(IconChevronDown, { className: "size-4 text-muted-foreground" })
1697
+ ] }),
1698
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex max-h-[80vh] items-stretch", children: [
1699
+ (presets.length > 0 || comparable) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-48 shrink-0 flex-col border-r", children: [
1700
+ presets.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1701
+ PresetRail,
1702
+ {
1703
+ presets,
1704
+ selected: draft.preset,
1705
+ onSelect: pickPreset,
1706
+ className: "min-h-0 flex-1 overflow-y-auto p-2"
1707
+ }
1708
+ ),
1709
+ comparable && /* @__PURE__ */ jsxRuntime.jsx(
1710
+ CompareSection,
1711
+ {
1712
+ draft,
1713
+ compareRange,
1714
+ onModeChange: chooseCompare
1715
+ }
1716
+ )
1717
+ ] }),
1636
1718
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
1719
+ comparable && /* @__PURE__ */ jsxRuntime.jsx(
1720
+ TargetPills,
1721
+ {
1722
+ target: activeTarget,
1723
+ onSelect: focusTarget,
1724
+ compareEditable
1725
+ }
1726
+ ),
1637
1727
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative p-3", children: /* @__PURE__ */ jsxRuntime.jsx(
1638
1728
  reactDayPicker.DayPicker,
1639
1729
  {
1640
1730
  mode: "range",
1641
1731
  locale: fr.fr,
1642
1732
  weekStartsOn: 1,
1643
- numberOfMonths: months,
1733
+ numberOfMonths: visibleMonths,
1644
1734
  month,
1645
1735
  onMonthChange: setMonth,
1646
- selected: selectedDays,
1736
+ today,
1737
+ disabled: { after: today },
1738
+ fixedWeeks: true,
1739
+ showOutsideDays: visibleMonths === 1,
1740
+ selected: activeTarget === "compare" ? selectionOf(draft.compareFrom, draft.compareTo) : selectionOf(draft.from, draft.to),
1647
1741
  onSelect: pickDays,
1648
1742
  modifiers: {
1649
- compare: compareRange?.from ? [{ from: fromDay(compareRange.from), to: fromDay(compareRange.to) }] : []
1743
+ period_middle: periodBand ? [{ from: periodBand.from, to: periodBand.to }] : [],
1744
+ period_start: periodBand ? [periodBand.from] : [],
1745
+ period_end: periodBand ? [periodBand.to] : [],
1746
+ // The period wins any cell both ranges claim.
1747
+ compare_middle: (date) => inBand(compareBand, date) && !inBand(periodBand, date),
1748
+ compare_start: (date) => isBound(date, compareBand?.from) && !inBand(periodBand, date),
1749
+ compare_end: (date) => isBound(date, compareBand?.to) && !inBand(periodBand, date)
1650
1750
  },
1651
- modifiersClassNames: { compare: COMPARE_MODIFIER_CLASS },
1652
- classNames: DAY_PICKER_CLASS_NAMES
1751
+ modifiersClassNames: DAY_MODIFIER_CLASS_NAMES,
1752
+ classNames: DAY_PICKER_CLASS_NAMES,
1753
+ "aria-label": activeTarget === "compare" ? "Calendrier \u2014 s\xE9lection de la comparaison" : "Calendrier \u2014 s\xE9lection de la p\xE9riode",
1754
+ labels: {
1755
+ // Suffixed rather than replaced: which range a click lands in is
1756
+ // the one thing a screen reader cannot get from the grid, and the
1757
+ // default label still carries the date and its state.
1758
+ labelDayButton: (date, modifiers, options, dateLib) => `${reactDayPicker.labelDayButton(date, modifiers, options, dateLib)} \u2014 ${activeTarget === "compare" ? "comparaison" : "p\xE9riode"}`
1759
+ }
1653
1760
  }
1654
1761
  ) }),
1655
- comparable && /* @__PURE__ */ jsxRuntime.jsx(
1656
- CompareSection,
1657
- {
1658
- draft,
1659
- compareRange,
1660
- onModeChange: (compare2) => setDraft({ ...draft, compare: compare2 }),
1661
- onCustomChange: (from, to) => setDraft({ ...draft, compareFrom: from, compareTo: to })
1662
- }
1663
- ),
1664
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-4 border-t px-3 py-2", children: [
1665
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
1666
- range ? formatRange(range) : "Choisissez une date de fin",
1667
- compareRange && ` vs ${formatRange(compareRange)}`
1668
- ] }),
1762
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-auto flex items-center justify-between gap-4 border-t px-3 py-2", children: [
1763
+ /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", "aria-live": "polite", className: "text-xs text-muted-foreground", children: footer }),
1669
1764
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
1670
1765
  /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", onClick: () => setOpen(false), children: "Annuler" }),
1671
1766
  /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", onClick: apply, disabled: !range, children: "Appliquer" })
@@ -1686,6 +1781,7 @@ function DateRangePicker({ value, onChange, className, disabled }) {
1686
1781
  },
1687
1782
  comparable: false,
1688
1783
  presets: [],
1784
+ months: "auto",
1689
1785
  disabled,
1690
1786
  className
1691
1787
  }
@@ -1694,7 +1790,8 @@ function DateRangePicker({ value, onChange, className, disabled }) {
1694
1790
  function PresetRail({
1695
1791
  presets,
1696
1792
  selected,
1697
- onSelect
1793
+ onSelect,
1794
+ className
1698
1795
  }) {
1699
1796
  const refs = React2__namespace.useRef([]);
1700
1797
  const activeIndex = Math.max(
@@ -1704,7 +1801,7 @@ function PresetRail({
1704
1801
  const moveTo = (index) => {
1705
1802
  const next = (index + presets.length) % presets.length;
1706
1803
  refs.current[next]?.focus();
1707
- onSelect(presets[next].key);
1804
+ onSelect(presets[next].key, false);
1708
1805
  };
1709
1806
  const onKeyDown = (event, index) => {
1710
1807
  const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];
@@ -1723,7 +1820,7 @@ function PresetRail({
1723
1820
  {
1724
1821
  role: "radiogroup",
1725
1822
  "aria-label": "P\xE9riode pr\xE9d\xE9finie",
1726
- className: "flex w-44 shrink-0 flex-col gap-0.5 border-r p-2",
1823
+ className: cn("flex flex-col gap-0.5", className),
1727
1824
  children: presets.map((preset, index) => /* @__PURE__ */ jsxRuntime.jsx(
1728
1825
  "button",
1729
1826
  {
@@ -1735,9 +1832,9 @@ function PresetRail({
1735
1832
  "aria-checked": preset.key === selected,
1736
1833
  tabIndex: index === activeIndex ? 0 : -1,
1737
1834
  onKeyDown: (event) => onKeyDown(event, index),
1738
- onClick: () => onSelect(preset.key),
1835
+ onClick: () => onSelect(preset.key, true),
1739
1836
  className: cn(
1740
- "rounded px-3 py-1.5 text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1837
+ "shrink-0 rounded px-3 py-1.5 text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1741
1838
  preset.key === selected ? "bg-muted font-medium text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"
1742
1839
  ),
1743
1840
  children: preset.label
@@ -1747,17 +1844,62 @@ function PresetRail({
1747
1844
  }
1748
1845
  );
1749
1846
  }
1847
+ var TARGETS = [
1848
+ { key: "period", label: "P\xE9riode", swatch: "bg-primary" },
1849
+ { key: "compare", label: "Comparaison", swatch: "ring-2 ring-inset ring-chart-2" }
1850
+ ];
1851
+ function TargetPills({
1852
+ target,
1853
+ onSelect,
1854
+ compareEditable
1855
+ }) {
1856
+ return /* @__PURE__ */ jsxRuntime.jsx(
1857
+ "div",
1858
+ {
1859
+ role: "radiogroup",
1860
+ "aria-label": "Cible de la s\xE9lection",
1861
+ className: "flex items-center gap-1 border-b px-3 py-2",
1862
+ children: TARGETS.map((entry) => {
1863
+ const enabled = entry.key === "period" || compareEditable;
1864
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1865
+ "button",
1866
+ {
1867
+ type: "button",
1868
+ role: "radio",
1869
+ "aria-checked": target === entry.key,
1870
+ disabled: !enabled,
1871
+ tabIndex: target === entry.key ? 0 : -1,
1872
+ onKeyDown: (event) => {
1873
+ if (!["ArrowDown", "ArrowRight", "ArrowUp", "ArrowLeft"].includes(event.key)) return;
1874
+ event.preventDefault();
1875
+ if (compareEditable) onSelect(target === "period" ? "compare" : "period");
1876
+ },
1877
+ onClick: () => onSelect(entry.key),
1878
+ className: cn(
1879
+ "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40",
1880
+ target === entry.key ? "border-border bg-muted font-medium text-foreground" : "border-border text-muted-foreground hover:bg-muted hover:text-foreground"
1881
+ ),
1882
+ children: [
1883
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: cn("size-2.5 rounded-sm", entry.swatch) }),
1884
+ entry.label
1885
+ ]
1886
+ },
1887
+ entry.key
1888
+ );
1889
+ })
1890
+ }
1891
+ );
1892
+ }
1750
1893
  function CompareSection({
1751
1894
  draft,
1752
1895
  compareRange,
1753
- onModeChange,
1754
- onCustomChange
1896
+ onModeChange
1755
1897
  }) {
1756
1898
  const derivable = draft.preset !== "all";
1757
1899
  const active = activeCompare(draft);
1758
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 border-t px-3 py-2", children: [
1759
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Comparer \xE0" }),
1760
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1", children: COMPARE_MODES.map((mode) => /* @__PURE__ */ jsxRuntime.jsx(
1900
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0.5 border-t p-2", children: [
1901
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 pb-1 text-xs font-medium text-muted-foreground", children: "Comparer \xE0" }),
1902
+ COMPARE_MODES.map((mode) => /* @__PURE__ */ jsxRuntime.jsx(
1761
1903
  "button",
1762
1904
  {
1763
1905
  type: "button",
@@ -1765,44 +1907,53 @@ function CompareSection({
1765
1907
  disabled: mode !== "none" && !derivable,
1766
1908
  onClick: () => onModeChange(mode),
1767
1909
  className: cn(
1768
- "rounded-full border px-2.5 py-1 text-xs transition-colors disabled:opacity-40",
1769
- active === mode ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground hover:bg-muted hover:text-foreground"
1910
+ "shrink-0 rounded px-3 py-1.5 text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40",
1911
+ active === mode ? "bg-muted font-medium text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"
1770
1912
  ),
1771
1913
  children: COMPARE_LABELS[mode]
1772
1914
  },
1773
1915
  mode
1774
- )) }),
1775
- active === "custom" && // Two inputs rather than a second selection mode in the calendar: with
1776
- // one grid and two ranges to fill, nothing on screen would say which one
1777
- // a click is about to change.
1778
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1779
- /* @__PURE__ */ jsxRuntime.jsx(
1780
- "input",
1781
- {
1782
- type: "date",
1783
- "aria-label": "D\xE9but de la comparaison",
1784
- value: draft.compareFrom ?? "",
1785
- max: draft.compareTo,
1786
- onChange: (event) => onCustomChange(event.target.value || void 0, draft.compareTo),
1787
- className: "h-8 rounded-md border border-input bg-transparent px-2 text-sm"
1788
- }
1789
- ),
1790
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "\u2192" }),
1791
- /* @__PURE__ */ jsxRuntime.jsx(
1792
- "input",
1793
- {
1794
- type: "date",
1795
- "aria-label": "Fin de la comparaison",
1796
- value: draft.compareTo ?? "",
1797
- min: draft.compareFrom,
1798
- onChange: (event) => onCustomChange(draft.compareFrom, event.target.value || void 0),
1799
- className: "h-8 rounded-md border border-input bg-transparent px-2 text-sm"
1800
- }
1801
- )
1802
- ] }),
1803
- compareRange && active !== "custom" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: formatRange(compareRange) })
1916
+ )),
1917
+ compareRange && active !== "custom" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 pt-1 text-xs text-muted-foreground", children: formatRange(compareRange) })
1804
1918
  ] });
1805
1919
  }
1920
+ function IconCalendar({ className }) {
1921
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1922
+ "svg",
1923
+ {
1924
+ className,
1925
+ xmlns: "http://www.w3.org/2000/svg",
1926
+ viewBox: "0 0 24 24",
1927
+ fill: "none",
1928
+ stroke: "currentColor",
1929
+ strokeWidth: "2",
1930
+ strokeLinecap: "round",
1931
+ strokeLinejoin: "round",
1932
+ "aria-hidden": "true",
1933
+ children: [
1934
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 2v4M16 2v4M3 10h18" }),
1935
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "18", height: "18", x: "3", y: "4", rx: "2" })
1936
+ ]
1937
+ }
1938
+ );
1939
+ }
1940
+ function IconChevronDown({ className }) {
1941
+ return /* @__PURE__ */ jsxRuntime.jsx(
1942
+ "svg",
1943
+ {
1944
+ className,
1945
+ xmlns: "http://www.w3.org/2000/svg",
1946
+ viewBox: "0 0 24 24",
1947
+ fill: "none",
1948
+ stroke: "currentColor",
1949
+ strokeWidth: "2",
1950
+ strokeLinecap: "round",
1951
+ strokeLinejoin: "round",
1952
+ "aria-hidden": "true",
1953
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" })
1954
+ }
1955
+ );
1956
+ }
1806
1957
  var CHART_COLORS = [
1807
1958
  "var(--chart-1)",
1808
1959
  "var(--chart-2)",
@@ -2231,6 +2382,7 @@ exports.Badge = Badge;
2231
2382
  exports.BarChart = BarChart;
2232
2383
  exports.Button = Button;
2233
2384
  exports.COMPARE_LABELS = COMPARE_LABELS;
2385
+ exports.COMPARE_SHORT_LABELS = COMPARE_SHORT_LABELS;
2234
2386
  exports.Card = Card;
2235
2387
  exports.CardContent = CardContent;
2236
2388
  exports.CardDescription = CardDescription;