@epa-datos/ui 0.1.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.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/dist/components/epa/conversion-funnel.d.ts +17 -0
  4. package/dist/components/epa/conversion-funnel.js +52 -0
  5. package/dist/components/epa/date-range-picker.d.ts +42 -0
  6. package/dist/components/epa/date-range-picker.js +184 -0
  7. package/dist/components/epa/heatmap.d.ts +25 -0
  8. package/dist/components/epa/heatmap.js +79 -0
  9. package/dist/components/epa/metric-card-group.d.ts +41 -0
  10. package/dist/components/epa/metric-card-group.js +47 -0
  11. package/dist/components/ui/accordion.d.ts +6 -0
  12. package/dist/components/ui/accordion.js +17 -0
  13. package/dist/components/ui/alert-dialog.d.ts +18 -0
  14. package/dist/components/ui/alert-dialog.js +42 -0
  15. package/dist/components/ui/alert.d.ts +10 -0
  16. package/dist/components/ui/alert.js +30 -0
  17. package/dist/components/ui/aspect-ratio.d.ts +4 -0
  18. package/dist/components/ui/aspect-ratio.js +8 -0
  19. package/dist/components/ui/attachment.d.ts +23 -0
  20. package/dist/components/ui/attachment.js +68 -0
  21. package/dist/components/ui/avatar.d.ts +11 -0
  22. package/dist/components/ui/avatar.js +23 -0
  23. package/dist/components/ui/badge.d.ts +7 -0
  24. package/dist/components/ui/badge.js +36 -0
  25. package/dist/components/ui/breadcrumb.d.ts +10 -0
  26. package/dist/components/ui/breadcrumb.js +36 -0
  27. package/dist/components/ui/bubble.d.ts +16 -0
  28. package/dist/components/ui/bubble.js +59 -0
  29. package/dist/components/ui/button-group.d.ts +10 -0
  30. package/dist/components/ui/button-group.js +36 -0
  31. package/dist/components/ui/button.d.ts +8 -0
  32. package/dist/components/ui/button.js +34 -0
  33. package/dist/components/ui/calendar.d.ts +10 -0
  34. package/dist/components/ui/calendar.js +76 -0
  35. package/dist/components/ui/card.d.ts +12 -0
  36. package/dist/components/ui/card.js +33 -0
  37. package/dist/components/ui/carousel.d.ts +28 -0
  38. package/dist/components/ui/carousel.js +91 -0
  39. package/dist/components/ui/chart.d.ts +44 -0
  40. package/dist/components/ui/chart.js +132 -0
  41. package/dist/components/ui/checkbox.d.ts +3 -0
  42. package/dist/components/ui/checkbox.js +9 -0
  43. package/dist/components/ui/collapsible.d.ts +5 -0
  44. package/dist/components/ui/collapsible.js +13 -0
  45. package/dist/components/ui/combobox.d.ts +24 -0
  46. package/dist/components/ui/combobox.js +58 -0
  47. package/dist/components/ui/command.d.ts +19 -0
  48. package/dist/components/ui/command.js +35 -0
  49. package/dist/components/ui/context-menu.d.ts +29 -0
  50. package/dist/components/ui/context-menu.js +51 -0
  51. package/dist/components/ui/dialog.d.ts +17 -0
  52. package/dist/components/ui/dialog.js +37 -0
  53. package/dist/components/ui/direction.d.ts +1 -0
  54. package/dist/components/ui/direction.js +2 -0
  55. package/dist/components/ui/drawer.d.ts +16 -0
  56. package/dist/components/ui/drawer.js +75 -0
  57. package/dist/components/ui/dropdown-menu.d.ts +29 -0
  58. package/dist/components/ui/dropdown-menu.js +51 -0
  59. package/dist/components/ui/empty.d.ts +11 -0
  60. package/dist/components/ui/empty.js +33 -0
  61. package/dist/components/ui/field.d.ts +24 -0
  62. package/dist/components/ui/field.js +68 -0
  63. package/dist/components/ui/hover-card.d.ts +5 -0
  64. package/dist/components/ui/hover-card.js +14 -0
  65. package/dist/components/ui/input-group.d.ts +18 -0
  66. package/dist/components/ui/input-group.js +57 -0
  67. package/dist/components/ui/input-otp.d.ts +11 -0
  68. package/dist/components/ui/input-otp.js +21 -0
  69. package/dist/components/ui/input.d.ts +3 -0
  70. package/dist/components/ui/input.js +7 -0
  71. package/dist/components/ui/item.d.ts +22 -0
  72. package/dist/components/ui/item.js +78 -0
  73. package/dist/components/ui/kbd.d.ts +3 -0
  74. package/dist/components/ui/kbd.js +9 -0
  75. package/dist/components/ui/label.d.ts +3 -0
  76. package/dist/components/ui/label.js +7 -0
  77. package/dist/components/ui/marker.d.ts +10 -0
  78. package/dist/components/ui/marker.js +34 -0
  79. package/dist/components/ui/menubar.d.ts +29 -0
  80. package/dist/components/ui/menubar.js +56 -0
  81. package/dist/components/ui/message-scroller.d.ts +10 -0
  82. package/dist/components/ui/message-scroller.js +25 -0
  83. package/dist/components/ui/message.d.ts +10 -0
  84. package/dist/components/ui/message.js +21 -0
  85. package/dist/components/ui/native-select.d.ts +8 -0
  86. package/dist/components/ui/native-select.js +13 -0
  87. package/dist/components/ui/navigation-menu.d.ts +11 -0
  88. package/dist/components/ui/navigation-menu.js +31 -0
  89. package/dist/components/ui/pagination.d.ts +17 -0
  90. package/dist/components/ui/pagination.js +26 -0
  91. package/dist/components/ui/popover.d.ts +9 -0
  92. package/dist/components/ui/popover.js +23 -0
  93. package/dist/components/ui/progress.d.ts +7 -0
  94. package/dist/components/ui/progress.js +20 -0
  95. package/dist/components/ui/questionnaire.d.ts +19 -0
  96. package/dist/components/ui/questionnaire.js +52 -0
  97. package/dist/components/ui/radio-group.d.ts +5 -0
  98. package/dist/components/ui/radio-group.js +12 -0
  99. package/dist/components/ui/resizable.d.ts +7 -0
  100. package/dist/components/ui/resizable.js +14 -0
  101. package/dist/components/ui/scroll-area.d.ts +5 -0
  102. package/dist/components/ui/scroll-area.js +11 -0
  103. package/dist/components/ui/select.d.ts +15 -0
  104. package/dist/components/ui/select.js +34 -0
  105. package/dist/components/ui/separator.d.ts +3 -0
  106. package/dist/components/ui/separator.js +8 -0
  107. package/dist/components/ui/sheet.d.ts +14 -0
  108. package/dist/components/ui/sheet.js +37 -0
  109. package/dist/components/ui/sidebar.d.ts +63 -0
  110. package/dist/components/ui/sidebar.js +256 -0
  111. package/dist/components/ui/skeleton.d.ts +2 -0
  112. package/dist/components/ui/skeleton.js +6 -0
  113. package/dist/components/ui/slider.d.ts +3 -0
  114. package/dist/components/ui/slider.js +12 -0
  115. package/dist/components/ui/spinner.d.ts +2 -0
  116. package/dist/components/ui/spinner.js +7 -0
  117. package/dist/components/ui/switch.d.ts +5 -0
  118. package/dist/components/ui/switch.js +8 -0
  119. package/dist/components/ui/table.d.ts +10 -0
  120. package/dist/components/ui/table.js +28 -0
  121. package/dist/components/ui/tabs.d.ts +10 -0
  122. package/dist/components/ui/tabs.js +29 -0
  123. package/dist/components/ui/textarea.d.ts +3 -0
  124. package/dist/components/ui/textarea.js +6 -0
  125. package/dist/components/ui/toast.d.ts +16 -0
  126. package/dist/components/ui/toast.js +66 -0
  127. package/dist/components/ui/toggle-group.d.ts +11 -0
  128. package/dist/components/ui/toggle-group.js +24 -0
  129. package/dist/components/ui/toggle.d.ts +8 -0
  130. package/dist/components/ui/toggle.js +26 -0
  131. package/dist/components/ui/tooltip.d.ts +6 -0
  132. package/dist/components/ui/tooltip.js +17 -0
  133. package/dist/hooks/use-mobile.d.ts +1 -0
  134. package/dist/hooks/use-mobile.js +15 -0
  135. package/dist/lib/utils.d.ts +2 -0
  136. package/dist/lib/utils.js +5 -0
  137. package/dist/styles/tokens.css +200 -0
  138. package/package.json +100 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 EPA Digital
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # @epa-datos/ui
2
+
3
+ React + Tailwind dashboard components built on [Base UI](https://base-ui.com), published as source (not a bundle) so the components stay fully readable and themeable in the projects that install them.
4
+
5
+ There's no barrel `index` — each file is imported directly, the same way it's imported inside this repo:
6
+
7
+ ```tsx
8
+ import { Button } from "@epa-datos/ui/components/ui/button"
9
+ import { Badge } from "@epa-datos/ui/components/ui/badge"
10
+ import { Heatmap } from "@epa-datos/ui/components/epa/heatmap"
11
+ import { cn } from "@epa-datos/ui/lib/utils"
12
+ ```
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install @epa-datos/ui
18
+ ```
19
+
20
+ `react`/`react-dom` ^19 are peer dependencies — everything else the components need (Base UI, Recharts, date-fns, lucide-react, etc.) installs automatically.
21
+
22
+ ## Set up styles
23
+
24
+ Tailwind v4 project required. In your global stylesheet:
25
+
26
+ ```css
27
+ @import "tailwindcss";
28
+ @import "@epa-datos/ui/styles/tokens.css";
29
+ ```
30
+
31
+ That one import brings in everything the components rely on: the semantic color tokens (`--primary`, `--success`, `--warning`, `--info`, `--chart-1..10`, etc.) in light and dark, the `dark:`/`short:` custom variants, and the animation/data-state utilities components use for open/close transitions. Override any token by redeclaring it after the import — see [Theming](#theming) below.
32
+
33
+ Your project also needs to supply two font variables the components inherit rather than hardcode:
34
+
35
+ ```ts
36
+ // wherever you load fonts (e.g. app/layout.tsx in Next.js)
37
+ const sans = YourSansFont({ variable: "--font-sans" })
38
+ const mono = YourMonoFont({ variable: "--font-mono" }) // optional, for code/tabular content
39
+ ```
40
+
41
+ ## Global providers
42
+
43
+ Mount these once near the root of your app — components assume they exist, the same way they do in this repo's own `app/layout.tsx`:
44
+
45
+ ```tsx
46
+ import { ThemeProvider } from "next-themes"
47
+ import { TooltipProvider } from "@epa-datos/ui/components/ui/tooltip"
48
+ import { Toaster } from "@epa-datos/ui/components/ui/toast"
49
+
50
+ export default function RootLayout({ children }) {
51
+ return (
52
+ <ThemeProvider attribute="class" defaultTheme="light">
53
+ <TooltipProvider>
54
+ {children}
55
+ <Toaster />
56
+ </TooltipProvider>
57
+ </ThemeProvider>
58
+ )
59
+ }
60
+ ```
61
+
62
+ ## Theming
63
+
64
+ All colors are OKLCH CSS variables — never hardcoded in component source — so re-theming means overriding variables, not editing component files:
65
+
66
+ ```css
67
+ @import "tailwindcss";
68
+ @import "@epa-datos/ui/styles/tokens.css";
69
+
70
+ :root {
71
+ --primary: oklch(0.55 0.2 250);
72
+ }
73
+ .dark {
74
+ --primary: oklch(0.65 0.2 250);
75
+ }
76
+ ```
77
+
78
+ ## Browsing components
79
+
80
+ This repo is also the showcase — every component and its variants are demoed in `components/showcase/demos/*`. Clone the repo and run `pnpm dev` to browse them interactively, or read the source directly under `components/ui/` and `components/epa/`.
81
+
82
+ ## Conventions
83
+
84
+ Full component-selection guidance ("which component fits this request"), styling rules, and the reasoning behind the design tokens live in [`AGENTS.md`](./AGENTS.md) — written for both human contributors and coding agents working in this repo.
85
+
86
+ ## Contributing / local development
87
+
88
+ ```bash
89
+ pnpm install
90
+ pnpm dev # showcase app at localhost:3000
91
+ pnpm build:lib # builds the publishable package into dist/
92
+ ```
93
+
94
+ Component and design-token changes are made here, in `epa-ui`, not in projects that depend on the package — see `AGENTS.md` for the full editing conventions.
95
+
96
+ ## License
97
+
98
+ MIT
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ type ConversionFunnelStage = {
3
+ label: string;
4
+ value: number;
5
+ /** % change vs. the same stage in the previous period; omit to hide the trend badge */
6
+ previousPeriodDelta?: number;
7
+ };
8
+ type ConversionFunnelProps = {
9
+ stages: ConversionFunnelStage[];
10
+ formatValue?: (value: number) => string;
11
+ formatDelta?: (delta: number) => string;
12
+ formatDropOff?: (percent: number) => React.ReactNode;
13
+ className?: string;
14
+ };
15
+ declare function ConversionFunnel({ stages, formatValue, formatDelta, formatDropOff, className, }: ConversionFunnelProps): React.JSX.Element;
16
+ export { ConversionFunnel };
17
+ export type { ConversionFunnelProps, ConversionFunnelStage };
@@ -0,0 +1,52 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Progress as ProgressPrimitive } from "@base-ui/react/progress";
4
+ import { ArrowDownIcon, ArrowDownRightIcon, ArrowUpRightIcon } from "lucide-react";
5
+ import { cn } from "../../lib/utils.js";
6
+ import { Badge } from "../../components/ui/badge.js";
7
+ import { ProgressIndicator, ProgressLabel, ProgressTrack } from "../../components/ui/progress.js";
8
+ // Segments narrower than this can't fit their value label without clipping it (see the
9
+ // reference screenshot, where a narrow stage's compact-unit suffix gets cut off) — flip the
10
+ // label to sit just past the bar instead of inside it.
11
+ const NARROW_SEGMENT_THRESHOLD = 18;
12
+ function defaultFormatValue(value) {
13
+ if (Math.abs(value) >= 1000000) {
14
+ return `${(value / 1000000).toFixed(2).replace(/\.?0+$/, "")}M`;
15
+ }
16
+ if (Math.abs(value) >= 1000) {
17
+ return `${(value / 1000).toFixed(1).replace(/\.0$/, "")}K`;
18
+ }
19
+ return value.toLocaleString();
20
+ }
21
+ function defaultFormatDelta(delta) {
22
+ return `${delta > 0 ? "+" : ""}${delta.toFixed(1)}%`;
23
+ }
24
+ function defaultFormatDropOff(percent) {
25
+ return percent >= 0
26
+ ? `${percent.toFixed(1)}% de caída`
27
+ : `${Math.abs(percent).toFixed(1)}% de alza`;
28
+ }
29
+ function TrendBadge({ delta, formatDelta, }) {
30
+ const isPositive = delta >= 0;
31
+ const Icon = isPositive ? ArrowUpRightIcon : ArrowDownRightIcon;
32
+ return (_jsxs(Badge, { variant: isPositive ? "success" : "destructive", className: "gap-0.5", children: [_jsx(Icon, { "data-icon": "inline-start" }), formatDelta(delta)] }));
33
+ }
34
+ function DropOff({ percent, formatDropOff, }) {
35
+ return (_jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(ArrowDownIcon, { className: cn("size-3 shrink-0", percent < 0 && "rotate-180"), "aria-hidden": "true" }), _jsx("span", { className: "truncate", children: formatDropOff(percent) })] }));
36
+ }
37
+ function ConversionFunnel({ stages, formatValue = defaultFormatValue, formatDelta = defaultFormatDelta, formatDropOff = defaultFormatDropOff, className, }) {
38
+ const baseline = stages[0]?.value ?? 0;
39
+ return (_jsx("div", { className: cn("flex flex-col gap-2.5", className), children: stages.map((stage, index) => {
40
+ const widthPercent = baseline > 0 ? (stage.value / baseline) * 100 : 0;
41
+ const showLabelInside = widthPercent >= NARROW_SEGMENT_THRESHOLD;
42
+ const formatted = formatValue(stage.value);
43
+ const previous = index > 0 ? stages[index - 1] : undefined;
44
+ const dropOffPercent = previous && previous.value > 0
45
+ ? ((previous.value - stage.value) / previous.value) * 100
46
+ : undefined;
47
+ return (_jsxs(ProgressPrimitive.Root, { value: stage.value, min: 0, max: baseline, getAriaValueText: () => stage.previousPeriodDelta != null
48
+ ? `${formatted}, ${formatDelta(stage.previousPeriodDelta)} vs. previous period`
49
+ : formatted, className: "flex items-center gap-3", children: [_jsxs("div", { className: "flex w-36 shrink-0 flex-col gap-0.5", children: [_jsx(ProgressLabel, { className: "truncate text-sm font-semibold text-foreground", children: stage.label }), dropOffPercent != null && (_jsx(DropOff, { percent: dropOffPercent, formatDropOff: formatDropOff }))] }), _jsxs(ProgressTrack, { className: "h-9 flex-1 rounded-md bg-muted", children: [_jsx(ProgressIndicator, { className: "relative flex items-center overflow-hidden rounded-md", style: { backgroundColor: `var(--chart-${(index % 10) + 1})` }, children: showLabelInside && (_jsx("span", { className: "truncate px-3 text-sm font-medium text-white tabular-nums", children: formatted })) }), !showLabelInside && (_jsx("span", { className: "absolute inset-y-0 flex items-center pl-3 text-sm font-medium text-foreground tabular-nums", style: { left: `${widthPercent}%` }, children: formatted }))] }), stage.previousPeriodDelta != null && (_jsx("div", { className: "w-20 shrink-0 text-right", children: _jsx(TrendBadge, { delta: stage.previousPeriodDelta, formatDelta: formatDelta }) }))] }, stage.label));
50
+ }) }));
51
+ }
52
+ export { ConversionFunnel };
@@ -0,0 +1,42 @@
1
+ import * as React from "react";
2
+ import type { DateRange } from "react-day-picker";
3
+ import { PopoverContent } from "../../components/ui/popover.js";
4
+ type CompareType = "previous-period" | "previous-year" | "custom";
5
+ type DateRangePreset = {
6
+ label: string;
7
+ getRange: () => DateRange;
8
+ };
9
+ type CompareOption = {
10
+ value: CompareType;
11
+ label: string;
12
+ };
13
+ declare const DEFAULT_PRESETS: DateRangePreset[];
14
+ declare const TODAY_PRESET: DateRangePreset;
15
+ declare const DEFAULT_COMPARE_OPTIONS: CompareOption[];
16
+ declare function getPreviousPeriod(range: DateRange): DateRange | undefined;
17
+ declare function getPreviousYear(range: DateRange): DateRange | undefined;
18
+ type DateRangePickerBaseProps = {
19
+ value?: DateRange;
20
+ defaultValue?: DateRange;
21
+ onValueChange?: (value: DateRange) => void;
22
+ presets?: DateRangePreset[];
23
+ className?: string;
24
+ };
25
+ type DateRangePickerPopoverProps = DateRangePickerBaseProps & {
26
+ variant?: "popover";
27
+ compareEnabled?: boolean;
28
+ defaultCompareEnabled?: boolean;
29
+ onCompareEnabledChange?: (enabled: boolean) => void;
30
+ compareValue?: DateRange;
31
+ onCompareValueChange?: (value: DateRange | undefined) => void;
32
+ compareOptions?: CompareOption[];
33
+ numberOfMonths?: number;
34
+ align?: React.ComponentProps<typeof PopoverContent>["align"];
35
+ };
36
+ type DateRangePickerInlineProps = DateRangePickerBaseProps & {
37
+ variant: "inline";
38
+ };
39
+ type DateRangePickerProps = DateRangePickerPopoverProps | DateRangePickerInlineProps;
40
+ declare function DateRangePicker(props: DateRangePickerProps): React.JSX.Element;
41
+ export { DateRangePicker, DEFAULT_PRESETS, DEFAULT_COMPARE_OPTIONS, TODAY_PRESET, getPreviousPeriod, getPreviousYear, };
42
+ export type { DateRangePickerProps, DateRangePickerPopoverProps, DateRangePickerInlineProps, DateRangePreset, CompareOption, CompareType, };
@@ -0,0 +1,184 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { differenceInCalendarDays, format, isSameDay, startOfToday, startOfYear, subDays, subMonths, subYears, } from "date-fns";
5
+ import { es } from "date-fns/locale";
6
+ import { CalendarIcon, ChevronDownIcon } from "lucide-react";
7
+ import { cn } from "../../lib/utils.js";
8
+ import { Button } from "../../components/ui/button.js";
9
+ import { ButtonGroup } from "../../components/ui/button-group.js";
10
+ import { Calendar } from "../../components/ui/calendar.js";
11
+ import { Popover, PopoverContent, PopoverTrigger } from "../../components/ui/popover.js";
12
+ import { Separator } from "../../components/ui/separator.js";
13
+ import { Switch } from "../../components/ui/switch.js";
14
+ const DEFAULT_PRESETS = [
15
+ {
16
+ label: "Ayer",
17
+ getRange: () => ({ from: subDays(startOfToday(), 1), to: startOfToday() }),
18
+ },
19
+ {
20
+ label: "Últimos 7 días",
21
+ getRange: () => ({ from: subDays(startOfToday(), 6), to: startOfToday() }),
22
+ },
23
+ {
24
+ label: "Últimos 14 días",
25
+ getRange: () => ({ from: subDays(startOfToday(), 13), to: startOfToday() }),
26
+ },
27
+ {
28
+ label: "Últimos 30 días",
29
+ getRange: () => ({ from: subDays(startOfToday(), 29), to: startOfToday() }),
30
+ },
31
+ {
32
+ label: "Últimos 90 días",
33
+ getRange: () => ({ from: subDays(startOfToday(), 89), to: startOfToday() }),
34
+ },
35
+ {
36
+ label: "Últimos 12 meses",
37
+ getRange: () => ({ from: subMonths(startOfToday(), 12), to: startOfToday() }),
38
+ },
39
+ {
40
+ label: "Año en curso",
41
+ getRange: () => ({ from: startOfYear(startOfToday()), to: startOfToday() }),
42
+ },
43
+ ];
44
+ const TODAY_PRESET = {
45
+ label: "Hoy",
46
+ getRange: () => ({ from: startOfToday(), to: startOfToday() }),
47
+ };
48
+ const DEFAULT_COMPARE_OPTIONS = [
49
+ { value: "previous-period", label: "Período anterior" },
50
+ { value: "previous-year", label: "Año anterior" },
51
+ { value: "custom", label: "Personalizado" },
52
+ ];
53
+ function rangesEqual(a, b) {
54
+ if (!a?.from || !a?.to || !b?.from || !b?.to)
55
+ return false;
56
+ return isSameDay(a.from, b.from) && isSameDay(a.to, b.to);
57
+ }
58
+ function getPreviousPeriod(range) {
59
+ if (!range.from || !range.to)
60
+ return undefined;
61
+ const length = differenceInCalendarDays(range.to, range.from) + 1;
62
+ const to = subDays(range.from, 1);
63
+ return { from: subDays(to, length - 1), to };
64
+ }
65
+ function getPreviousYear(range) {
66
+ if (!range.from || !range.to)
67
+ return undefined;
68
+ return { from: subYears(range.from, 1), to: subYears(range.to, 1) };
69
+ }
70
+ function formatRange(range) {
71
+ if (!range?.from)
72
+ return "Seleccionar fechas";
73
+ if (!range.to || isSameDay(range.from, range.to)) {
74
+ return format(range.from, "d MMM yyyy", { locale: es });
75
+ }
76
+ return `${format(range.from, "d MMM yyyy", { locale: es })} – ${format(range.to, "d MMM yyyy", { locale: es })}`;
77
+ }
78
+ function formatTriggerLabel(range, presets) {
79
+ if (!range?.from || !range?.to)
80
+ return "Seleccionar fechas";
81
+ const preset = presets.find((p) => rangesEqual(p.getRange(), range));
82
+ if (preset)
83
+ return preset.label;
84
+ if (isSameDay(range.from, range.to))
85
+ return format(range.from, "d MMM yyyy", { locale: es });
86
+ const sameYear = range.from.getFullYear() === range.to.getFullYear();
87
+ return `${format(range.from, sameYear ? "d MMM" : "d MMM yyyy", { locale: es })} – ${format(range.to, "d MMM yyyy", { locale: es })}`;
88
+ }
89
+ function DateRangePicker(props) {
90
+ if (props.variant === "inline") {
91
+ return _jsx(DateRangePickerInline, { ...props });
92
+ }
93
+ return _jsx(DateRangePickerPopover, { ...props });
94
+ }
95
+ function DateRangePickerPopover({ value, defaultValue, onValueChange, compareEnabled, defaultCompareEnabled = false, onCompareEnabledChange, compareValue, onCompareValueChange, presets = DEFAULT_PRESETS, compareOptions = DEFAULT_COMPARE_OPTIONS, numberOfMonths = 2, align = "end", className, }) {
96
+ const [internalValue, setInternalValue] = React.useState(defaultValue ?? presets[3]?.getRange());
97
+ const appliedValue = value ?? internalValue;
98
+ const [internalCompareEnabled, setInternalCompareEnabled] = React.useState(defaultCompareEnabled);
99
+ const appliedCompareEnabled = compareEnabled ?? internalCompareEnabled;
100
+ const [internalCompareValue, setInternalCompareValue] = React.useState(() => (appliedValue ? getPreviousPeriod(appliedValue) : undefined));
101
+ const appliedCompareValue = compareValue ?? internalCompareValue;
102
+ const [compareType, setCompareType] = React.useState("previous-period");
103
+ const [open, setOpen] = React.useState(false);
104
+ const [draftValue, setDraftValue] = React.useState(appliedValue);
105
+ const [draftCompareEnabled, setDraftCompareEnabled] = React.useState(appliedCompareEnabled);
106
+ const [draftCompareType, setDraftCompareType] = React.useState(compareType);
107
+ const [customCompareValue, setCustomCompareValue] = React.useState(appliedCompareValue);
108
+ const [editingTarget, setEditingTarget] = React.useState("main");
109
+ const draftCompareValue = draftCompareType === "custom"
110
+ ? customCompareValue
111
+ : draftValue?.from && draftValue?.to
112
+ ? draftCompareType === "previous-period"
113
+ ? getPreviousPeriod(draftValue)
114
+ : getPreviousYear(draftValue)
115
+ : undefined;
116
+ function handleOpenChange(nextOpen) {
117
+ if (nextOpen) {
118
+ setDraftValue(appliedValue);
119
+ setDraftCompareEnabled(appliedCompareEnabled);
120
+ setDraftCompareType(compareType);
121
+ setCustomCompareValue(appliedCompareValue);
122
+ setEditingTarget("main");
123
+ }
124
+ setOpen(nextOpen);
125
+ }
126
+ function handlePresetClick(preset) {
127
+ setDraftValue(preset.getRange());
128
+ setEditingTarget("main");
129
+ }
130
+ function handleCompareTypeClick(type) {
131
+ if (type === "custom" && draftCompareType !== "custom") {
132
+ setCustomCompareValue(draftCompareValue);
133
+ }
134
+ setDraftCompareType(type);
135
+ setEditingTarget(type === "custom" ? "compare" : "main");
136
+ }
137
+ function handleApply() {
138
+ if (draftValue) {
139
+ setInternalValue(draftValue);
140
+ onValueChange?.(draftValue);
141
+ }
142
+ setInternalCompareEnabled(draftCompareEnabled);
143
+ onCompareEnabledChange?.(draftCompareEnabled);
144
+ setCompareType(draftCompareType);
145
+ const nextCompareValue = draftCompareEnabled ? draftCompareValue : undefined;
146
+ setInternalCompareValue(nextCompareValue);
147
+ onCompareValueChange?.(nextCompareValue);
148
+ setOpen(false);
149
+ }
150
+ const calendarSelected = editingTarget === "compare" ? draftCompareValue : draftValue;
151
+ const otherRange = editingTarget === "compare"
152
+ ? draftValue
153
+ : draftCompareEnabled
154
+ ? draftCompareValue
155
+ : undefined;
156
+ function handleCalendarSelect(range) {
157
+ if (editingTarget === "compare") {
158
+ setCustomCompareValue(range);
159
+ }
160
+ else {
161
+ setDraftValue(range);
162
+ }
163
+ }
164
+ return (_jsxs(Popover, { open: open, onOpenChange: handleOpenChange, children: [_jsxs(PopoverTrigger, { render: _jsx(Button, { variant: "outline", className: cn("gap-1.5", className) }), children: [_jsx(CalendarIcon, { "data-icon": "inline-start" }), formatTriggerLabel(appliedValue, presets), appliedCompareEnabled && (_jsx("span", { className: "size-1.5 shrink-0 rounded-full bg-primary", "aria-hidden": "true" })), _jsx(ChevronDownIcon, { "data-icon": "inline-end", className: "text-muted-foreground" })] }), _jsxs(PopoverContent, { align: align, className: "w-auto flex-row gap-0 p-0", children: [_jsxs("div", { className: "flex w-48 shrink-0 flex-col gap-0.5 p-2", children: [presets.map((preset) => (_jsx("button", { type: "button", "data-active": rangesEqual(draftValue, preset.getRange()), onClick: () => handlePresetClick(preset), className: cn("w-full rounded-md px-2.5 py-1.5 text-left text-sm text-foreground transition-colors hover:bg-muted", "data-[active=true]:bg-muted data-[active=true]:font-medium data-[active=true]:text-primary"), children: preset.label }, preset.label))), _jsx(Separator, { className: "my-2" }), _jsxs("div", { className: "flex items-center justify-between px-2.5 py-1.5", children: [_jsx("span", { className: "text-sm font-medium", children: "Comparar" }), _jsx(Switch, { checked: draftCompareEnabled, onCheckedChange: (checked) => {
165
+ setDraftCompareEnabled(checked);
166
+ setEditingTarget(checked && draftCompareType === "custom" ? "compare" : "main");
167
+ } })] }), draftCompareEnabled &&
168
+ compareOptions.map((option) => (_jsx("button", { type: "button", "data-active": draftCompareType === option.value, onClick: () => handleCompareTypeClick(option.value), className: cn("w-full rounded-md px-2.5 py-1.5 text-left text-sm text-foreground transition-colors hover:bg-muted", "data-[active=true]:bg-muted data-[active=true]:font-medium data-[active=true]:text-primary"), children: option.label }, option.value)))] }), _jsx(Separator, { orientation: "vertical" }), _jsxs("div", { className: "flex flex-col gap-3 p-4", children: [_jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [_jsxs("button", { type: "button", onClick: () => setEditingTarget("main"), className: cn("flex-1 rounded-lg border border-border px-3 py-2 text-left transition-colors", editingTarget === "main" && "border-primary"), children: [_jsx("span", { className: "text-[0.7rem] font-medium tracking-wide text-muted-foreground uppercase", children: "Per\u00EDodo" }), _jsx("p", { className: cn("text-sm font-medium", editingTarget === "main" && "text-primary"), children: formatRange(draftValue) })] }), draftCompareEnabled && (_jsxs("button", { type: "button", disabled: draftCompareType !== "custom", onClick: () => draftCompareType === "custom" && setEditingTarget("compare"), className: cn("flex-1 rounded-lg border border-border px-3 py-2 text-left transition-colors", draftCompareType === "custom" && "cursor-pointer", editingTarget === "compare" && "border-primary"), children: [_jsx("span", { className: "text-[0.7rem] font-medium tracking-wide text-muted-foreground uppercase", children: "Comparar" }), _jsx("p", { className: cn("text-sm font-medium", editingTarget === "compare" && "text-primary"), children: formatRange(draftCompareValue) })] }))] }), _jsx(Calendar, { mode: "range", locale: es, numberOfMonths: numberOfMonths, selected: calendarSelected, onSelect: handleCalendarSelect, defaultMonth: (editingTarget === "compare" ? draftCompareValue?.from : draftValue?.from) ??
169
+ undefined, modifiers: otherRange?.from && otherRange?.to ? { compare: otherRange } : undefined, modifiersClassNames: { compare: "bg-chart-2/15" }, className: "w-fit p-0" }), _jsx(Separator, {}), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "ghost", onClick: () => setOpen(false), children: "Cancelar" }), _jsx(Button, { onClick: handleApply, children: "Aplicar" })] })] })] })] }));
170
+ }
171
+ function DateRangePickerInline({ value, defaultValue, onValueChange, presets = DEFAULT_PRESETS, className, }) {
172
+ const [internalValue, setInternalValue] = React.useState(defaultValue ?? presets[0]?.getRange());
173
+ const appliedValue = value ?? internalValue;
174
+ function commit(range) {
175
+ setInternalValue(range);
176
+ if (range)
177
+ onValueChange?.(range);
178
+ }
179
+ return (_jsx(ButtonGroup, { className: className, children: presets.map((preset) => {
180
+ const active = rangesEqual(appliedValue, preset.getRange());
181
+ return (_jsx(Button, { type: "button", variant: active ? "default" : "outline", onClick: () => commit(preset.getRange()), children: preset.label }, preset.label));
182
+ }) }));
183
+ }
184
+ export { DateRangePicker, DEFAULT_PRESETS, DEFAULT_COMPARE_OPTIONS, TODAY_PRESET, getPreviousPeriod, getPreviousYear, };
@@ -0,0 +1,25 @@
1
+ import * as React from "react";
2
+ type HeatmapNormalize = "row" | "column" | "global";
3
+ type HeatmapLegend = {
4
+ low?: React.ReactNode;
5
+ high?: React.ReactNode;
6
+ };
7
+ type HeatmapProps = {
8
+ rowLabels: string[];
9
+ columnLabels: string[];
10
+ /** data[rowIndex][columnIndex]; null/undefined renders as a neutral empty cell */
11
+ data: (number | null | undefined)[][];
12
+ cornerLabel?: React.ReactNode;
13
+ formatValue?: (value: number) => string;
14
+ /** which slice of the data each cell's color is computed against (default: "column") */
15
+ normalize?: HeatmapNormalize;
16
+ /** pass false to hide the scale legend */
17
+ legend?: HeatmapLegend | false;
18
+ emptyLabel?: React.ReactNode;
19
+ className?: string;
20
+ cellClassName?: string;
21
+ onCellClick?: (value: number | null | undefined, rowIndex: number, columnIndex: number) => void;
22
+ };
23
+ declare function Heatmap({ rowLabels, columnLabels, data, cornerLabel, formatValue, normalize, legend, emptyLabel, className, cellClassName, onCellClick, }: HeatmapProps): React.JSX.Element;
24
+ export { Heatmap };
25
+ export type { HeatmapProps, HeatmapNormalize, HeatmapLegend };
@@ -0,0 +1,79 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../../components/ui/table.js";
6
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/tooltip.js";
7
+ const CELL_PADDING = "px-3 py-1.5";
8
+ function heatBackground(t) {
9
+ const clamped = Math.min(1, Math.max(0, t));
10
+ const scaled = clamped * 4;
11
+ const index = Math.min(3, Math.floor(scaled));
12
+ const localT = scaled - index;
13
+ return `color-mix(in oklch, var(--heat-${index + 1}) ${(1 - localT) * 100}%, var(--heat-${index + 2}) ${localT * 100}%)`;
14
+ }
15
+ function useHeatmapRanges(data, normalize) {
16
+ return React.useMemo(() => {
17
+ const colCount = data[0]?.length ?? 0;
18
+ if (normalize === "global") {
19
+ const range = { min: Infinity, max: -Infinity };
20
+ for (const row of data) {
21
+ for (const value of row) {
22
+ if (value == null)
23
+ continue;
24
+ if (value < range.min)
25
+ range.min = value;
26
+ if (value > range.max)
27
+ range.max = value;
28
+ }
29
+ }
30
+ return () => range;
31
+ }
32
+ if (normalize === "row") {
33
+ const ranges = data.map((row) => {
34
+ const range = { min: Infinity, max: -Infinity };
35
+ for (const value of row) {
36
+ if (value == null)
37
+ continue;
38
+ if (value < range.min)
39
+ range.min = value;
40
+ if (value > range.max)
41
+ range.max = value;
42
+ }
43
+ return range;
44
+ });
45
+ return (r) => ranges[r];
46
+ }
47
+ const ranges = Array.from({ length: colCount }, () => ({
48
+ min: Infinity,
49
+ max: -Infinity,
50
+ }));
51
+ for (const row of data) {
52
+ row.forEach((value, c) => {
53
+ if (value == null)
54
+ return;
55
+ if (value < ranges[c].min)
56
+ ranges[c].min = value;
57
+ if (value > ranges[c].max)
58
+ ranges[c].max = value;
59
+ });
60
+ }
61
+ return (r, c) => (normalize === "column" ? ranges[c] : ranges[r]);
62
+ }, [data, normalize]);
63
+ }
64
+ function Heatmap({ rowLabels, columnLabels, data, cornerLabel, formatValue = (value) => value.toLocaleString(), normalize = "column", legend = { low: "Bajo", high: "Alto" }, emptyLabel = "–", className, cellClassName, onCellClick, }) {
65
+ const getRange = useHeatmapRanges(data, normalize);
66
+ return (_jsxs("div", { className: cn("flex flex-col gap-3", className), children: [legend !== false && (_jsxs("div", { className: "flex items-center justify-end gap-2 text-xs text-muted-foreground", children: [_jsx("span", { children: legend.low ?? "Bajo" }), _jsx("div", { className: "h-2 w-24 rounded-full", style: {
67
+ backgroundImage: "linear-gradient(in oklch to right, var(--heat-1), var(--heat-2), var(--heat-3), var(--heat-4), var(--heat-5))",
68
+ }, "aria-hidden": "true" }), _jsx("span", { children: legend.high ?? "Alto" })] })), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: cn("sticky left-0 z-10 bg-background", CELL_PADDING), children: cornerLabel }), columnLabels.map((label, c) => (_jsx(TableHead, { className: cn("text-right", CELL_PADDING), children: label }, c)))] }) }), _jsx(TableBody, { children: rowLabels.map((rowLabel, r) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: cn("sticky left-0 z-10 bg-background font-medium text-foreground", CELL_PADDING), children: rowLabel }), columnLabels.map((columnLabel, c) => {
69
+ const value = data[r]?.[c];
70
+ if (value == null) {
71
+ return (_jsx(TableCell, { className: cn("bg-muted/40 text-right text-muted-foreground tabular-nums", CELL_PADDING, cellClassName), children: emptyLabel }, c));
72
+ }
73
+ const { min, max } = getRange(r, c);
74
+ const t = max > min ? (value - min) / (max - min) : 0.5;
75
+ const display = formatValue(value);
76
+ return (_jsx(TableCell, { className: cn("p-0 text-right", cellClassName), style: { backgroundColor: heatBackground(t) }, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx("button", { type: "button", onClick: () => onCellClick?.(value, r, c) }), className: cn("block w-full bg-transparent text-right text-(--heat-ink) tabular-nums outline-none transition-[filter] hover:brightness-95 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", CELL_PADDING, onCellClick ? "cursor-pointer" : "cursor-default"), children: display }), _jsxs(TooltipContent, { children: [columnLabel, " \u00B7 ", rowLabel, ": ", display] })] }) }, c));
77
+ })] }, r))) })] })] }));
78
+ }
79
+ export { Heatmap };
@@ -0,0 +1,41 @@
1
+ import * as React from "react";
2
+ type MetricCardGroupProps = {
3
+ onValueChange?: (value: string[]) => void;
4
+ /** Label for the control that activates every metric. Default: "Mostrar todo". */
5
+ selectAllLabel?: string;
6
+ /** Label for the control that clears the active set — the chart is allowed to end up empty. Default: "Ocultar todo". */
7
+ selectNoneLabel?: string;
8
+ "aria-label"?: string;
9
+ className?: string;
10
+ children: React.ReactNode;
11
+ } & ({
12
+ value: string[];
13
+ defaultValue?: never;
14
+ } | {
15
+ value?: undefined;
16
+ /**
17
+ * The metrics active on first render. Required — a KPI toggle group should
18
+ * always start with a deliberate set of series plotted, even though
19
+ * `selectNoneLabel` (or manual toggling) can clear it down to none afterward.
20
+ */
21
+ defaultValue: string[];
22
+ });
23
+ declare function MetricCardGroup({ value, defaultValue, onValueChange, selectAllLabel, selectNoneLabel, "aria-label": ariaLabel, className, children, }: MetricCardGroupProps): React.JSX.Element;
24
+ type MetricCardProps = {
25
+ /** Key this card toggles in the group's active list — pass the same key as the chart series' dataKey. */
26
+ metricKey: string;
27
+ label: string;
28
+ value: React.ReactNode;
29
+ /** % change vs. the previous period; omit to hide the trend badge. */
30
+ delta?: number;
31
+ formatDelta?: (delta: number) => string;
32
+ /**
33
+ * The chart series color this metric drives when active, e.g. `"var(--chart-1)"`.
34
+ * Shown as the border/background accent and the leading legend dot while selected.
35
+ */
36
+ color: string;
37
+ className?: string;
38
+ };
39
+ declare function MetricCard({ metricKey, label, value, delta, formatDelta, color, className, }: MetricCardProps): React.JSX.Element;
40
+ export { MetricCardGroup, MetricCard };
41
+ export type { MetricCardGroupProps, MetricCardProps };
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { ArrowDownRightIcon, ArrowUpRightIcon } from "lucide-react";
5
+ import { cn } from "../../lib/utils.js";
6
+ import { Badge } from "../../components/ui/badge.js";
7
+ import { Button } from "../../components/ui/button.js";
8
+ import { ButtonGroup } from "../../components/ui/button-group.js";
9
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../../components/ui/card.js";
10
+ const MetricCardGroupContext = React.createContext(null);
11
+ function useMetricCardGroupContext(metricKey) {
12
+ const context = React.useContext(MetricCardGroupContext);
13
+ if (!context) {
14
+ throw new Error("MetricCard must be used within a MetricCardGroup");
15
+ }
16
+ return { selected: context.active.includes(metricKey), toggle: () => context.toggle(metricKey) };
17
+ }
18
+ function getMetricKeys(children) {
19
+ return React.Children.toArray(children)
20
+ .filter(React.isValidElement)
21
+ .map((child) => child.props.metricKey)
22
+ .filter((key) => typeof key === "string");
23
+ }
24
+ function MetricCardGroup({ value, defaultValue, onValueChange, selectAllLabel = "Mostrar todo", selectNoneLabel = "Ocultar todo", "aria-label": ariaLabel, className, children, }) {
25
+ const [internalValue, setInternalValue] = React.useState(defaultValue ?? value ?? []);
26
+ const active = value ?? internalValue;
27
+ const allKeys = React.useMemo(() => getMetricKeys(children), [children]);
28
+ const setActive = React.useCallback((next) => {
29
+ if (value === undefined)
30
+ setInternalValue(next);
31
+ onValueChange?.(next);
32
+ }, [onValueChange, value]);
33
+ const toggle = React.useCallback((key) => {
34
+ setActive(active.includes(key) ? active.filter((k) => k !== key) : [...active, key]);
35
+ }, [active, setActive]);
36
+ return (_jsx(MetricCardGroupContext.Provider, { value: { active, toggle }, children: _jsxs("div", { className: cn("@container/metric-cards flex flex-col gap-2", className), children: [_jsx("div", { className: "flex justify-end", children: _jsxs(ButtonGroup, { children: [_jsx(Button, { variant: "outline", size: "xs", onClick: () => setActive(allKeys), disabled: allKeys.every((key) => active.includes(key)), children: selectAllLabel }), _jsx(Button, { variant: "outline", size: "xs", onClick: () => setActive([]), disabled: active.length === 0, children: selectNoneLabel })] }) }), _jsx("div", { role: "group", "aria-label": ariaLabel, className: "grid grid-cols-2 gap-3 @lg/metric-cards:grid-cols-3 @3xl/metric-cards:grid-cols-5", children: children })] }) }));
37
+ }
38
+ function defaultFormatDelta(delta) {
39
+ return `${delta > 0 ? "+" : ""}${delta.toFixed(1)}%`;
40
+ }
41
+ function MetricCard({ metricKey, label, value, delta, formatDelta = defaultFormatDelta, color, className, }) {
42
+ const { selected, toggle } = useMetricCardGroupContext(metricKey);
43
+ const isPositive = delta != null && delta >= 0;
44
+ const TrendIcon = isPositive ? ArrowUpRightIcon : ArrowDownRightIcon;
45
+ return (_jsxs(Card, { render: _jsx("button", { type: "button" }), size: "sm", "aria-pressed": selected, "aria-label": `${label}, ${selected ? "visible" : "oculto"} en el gráfico`, onClick: toggle, style: { "--selected-color": color }, className: cn("min-h-24 border border-transparent text-left outline-none transition-colors hover:bg-muted/40 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50", "aria-pressed:border-(--selected-color) aria-pressed:bg-(--selected-color)/8 aria-pressed:ring-1 aria-pressed:ring-(--selected-color)/25 aria-pressed:hover:bg-(--selected-color)/12", className), children: [_jsx(CardHeader, { children: _jsxs(CardDescription, { className: "flex min-w-0 items-center gap-1.5 text-xs font-medium tracking-wide text-muted-foreground uppercase", children: [selected && (_jsx("span", { "aria-hidden": "true", className: "size-1.5 shrink-0 rounded-full bg-(--selected-color)" })), _jsx("span", { className: "truncate", children: label })] }) }), _jsx(CardContent, { className: "flex flex-1 flex-col justify-center", children: _jsx(CardTitle, { className: cn("truncate text-2xl font-semibold tabular-nums", selected && "text-(--selected-color)"), children: value }) }), delta != null && (_jsx("div", { className: "min-w-0 px-(--card-spacing)", children: _jsxs(Badge, { variant: isPositive ? "success" : "destructive", className: "gap-0.5", children: [_jsx(TrendIcon, { "data-icon": "inline-start" }), formatDelta(delta)] }) }))] }));
46
+ }
47
+ export { MetricCardGroup, MetricCard };
@@ -0,0 +1,6 @@
1
+ import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
2
+ declare function Accordion({ className, ...props }: AccordionPrimitive.Root.Props): import("react").JSX.Element;
3
+ declare function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props): import("react").JSX.Element;
4
+ declare function AccordionTrigger({ className, children, ...props }: AccordionPrimitive.Trigger.Props): import("react").JSX.Element;
5
+ declare function AccordionContent({ className, children, ...props }: AccordionPrimitive.Panel.Props): import("react").JSX.Element;
6
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };