@epa-datos/ui 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -93,6 +93,14 @@ pnpm build:lib # builds the publishable package into dist/
93
93
 
94
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
95
 
96
+ ### Releasing a new version
97
+
98
+ 1. Bump `version` in `package.json` (semver — this is the only manual step).
99
+ 2. Commit that change and cut a [GitHub release](https://github.com/epa-datos/epa-ui/releases/new) (tag `vX.Y.Z` matching the version).
100
+ 3. Publishing to npm happens automatically from there — the `Publish package` workflow (`.github/workflows/publish.yml`) builds the package and runs `npm publish` on release, and it does not use a stored `NPM_TOKEN`. Authentication is [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/) over GitHub Actions OIDC: npm exchanges a short-lived token scoped to that one workflow run for publish rights, instead of a long-lived secret sitting in repo settings. That trust relationship is configured on the package's [npm settings page](https://www.npmjs.com/package/@epa-datos/ui/access) (provider GitHub Actions, repo `epa-datos/epa-ui`, workflow `publish.yml`) — if the workflow filename or repo ever changes, that config needs updating too, or CI publishing breaks.
101
+
102
+ The very first publish of a new package name/scope still has to be done manually (`npm publish --access public`) — Trusted Publishing can only be configured once the package already exists on the registry.
103
+
96
104
  ## License
97
105
 
98
106
  MIT
@@ -30,6 +30,8 @@ type DateRangePickerPopoverProps = DateRangePickerBaseProps & {
30
30
  compareValue?: DateRange;
31
31
  onCompareValueChange?: (value: DateRange | undefined) => void;
32
32
  compareOptions?: CompareOption[];
33
+ /** Muestra el interruptor "Comparar" y sus opciones. Ocúltalo cuando la vista ya maneja su propio comparativo. Default: true. */
34
+ showCompare?: boolean;
33
35
  numberOfMonths?: number;
34
36
  align?: React.ComponentProps<typeof PopoverContent>["align"];
35
37
  };
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { differenceInCalendarDays, format, isSameDay, startOfToday, startOfYear, subDays, subMonths, subYears, } from "date-fns";
5
5
  import { es } from "date-fns/locale";
@@ -92,7 +92,7 @@ function DateRangePicker(props) {
92
92
  }
93
93
  return _jsx(DateRangePickerPopover, { ...props });
94
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, }) {
95
+ function DateRangePickerPopover({ value, defaultValue, onValueChange, compareEnabled, defaultCompareEnabled = false, onCompareEnabledChange, compareValue, onCompareValueChange, presets = DEFAULT_PRESETS, compareOptions = DEFAULT_COMPARE_OPTIONS, showCompare = true, numberOfMonths = 2, align = "end", className, }) {
96
96
  const [internalValue, setInternalValue] = React.useState(defaultValue ?? presets[3]?.getRange());
97
97
  const appliedValue = value ?? internalValue;
98
98
  const [internalCompareEnabled, setInternalCompareEnabled] = React.useState(defaultCompareEnabled);
@@ -153,20 +153,32 @@ function DateRangePickerPopover({ value, defaultValue, onValueChange, compareEna
153
153
  : draftCompareEnabled
154
154
  ? draftCompareValue
155
155
  : undefined;
156
- function handleCalendarSelect(range) {
156
+ function handleCalendarSelect(range, triggerDate) {
157
+ // Con un rango completo, react-day-picker solo mueve el extremo final (o el
158
+ // inicial si el clic cae antes): desde "últimos 30 días" no hay forma de
159
+ // mover el inicio hacia adelante. Un clic sobre un rango ya completo
160
+ // empieza uno nuevo en esa fecha.
161
+ const next = calendarSelected?.from && calendarSelected.to
162
+ ? { from: triggerDate, to: undefined }
163
+ : range;
157
164
  if (editingTarget === "compare") {
158
- setCustomCompareValue(range);
165
+ setCustomCompareValue(next);
159
166
  }
160
167
  else {
161
- setDraftValue(range);
168
+ setDraftValue(next);
162
169
  }
163
170
  }
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" })] })] })] })] }));
171
+ // Aplicar con un rango a medias cerraba el popover sin cambiar nada.
172
+ const canApply = Boolean(draftValue?.from && draftValue.to) &&
173
+ !(draftCompareEnabled &&
174
+ draftCompareType === "custom" &&
175
+ !(draftCompareValue?.from && draftCompareValue.to));
176
+ 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))), showCompare && (_jsxs(_Fragment, { children: [_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) => {
177
+ setDraftCompareEnabled(checked);
178
+ setEditingTarget(checked && draftCompareType === "custom" ? "compare" : "main");
179
+ } })] }), draftCompareEnabled &&
180
+ 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) ??
181
+ 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, disabled: !canApply, children: "Aplicar" })] })] })] })] }));
170
182
  }
171
183
  function DateRangePickerInline({ value, defaultValue, onValueChange, presets = DEFAULT_PRESETS, className, }) {
172
184
  const [internalValue, setInternalValue] = React.useState(defaultValue ?? presets[0]?.getRange());
@@ -3,11 +3,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
4
4
  import { cva } from "class-variance-authority";
5
5
  import { cn } from "../../lib/utils.js";
6
- const toggleVariants = cva("group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
6
+ const toggleVariants = cva("group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-primary/10 aria-pressed:font-semibold aria-pressed:text-primary data-[state=on]:bg-primary/10 data-[state=on]:text-primary dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
7
7
  variants: {
8
8
  variant: {
9
9
  default: "bg-transparent",
10
- outline: "border border-input bg-transparent hover:bg-muted",
10
+ outline: "border border-input bg-transparent hover:bg-muted aria-pressed:border-primary aria-pressed:hover:bg-primary/15",
11
11
  },
12
12
  size: {
13
13
  default: "h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@epa-datos/ui",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "React + Tailwind dashboard component library built on Base UI, published as source so it stays fully readable and themeable in the projects that consume it.",
5
5
  "license": "MIT",
6
6
  "author": "EPA Digital",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/epa-datos/epa-ui.git"
9
+ "url": "git+https://github.com/epa-datos/epa-ui.git"
10
10
  },
11
11
  "homepage": "https://github.com/epa-datos/epa-ui#readme",
12
12
  "bugs": {
@@ -70,9 +70,7 @@
70
70
  "input-otp": "^1.4.2",
71
71
  "lucide-react": "^1.31.0",
72
72
  "next-themes": "^0.4.6",
73
- "react": "19.2.8",
74
73
  "react-day-picker": "^10.0.1",
75
- "react-dom": "19.2.8",
76
74
  "react-resizable-panels": "^4.12.2",
77
75
  "recharts": "3.8.0",
78
76
  "shadcn": "^4.17.0",
@@ -91,6 +89,8 @@
91
89
  "eslint": "^9",
92
90
  "eslint-config-next": "16.3.0",
93
91
  "next": "16.3.0",
92
+ "react": "19.2.8",
93
+ "react-dom": "19.2.8",
94
94
  "rimraf": "^6.0.1",
95
95
  "tailwindcss": "^4",
96
96
  "tsc-alias": "^1.8.10",