@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.js CHANGED
@@ -14,8 +14,8 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
14
14
  import { useReactTable, getFilteredRowModel, getSortedRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
15
15
  import { useVirtualizer } from '@tanstack/react-virtual';
16
16
  import { fr } from 'date-fns/locale/fr';
17
- import { DayPicker } from 'react-day-picker';
18
- import { subYears, subMonths, endOfMonth, startOfMonth, addQuarters, endOfQuarter, startOfQuarter, endOfYear, startOfYear, differenceInCalendarDays, subDays, parse, format, addDays } from 'date-fns';
17
+ import { DayPicker, labelDayButton } from 'react-day-picker';
18
+ import { subYears, differenceInCalendarDays, subDays, parse, format, startOfYear, subMonths, startOfQuarter, endOfMonth, startOfMonth } from 'date-fns';
19
19
  import { BarChart as BarChart$1, Bar, LineChart as LineChart$1, Line, AreaChart as AreaChart$1, Area, FunnelChart as FunnelChart$1, Funnel, Cell, LabelList, ResponsiveContainer, Tooltip, Legend, CartesianGrid, XAxis, YAxis } from 'recharts';
20
20
 
21
21
  // src/cn.ts
@@ -1079,8 +1079,15 @@ function DataTable({
1079
1079
  TableRow,
1080
1080
  {
1081
1081
  "data-state": selected.has(row.id) ? "selected" : void 0,
1082
- className: onRowClick ? "cursor-pointer" : "hover:bg-transparent",
1082
+ className: onRowClick ? "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset" : "hover:bg-transparent",
1083
+ tabIndex: onRowClick ? 0 : void 0,
1083
1084
  onClick: onRowClick ? () => onRowClick(row.original) : void 0,
1085
+ onKeyDown: onRowClick ? (event) => {
1086
+ if (event.target !== event.currentTarget) return;
1087
+ if (event.key !== "Enter" && event.key !== " ") return;
1088
+ event.preventDefault();
1089
+ onRowClick(row.original);
1090
+ } : void 0,
1084
1091
  children: [
1085
1092
  selectable && /* @__PURE__ */ jsx(TableCell, { onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsx(
1086
1093
  Checkbox,
@@ -1354,20 +1361,20 @@ var PERIOD_PRESETS = [
1354
1361
  { key: "all", label: "Tout l'historique" }
1355
1362
  ];
1356
1363
  var COMPARE_LABELS = {
1357
- none: "Aucune",
1364
+ none: "Ne pas comparer",
1358
1365
  previous_period: "P\xE9riode pr\xE9c\xE9dente",
1359
1366
  previous_year: "Ann\xE9e pr\xE9c\xE9dente",
1360
- custom: "Personnalis\xE9e"
1367
+ custom: "P\xE9riode personnalis\xE9e"
1368
+ };
1369
+ var COMPARE_SHORT_LABELS = {
1370
+ none: "",
1371
+ previous_period: "p\xE9riode pr\xE9c.",
1372
+ previous_year: "N-1",
1373
+ custom: "p\xE9riode choisie"
1361
1374
  };
1362
1375
  var DAY_FORMAT = "yyyy-MM-dd";
1363
1376
  var toDay = (date) => format(date, DAY_FORMAT);
1364
1377
  var fromDay = (day) => parse(day, DAY_FORMAT, /* @__PURE__ */ new Date());
1365
- var CALENDAR_UNIT = {
1366
- thisMonth: "month",
1367
- lastMonth: "month",
1368
- thisQuarter: "quarter",
1369
- thisYear: "year"
1370
- };
1371
1378
  function presetRange(preset, today) {
1372
1379
  const day = toDay(today);
1373
1380
  switch (preset) {
@@ -1384,17 +1391,17 @@ function presetRange(preset, today) {
1384
1391
  case "last90":
1385
1392
  return { from: toDay(subDays(today, 89)), to: day };
1386
1393
  case "thisMonth":
1387
- return { from: toDay(startOfMonth(today)), to: toDay(endOfMonth(today)) };
1394
+ return { from: toDay(startOfMonth(today)), to: day };
1388
1395
  case "lastMonth": {
1389
1396
  const previous = subMonths(today, 1);
1390
1397
  return { from: toDay(startOfMonth(previous)), to: toDay(endOfMonth(previous)) };
1391
1398
  }
1392
1399
  case "thisQuarter":
1393
- return { from: toDay(startOfQuarter(today)), to: toDay(endOfQuarter(today)) };
1400
+ return { from: toDay(startOfQuarter(today)), to: day };
1394
1401
  case "last12Months":
1395
- return { from: toDay(addDays(subYears(today, 1), 1)), to: day };
1402
+ return { from: toDay(subMonths(today, 12)), to: day };
1396
1403
  case "thisYear":
1397
- return { from: toDay(startOfYear(today)), to: toDay(endOfYear(today)) };
1404
+ return { from: toDay(startOfYear(today)), to: day };
1398
1405
  case "all":
1399
1406
  return { to: day };
1400
1407
  case "custom":
@@ -1408,19 +1415,6 @@ function comparisonRange(preset, range, compare2) {
1408
1415
  if (compare2 === "previous_year") {
1409
1416
  return { from: toDay(subYears(from, 1)), to: toDay(subYears(to, 1)) };
1410
1417
  }
1411
- const unit = CALENDAR_UNIT[preset];
1412
- if (unit === "month") {
1413
- const previous = subMonths(from, 1);
1414
- return { from: toDay(startOfMonth(previous)), to: toDay(endOfMonth(previous)) };
1415
- }
1416
- if (unit === "quarter") {
1417
- const previous = addQuarters(from, -1);
1418
- return { from: toDay(startOfQuarter(previous)), to: toDay(endOfQuarter(previous)) };
1419
- }
1420
- if (unit === "year") {
1421
- const previous = subYears(from, 1);
1422
- return { from: toDay(startOfYear(previous)), to: toDay(endOfYear(previous)) };
1423
- }
1424
1418
  const length = differenceInCalendarDays(to, from) + 1;
1425
1419
  return { from: toDay(subDays(from, length)), to: toDay(subDays(to, length)) };
1426
1420
  }
@@ -1510,12 +1504,20 @@ var DAY_PICKER_CLASS_NAMES = {
1510
1504
  today: "font-semibold text-primary",
1511
1505
  outside: "text-muted-foreground/50",
1512
1506
  disabled: "opacity-40",
1513
- selected: "bg-primary/15",
1514
- range_start: "rounded-l-md bg-primary [&_button]:bg-primary [&_button]:text-primary-foreground",
1515
- range_end: "rounded-r-md bg-primary [&_button]:bg-primary [&_button]:text-primary-foreground",
1516
- range_middle: "bg-primary/15 [&_button]:hover:bg-primary/25"
1507
+ // A cell DayPicker decided not to fill still occupies the grid, and still
1508
+ // matches the range modifiers below — without this it would show a stretch of
1509
+ // painted band with no number in it. `invisible`, not `hidden`: the six-week
1510
+ // grid keeps its height either way.
1511
+ hidden: "invisible"
1512
+ };
1513
+ var DAY_MODIFIER_CLASS_NAMES = {
1514
+ period_middle: "bg-primary/15",
1515
+ period_start: "rounded-l-md [&_button]:bg-primary [&_button]:text-primary-foreground [&_button]:hover:bg-primary",
1516
+ period_end: "rounded-r-md [&_button]:bg-primary [&_button]:text-primary-foreground [&_button]:hover:bg-primary",
1517
+ compare_middle: "bg-chart-2/30",
1518
+ 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",
1519
+ 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"
1517
1520
  };
1518
- var COMPARE_MODIFIER_CLASS = "bg-chart-2/25 ring-1 ring-inset ring-chart-2/40";
1519
1521
  function draftOf(value) {
1520
1522
  return {
1521
1523
  preset: value.preset,
@@ -1547,16 +1549,38 @@ function previewCompare(draft) {
1547
1549
  function activeCompare(draft) {
1548
1550
  return draft.preset === "all" ? "none" : draft.compare;
1549
1551
  }
1550
- function anchorMonth(range) {
1551
- if (!range) return void 0;
1552
- if (isDay(range.from)) return fromDay(range.from);
1553
- return isDay(range.to) ? fromDay(range.to) : void 0;
1552
+ function bandOf(from, to) {
1553
+ if (!isDay(from)) return void 0;
1554
+ const start = fromDay(from);
1555
+ return { from: start, to: isDay(to) ? fromDay(to) : start };
1556
+ }
1557
+ var inBand = (band, date) => band !== void 0 && date >= band.from && date <= band.to;
1558
+ var isBound = (date, bound) => bound !== void 0 && date.getTime() === bound.getTime();
1559
+ function compareBandOf(draft) {
1560
+ const active = activeCompare(draft);
1561
+ if (active === "none") return void 0;
1562
+ if (active === "custom") return bandOf(draft.compareFrom, draft.compareTo);
1563
+ const derived = previewCompare(draft);
1564
+ return derived?.from ? { from: fromDay(derived.from), to: fromDay(derived.to) } : void 0;
1565
+ }
1566
+ function anchorMonth(range, visible, today) {
1567
+ const from = isDay(range.from) ? fromDay(range.from) : void 0;
1568
+ const to = isDay(range.to) ? fromDay(range.to) : void 0;
1569
+ if (visible === 2) return from ?? to;
1570
+ if (!to) return from;
1571
+ if (to <= today) return to;
1572
+ return from && from <= today ? today : from ?? today;
1573
+ }
1574
+ function selectionOf(from, to) {
1575
+ if (!isDay(from)) return void 0;
1576
+ return { from: fromDay(from), to: isDay(to) ? fromDay(to) : void 0 };
1554
1577
  }
1555
1578
  function DateRangeCompare({
1556
1579
  value,
1557
1580
  onChange,
1558
1581
  comparable = true,
1559
1582
  presets = PERIOD_PRESETS,
1583
+ months = 1,
1560
1584
  align = "start",
1561
1585
  disabled,
1562
1586
  className
@@ -1564,44 +1588,76 @@ function DateRangeCompare({
1564
1588
  const [open, setOpen] = React2.useState(false);
1565
1589
  const [draft, setDraft] = React2.useState(() => draftOf(value));
1566
1590
  const [month, setMonth] = React2.useState(void 0);
1567
- const [months, setMonths] = React2.useState(1);
1568
- const showRange = (range2) => {
1569
- setMonth(anchorMonth(range2));
1570
- setMonths(spansTwoMonths(range2) ? 2 : 1);
1591
+ const [autoMonths, setAutoMonths] = React2.useState(1);
1592
+ const [target, setTarget] = React2.useState("period");
1593
+ const [today, setToday] = React2.useState(() => /* @__PURE__ */ new Date());
1594
+ const visibleMonths = months === "auto" ? autoMonths : months;
1595
+ const showRange = (range2, at = today) => {
1596
+ const next = months === "auto" ? spansTwoMonths(range2) ? 2 : 1 : months;
1597
+ setAutoMonths(next);
1598
+ setMonth(anchorMonth(range2, next, at));
1571
1599
  };
1572
1600
  const openPanel = () => {
1601
+ const now = /* @__PURE__ */ new Date();
1602
+ setToday(now);
1573
1603
  setDraft(draftOf(value));
1574
- showRange(value.range);
1604
+ setTarget("period");
1605
+ showRange(value.range, now);
1575
1606
  setOpen(true);
1576
1607
  };
1577
- const pickPreset = (preset) => {
1578
- const resolved = periodFromPreset(preset, /* @__PURE__ */ new Date(), activeCompare(draft));
1579
- setDraft({
1608
+ const range = rangeOf(draft);
1609
+ const compareRange = previewCompare(draft);
1610
+ const compareEditable = comparable && activeCompare(draft) === "custom";
1611
+ const activeTarget = compareEditable ? target : "period";
1612
+ const periodBand = bandOf(draft.from, draft.to);
1613
+ const compareBand = compareBandOf(draft);
1614
+ const emit = (candidate) => {
1615
+ const complete = rangeOf(candidate);
1616
+ if (!complete) return false;
1617
+ onChange(
1618
+ makePeriod(candidate.preset, complete, activeCompare(candidate), customCompareOf(candidate))
1619
+ );
1620
+ setOpen(false);
1621
+ return true;
1622
+ };
1623
+ const apply = () => emit(draft);
1624
+ const pickPreset = (preset, commit) => {
1625
+ const resolved = periodFromPreset(preset, today, activeCompare(draft));
1626
+ const next = {
1580
1627
  ...draftOf(resolved),
1581
1628
  // The rail changes the period, not the comparison: the mode the user chose
1582
1629
  // survives, and `activeCompare` decides whether it can apply here.
1583
1630
  compare: draft.compare,
1584
1631
  compareFrom: draft.compareFrom,
1585
1632
  compareTo: draft.compareTo
1586
- });
1633
+ };
1634
+ setDraft(next);
1635
+ setTarget("period");
1587
1636
  showRange(resolved.range);
1637
+ if (!comparable && commit) emit(next);
1588
1638
  };
1589
1639
  const pickDays = (selected) => {
1590
- setDraft({
1591
- ...draft,
1592
- preset: "custom",
1593
- from: selected?.from ? toDay(selected.from) : void 0,
1594
- to: selected?.to ? toDay(selected.to) : void 0
1595
- });
1640
+ const from = selected?.from ? toDay(selected.from) : void 0;
1641
+ const to = selected?.to ? toDay(selected.to) : void 0;
1642
+ if (activeTarget === "compare") {
1643
+ setDraft({ ...draft, compare: "custom", compareFrom: from, compareTo: to });
1644
+ return;
1645
+ }
1646
+ setDraft({ ...draft, preset: "custom", from, to });
1596
1647
  };
1597
- const range = rangeOf(draft);
1598
- const compareRange = previewCompare(draft);
1599
- const apply = () => {
1600
- if (!range) return;
1601
- onChange(makePeriod(draft.preset, range, activeCompare(draft), customCompareOf(draft)));
1602
- setOpen(false);
1648
+ const chooseCompare = (mode) => {
1649
+ const next = { ...draft, compare: mode };
1650
+ setDraft(next);
1651
+ setTarget(mode === "custom" ? "compare" : "period");
1652
+ const shown = mode === "custom" ? customCompareOf(next) : range;
1653
+ if (shown) showRange(shown);
1654
+ };
1655
+ const focusTarget = (next) => {
1656
+ setTarget(next);
1657
+ const shown = next === "compare" ? customCompareOf(draft) : range;
1658
+ if (shown) showRange(shown);
1603
1659
  };
1604
- const selectedDays = isDay(draft.from) ? { from: fromDay(draft.from), to: isDay(draft.to) ? fromDay(draft.to) : void 0 } : void 0;
1660
+ 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";
1605
1661
  return /* @__PURE__ */ jsx(
1606
1662
  Popover,
1607
1663
  {
@@ -1609,42 +1665,81 @@ function DateRangeCompare({
1609
1665
  onOpenChange: (next) => next ? openPanel() : setOpen(false),
1610
1666
  align,
1611
1667
  className: cn("w-auto p-0", className),
1612
- trigger: /* @__PURE__ */ jsx(Button, { variant: "outline", disabled, children: formatPeriod(value) }),
1613
- children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
1614
- presets.length > 0 && /* @__PURE__ */ jsx(PresetRail, { presets, selected: draft.preset, onSelect: pickPreset }),
1668
+ trigger: /* @__PURE__ */ jsxs(Button, { variant: "outline", disabled, children: [
1669
+ /* @__PURE__ */ jsx(IconCalendar, { className: "size-4 text-muted-foreground" }),
1670
+ /* @__PURE__ */ jsx("span", { children: formatPeriod(value) }),
1671
+ value.compare !== "none" && value.compareRange !== void 0 && /* @__PURE__ */ jsxs("span", { className: "rounded-md bg-chart-2/15 px-1.5 py-0.5 text-xs font-medium text-chart-2", children: [
1672
+ "vs ",
1673
+ COMPARE_SHORT_LABELS[value.compare]
1674
+ ] }),
1675
+ /* @__PURE__ */ jsx(IconChevronDown, { className: "size-4 text-muted-foreground" })
1676
+ ] }),
1677
+ children: /* @__PURE__ */ jsxs("div", { className: "flex max-h-[80vh] items-stretch", children: [
1678
+ (presets.length > 0 || comparable) && /* @__PURE__ */ jsxs("div", { className: "flex w-48 shrink-0 flex-col border-r", children: [
1679
+ presets.length > 0 && /* @__PURE__ */ jsx(
1680
+ PresetRail,
1681
+ {
1682
+ presets,
1683
+ selected: draft.preset,
1684
+ onSelect: pickPreset,
1685
+ className: "min-h-0 flex-1 overflow-y-auto p-2"
1686
+ }
1687
+ ),
1688
+ comparable && /* @__PURE__ */ jsx(
1689
+ CompareSection,
1690
+ {
1691
+ draft,
1692
+ compareRange,
1693
+ onModeChange: chooseCompare
1694
+ }
1695
+ )
1696
+ ] }),
1615
1697
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
1698
+ comparable && /* @__PURE__ */ jsx(
1699
+ TargetPills,
1700
+ {
1701
+ target: activeTarget,
1702
+ onSelect: focusTarget,
1703
+ compareEditable
1704
+ }
1705
+ ),
1616
1706
  /* @__PURE__ */ jsx("div", { className: "relative p-3", children: /* @__PURE__ */ jsx(
1617
1707
  DayPicker,
1618
1708
  {
1619
1709
  mode: "range",
1620
1710
  locale: fr,
1621
1711
  weekStartsOn: 1,
1622
- numberOfMonths: months,
1712
+ numberOfMonths: visibleMonths,
1623
1713
  month,
1624
1714
  onMonthChange: setMonth,
1625
- selected: selectedDays,
1715
+ today,
1716
+ disabled: { after: today },
1717
+ fixedWeeks: true,
1718
+ showOutsideDays: visibleMonths === 1,
1719
+ selected: activeTarget === "compare" ? selectionOf(draft.compareFrom, draft.compareTo) : selectionOf(draft.from, draft.to),
1626
1720
  onSelect: pickDays,
1627
1721
  modifiers: {
1628
- compare: compareRange?.from ? [{ from: fromDay(compareRange.from), to: fromDay(compareRange.to) }] : []
1722
+ period_middle: periodBand ? [{ from: periodBand.from, to: periodBand.to }] : [],
1723
+ period_start: periodBand ? [periodBand.from] : [],
1724
+ period_end: periodBand ? [periodBand.to] : [],
1725
+ // The period wins any cell both ranges claim.
1726
+ compare_middle: (date) => inBand(compareBand, date) && !inBand(periodBand, date),
1727
+ compare_start: (date) => isBound(date, compareBand?.from) && !inBand(periodBand, date),
1728
+ compare_end: (date) => isBound(date, compareBand?.to) && !inBand(periodBand, date)
1629
1729
  },
1630
- modifiersClassNames: { compare: COMPARE_MODIFIER_CLASS },
1631
- classNames: DAY_PICKER_CLASS_NAMES
1730
+ modifiersClassNames: DAY_MODIFIER_CLASS_NAMES,
1731
+ classNames: DAY_PICKER_CLASS_NAMES,
1732
+ "aria-label": activeTarget === "compare" ? "Calendrier \u2014 s\xE9lection de la comparaison" : "Calendrier \u2014 s\xE9lection de la p\xE9riode",
1733
+ labels: {
1734
+ // Suffixed rather than replaced: which range a click lands in is
1735
+ // the one thing a screen reader cannot get from the grid, and the
1736
+ // default label still carries the date and its state.
1737
+ labelDayButton: (date, modifiers, options, dateLib) => `${labelDayButton(date, modifiers, options, dateLib)} \u2014 ${activeTarget === "compare" ? "comparaison" : "p\xE9riode"}`
1738
+ }
1632
1739
  }
1633
1740
  ) }),
1634
- comparable && /* @__PURE__ */ jsx(
1635
- CompareSection,
1636
- {
1637
- draft,
1638
- compareRange,
1639
- onModeChange: (compare2) => setDraft({ ...draft, compare: compare2 }),
1640
- onCustomChange: (from, to) => setDraft({ ...draft, compareFrom: from, compareTo: to })
1641
- }
1642
- ),
1643
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 border-t px-3 py-2", children: [
1644
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1645
- range ? formatRange(range) : "Choisissez une date de fin",
1646
- compareRange && ` vs ${formatRange(compareRange)}`
1647
- ] }),
1741
+ /* @__PURE__ */ jsxs("div", { className: "mt-auto flex items-center justify-between gap-4 border-t px-3 py-2", children: [
1742
+ /* @__PURE__ */ jsx("p", { role: "status", "aria-live": "polite", className: "text-xs text-muted-foreground", children: footer }),
1648
1743
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1649
1744
  /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setOpen(false), children: "Annuler" }),
1650
1745
  /* @__PURE__ */ jsx(Button, { size: "sm", onClick: apply, disabled: !range, children: "Appliquer" })
@@ -1665,6 +1760,7 @@ function DateRangePicker({ value, onChange, className, disabled }) {
1665
1760
  },
1666
1761
  comparable: false,
1667
1762
  presets: [],
1763
+ months: "auto",
1668
1764
  disabled,
1669
1765
  className
1670
1766
  }
@@ -1673,7 +1769,8 @@ function DateRangePicker({ value, onChange, className, disabled }) {
1673
1769
  function PresetRail({
1674
1770
  presets,
1675
1771
  selected,
1676
- onSelect
1772
+ onSelect,
1773
+ className
1677
1774
  }) {
1678
1775
  const refs = React2.useRef([]);
1679
1776
  const activeIndex = Math.max(
@@ -1683,7 +1780,7 @@ function PresetRail({
1683
1780
  const moveTo = (index) => {
1684
1781
  const next = (index + presets.length) % presets.length;
1685
1782
  refs.current[next]?.focus();
1686
- onSelect(presets[next].key);
1783
+ onSelect(presets[next].key, false);
1687
1784
  };
1688
1785
  const onKeyDown = (event, index) => {
1689
1786
  const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];
@@ -1702,7 +1799,7 @@ function PresetRail({
1702
1799
  {
1703
1800
  role: "radiogroup",
1704
1801
  "aria-label": "P\xE9riode pr\xE9d\xE9finie",
1705
- className: "flex w-44 shrink-0 flex-col gap-0.5 border-r p-2",
1802
+ className: cn("flex flex-col gap-0.5", className),
1706
1803
  children: presets.map((preset, index) => /* @__PURE__ */ jsx(
1707
1804
  "button",
1708
1805
  {
@@ -1714,9 +1811,9 @@ function PresetRail({
1714
1811
  "aria-checked": preset.key === selected,
1715
1812
  tabIndex: index === activeIndex ? 0 : -1,
1716
1813
  onKeyDown: (event) => onKeyDown(event, index),
1717
- onClick: () => onSelect(preset.key),
1814
+ onClick: () => onSelect(preset.key, true),
1718
1815
  className: cn(
1719
- "rounded px-3 py-1.5 text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1816
+ "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",
1720
1817
  preset.key === selected ? "bg-muted font-medium text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"
1721
1818
  ),
1722
1819
  children: preset.label
@@ -1726,17 +1823,62 @@ function PresetRail({
1726
1823
  }
1727
1824
  );
1728
1825
  }
1826
+ var TARGETS = [
1827
+ { key: "period", label: "P\xE9riode", swatch: "bg-primary" },
1828
+ { key: "compare", label: "Comparaison", swatch: "ring-2 ring-inset ring-chart-2" }
1829
+ ];
1830
+ function TargetPills({
1831
+ target,
1832
+ onSelect,
1833
+ compareEditable
1834
+ }) {
1835
+ return /* @__PURE__ */ jsx(
1836
+ "div",
1837
+ {
1838
+ role: "radiogroup",
1839
+ "aria-label": "Cible de la s\xE9lection",
1840
+ className: "flex items-center gap-1 border-b px-3 py-2",
1841
+ children: TARGETS.map((entry) => {
1842
+ const enabled = entry.key === "period" || compareEditable;
1843
+ return /* @__PURE__ */ jsxs(
1844
+ "button",
1845
+ {
1846
+ type: "button",
1847
+ role: "radio",
1848
+ "aria-checked": target === entry.key,
1849
+ disabled: !enabled,
1850
+ tabIndex: target === entry.key ? 0 : -1,
1851
+ onKeyDown: (event) => {
1852
+ if (!["ArrowDown", "ArrowRight", "ArrowUp", "ArrowLeft"].includes(event.key)) return;
1853
+ event.preventDefault();
1854
+ if (compareEditable) onSelect(target === "period" ? "compare" : "period");
1855
+ },
1856
+ onClick: () => onSelect(entry.key),
1857
+ className: cn(
1858
+ "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",
1859
+ target === entry.key ? "border-border bg-muted font-medium text-foreground" : "border-border text-muted-foreground hover:bg-muted hover:text-foreground"
1860
+ ),
1861
+ children: [
1862
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn("size-2.5 rounded-sm", entry.swatch) }),
1863
+ entry.label
1864
+ ]
1865
+ },
1866
+ entry.key
1867
+ );
1868
+ })
1869
+ }
1870
+ );
1871
+ }
1729
1872
  function CompareSection({
1730
1873
  draft,
1731
1874
  compareRange,
1732
- onModeChange,
1733
- onCustomChange
1875
+ onModeChange
1734
1876
  }) {
1735
1877
  const derivable = draft.preset !== "all";
1736
1878
  const active = activeCompare(draft);
1737
- return /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t px-3 py-2", children: [
1738
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Comparer \xE0" }),
1739
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: COMPARE_MODES.map((mode) => /* @__PURE__ */ jsx(
1879
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5 border-t p-2", children: [
1880
+ /* @__PURE__ */ jsx("p", { className: "px-3 pb-1 text-xs font-medium text-muted-foreground", children: "Comparer \xE0" }),
1881
+ COMPARE_MODES.map((mode) => /* @__PURE__ */ jsx(
1740
1882
  "button",
1741
1883
  {
1742
1884
  type: "button",
@@ -1744,44 +1886,53 @@ function CompareSection({
1744
1886
  disabled: mode !== "none" && !derivable,
1745
1887
  onClick: () => onModeChange(mode),
1746
1888
  className: cn(
1747
- "rounded-full border px-2.5 py-1 text-xs transition-colors disabled:opacity-40",
1748
- active === mode ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground hover:bg-muted hover:text-foreground"
1889
+ "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",
1890
+ active === mode ? "bg-muted font-medium text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"
1749
1891
  ),
1750
1892
  children: COMPARE_LABELS[mode]
1751
1893
  },
1752
1894
  mode
1753
- )) }),
1754
- active === "custom" && // Two inputs rather than a second selection mode in the calendar: with
1755
- // one grid and two ranges to fill, nothing on screen would say which one
1756
- // a click is about to change.
1757
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1758
- /* @__PURE__ */ jsx(
1759
- "input",
1760
- {
1761
- type: "date",
1762
- "aria-label": "D\xE9but de la comparaison",
1763
- value: draft.compareFrom ?? "",
1764
- max: draft.compareTo,
1765
- onChange: (event) => onCustomChange(event.target.value || void 0, draft.compareTo),
1766
- className: "h-8 rounded-md border border-input bg-transparent px-2 text-sm"
1767
- }
1768
- ),
1769
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2192" }),
1770
- /* @__PURE__ */ jsx(
1771
- "input",
1772
- {
1773
- type: "date",
1774
- "aria-label": "Fin de la comparaison",
1775
- value: draft.compareTo ?? "",
1776
- min: draft.compareFrom,
1777
- onChange: (event) => onCustomChange(draft.compareFrom, event.target.value || void 0),
1778
- className: "h-8 rounded-md border border-input bg-transparent px-2 text-sm"
1779
- }
1780
- )
1781
- ] }),
1782
- compareRange && active !== "custom" && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: formatRange(compareRange) })
1895
+ )),
1896
+ compareRange && active !== "custom" && /* @__PURE__ */ jsx("p", { className: "px-3 pt-1 text-xs text-muted-foreground", children: formatRange(compareRange) })
1783
1897
  ] });
1784
1898
  }
1899
+ function IconCalendar({ className }) {
1900
+ return /* @__PURE__ */ jsxs(
1901
+ "svg",
1902
+ {
1903
+ className,
1904
+ xmlns: "http://www.w3.org/2000/svg",
1905
+ viewBox: "0 0 24 24",
1906
+ fill: "none",
1907
+ stroke: "currentColor",
1908
+ strokeWidth: "2",
1909
+ strokeLinecap: "round",
1910
+ strokeLinejoin: "round",
1911
+ "aria-hidden": "true",
1912
+ children: [
1913
+ /* @__PURE__ */ jsx("path", { d: "M8 2v4M16 2v4M3 10h18" }),
1914
+ /* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "4", rx: "2" })
1915
+ ]
1916
+ }
1917
+ );
1918
+ }
1919
+ function IconChevronDown({ className }) {
1920
+ return /* @__PURE__ */ jsx(
1921
+ "svg",
1922
+ {
1923
+ className,
1924
+ xmlns: "http://www.w3.org/2000/svg",
1925
+ viewBox: "0 0 24 24",
1926
+ fill: "none",
1927
+ stroke: "currentColor",
1928
+ strokeWidth: "2",
1929
+ strokeLinecap: "round",
1930
+ strokeLinejoin: "round",
1931
+ "aria-hidden": "true",
1932
+ children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
1933
+ }
1934
+ );
1935
+ }
1785
1936
  var CHART_COLORS = [
1786
1937
  "var(--chart-1)",
1787
1938
  "var(--chart-2)",
@@ -2202,6 +2353,6 @@ function Tooltip2({ label, children, side = "top", className }) {
2202
2353
  ] });
2203
2354
  }
2204
2355
 
2205
- export { AppApiProvider, AppContextProvider, AppInvokeError, AreaChart, Badge, BarChart, Button, COMPARE_LABELS, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, CopyButton, DataTable, DateRangeCompare, DateRangePicker, DropdownMenu, FunnelChart, Input, KpiCard, LineChart, Link, Modal, PERIOD_PRESETS, Popover, Select, SidePanel, Spinner, StackedBarChart, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ToastProvider, Tooltip2 as Tooltip, badgeVariants, buttonVariants, cn, comparisonRange, copyToClipboard, downloadBlob, downloadCsv, formatPeriod, formatRange, makePeriod, periodFromPreset, relativeDelta, toCsv, useAppApi, useAppContext, useAppRun, useAppRuns, useAppSettings, useToast };
2356
+ export { AppApiProvider, AppContextProvider, AppInvokeError, AreaChart, Badge, BarChart, Button, COMPARE_LABELS, COMPARE_SHORT_LABELS, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, CopyButton, DataTable, DateRangeCompare, DateRangePicker, DropdownMenu, FunnelChart, Input, KpiCard, LineChart, Link, Modal, PERIOD_PRESETS, Popover, Select, SidePanel, Spinner, StackedBarChart, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ToastProvider, Tooltip2 as Tooltip, badgeVariants, buttonVariants, cn, comparisonRange, copyToClipboard, downloadBlob, downloadCsv, formatPeriod, formatRange, makePeriod, periodFromPreset, relativeDelta, toCsv, useAppApi, useAppContext, useAppRun, useAppRuns, useAppSettings, useToast };
2206
2357
  //# sourceMappingURL=index.js.map
2207
2358
  //# sourceMappingURL=index.js.map