@moeve-ui/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.
@@ -0,0 +1,395 @@
1
+ import * as react from 'react';
2
+ import { SVGProps, ReactNode, ButtonHTMLAttributes, HTMLAttributes } from 'react';
3
+
4
+ /**
5
+ * Design tokens - extracted directly from the "Redinamic-tec" Figma file
6
+ * (get_design_context on the "Simulador de Impacto" node), not made up.
7
+ *
8
+ * This is the ONLY source of truth for the library's colors. The Tailwind
9
+ * preset (`packages/ui/tailwind.preset.ts`) reads from here, so a brand
10
+ * change happens in a single place and propagates through the whole app -
11
+ * it's the same Open/Closed principle: extend by adding/changing tokens,
12
+ * not by editing every component that uses a color.
13
+ */
14
+ declare const colors: {
15
+ readonly brand: {
16
+ readonly sidebar: "#004656";
17
+ readonly sidebarHover: "#005a6e";
18
+ readonly accent: "#047dba";
19
+ readonly primary: "#006395";
20
+ readonly logo: "#8af9b5";
21
+ };
22
+ readonly surface: {
23
+ readonly page: "#f4fafe";
24
+ readonly card: "#ffffff";
25
+ readonly subtle: "#eef4f8";
26
+ readonly muted: "#e9eff3";
27
+ };
28
+ readonly text: {
29
+ readonly primary: "#161c1f";
30
+ readonly secondary: "#404850";
31
+ readonly tertiary: "#707881";
32
+ readonly onDark: "#ffffff";
33
+ readonly onDarkMuted: "rgba(255,255,255,0.6)";
34
+ readonly onDarkFaint: "rgba(255,255,255,0.5)";
35
+ };
36
+ readonly success: {
37
+ readonly DEFAULT: "#008851";
38
+ readonly dark: "#006c3f";
39
+ readonly bg: "#8af9b5";
40
+ };
41
+ readonly danger: {
42
+ readonly DEFAULT: "#ba1a1a";
43
+ readonly dark: "#93000a";
44
+ readonly bg: "#ffdad6";
45
+ };
46
+ readonly info: {
47
+ readonly DEFAULT: "#006395";
48
+ readonly dark: "#2e6576";
49
+ };
50
+ readonly border: {
51
+ readonly DEFAULT: "#dde3e7";
52
+ };
53
+ };
54
+ type ColorTokens = typeof colors;
55
+
56
+ /**
57
+ * Custom icon set, in plain SVG (no external library like
58
+ * lucide-react/heroicons) - so packages/ui doesn't depend on anything but
59
+ * React, and there's no separate icon package to install/update.
60
+ *
61
+ * NOTE ON DESIGN FIDELITY: the Figma design ships its own icons exported
62
+ * as images; those assets live on Figma's servers and the URLs expire in
63
+ * ~7 days, so they can't be "committed" as-is. These are an equivalent set
64
+ * (same visual meaning - pin, arrow, check, etc.) redrawn by hand in SVG,
65
+ * not an exact pixel-for-pixel copy of the original vector. If the exact
66
+ * Figma set is wanted later on, it can be exported as SVG from the file
67
+ * and used to replace these.
68
+ *
69
+ * All of them accept the standard SVG props (className, etc.) so size/color
70
+ * can be controlled with Tailwind from wherever they're used.
71
+ */
72
+ type IconProps = SVGProps<SVGSVGElement>;
73
+ declare function BoltIcon(props: IconProps): react.JSX.Element;
74
+ declare function MapPinIcon(props: IconProps): react.JSX.Element;
75
+ declare function ChevronDownIcon(props: IconProps): react.JSX.Element;
76
+ declare function ArrowUpIcon(props: IconProps): react.JSX.Element;
77
+ declare function ArrowDownIcon(props: IconProps): react.JSX.Element;
78
+ declare function RefreshIcon(props: IconProps): react.JSX.Element;
79
+ declare function UserCircleIcon(props: IconProps): react.JSX.Element;
80
+ declare function LogOutIcon(props: IconProps): react.JSX.Element;
81
+ declare function TagIcon(props: IconProps): react.JSX.Element;
82
+ declare function TargetIcon(props: IconProps): react.JSX.Element;
83
+ declare function TrendUpIcon(props: IconProps): react.JSX.Element;
84
+ declare function TrendDownIcon(props: IconProps): react.JSX.Element;
85
+ declare function FuelIcon(props: IconProps): react.JSX.Element;
86
+ declare function WalletIcon(props: IconProps): react.JSX.Element;
87
+ declare function CheckCircleIcon(props: IconProps): react.JSX.Element;
88
+ declare function XIcon(props: IconProps): react.JSX.Element;
89
+ /**
90
+ * Solid/filled icon set, exported directly from the "Redinamic-tec" Figma
91
+ * file (the "Gestion de Precios" stat cards) - unlike the outline set
92
+ * above, these are pixel-accurate copies of the Figma vectors, not
93
+ * hand-redrawn equivalents. Each keeps its own natural viewBox/aspect
94
+ * ratio instead of being forced into the outline set's 24x24 grid.
95
+ */
96
+ declare function FuelPumpSolidIcon(props: IconProps): react.JSX.Element;
97
+ declare function WalletSolidIcon(props: IconProps): react.JSX.Element;
98
+ declare function TrendUpSolidIcon(props: IconProps): react.JSX.Element;
99
+
100
+ interface AvatarProps {
101
+ /** letter or icon shown inside the circle */
102
+ children: ReactNode;
103
+ /** background color of the circle (hex or another Tailwind class via className) */
104
+ className?: string;
105
+ size?: "sm" | "md";
106
+ }
107
+ /**
108
+ * Avatar/initials circle - used both by the session user (sidebar) and
109
+ * each competitor in the "Nearby Competitors" list. It does not decide
110
+ * the background OR text color: it receives them via className
111
+ * (e.g. "bg-danger-bg text-danger-dark" or "bg-success text-white"), so
112
+ * the same component works for any combination without having to touch it.
113
+ */
114
+ declare function Avatar({ children, className, size }: AvatarProps): react.JSX.Element;
115
+
116
+ type BadgeTone = "success" | "danger" | "neutral";
117
+ interface BadgeProps {
118
+ children: ReactNode;
119
+ tone?: BadgeTone;
120
+ className?: string;
121
+ }
122
+ /**
123
+ * Small pill for deltas ("+0.7%", "-800L") and status labels. The set of
124
+ * variants ("tone") is the only extension point - adding a new tone
125
+ * doesn't touch the rest of the component (Open/Closed).
126
+ */
127
+ declare function Badge({ children, tone, className }: BadgeProps): react.JSX.Element;
128
+
129
+ type ButtonVariant = "primary" | "ghost" | "text";
130
+ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
131
+ children: ReactNode;
132
+ variant?: ButtonVariant;
133
+ icon?: ReactNode;
134
+ }
135
+ /**
136
+ * Single button with 3 variants (the ones that appear in the design: the
137
+ * solid blue pill for "Aplicar precio"/"Actualizar datos", the text link
138
+ * for "Restablecer a Actual", and a "ghost" variant for future cases).
139
+ * Same as Badge: extending the set of variants doesn't require touching
140
+ * the rest of the component.
141
+ */
142
+ declare function Button({ children, variant, icon, className, ...rest }: ButtonProps): react.JSX.Element;
143
+
144
+ interface CardProps extends HTMLAttributes<HTMLDivElement> {
145
+ children: ReactNode;
146
+ /** removes the default padding, for when the content manages its own */
147
+ noPadding?: boolean;
148
+ }
149
+ /**
150
+ * Base white, rounded container with a soft shadow - the same pattern
151
+ * that wraps every dashboard block in the design (Simulator Panel,
152
+ * Decision Factors, Nearby Competitors, etc.). Anything specific to a
153
+ * given block goes through composition (children), not new props here -
154
+ * single responsibility principle: Card only knows how to be "the white box".
155
+ */
156
+ declare function Card({ children, className, noPadding, ...rest }: CardProps): react.JSX.Element;
157
+
158
+ interface ProgressBarProps {
159
+ /** 0 to 100 */
160
+ value: number;
161
+ /** color of the filled bar - Tailwind class, e.g. "bg-info" */
162
+ fillClassName?: string;
163
+ /** bar height in px (the design uses 4px in stat tiles and 6px in Decision Factors) */
164
+ heightClassName?: string;
165
+ /** number of equal segments the bar is divided into (the design uses 3 in "Supply Cost") */
166
+ segments?: number;
167
+ className?: string;
168
+ }
169
+ /**
170
+ * Simple progress bar. It knows nothing about "what" it represents
171
+ * (elasticity, margin, inventory...) - that's decided by whoever uses it
172
+ * via `value` and `fillClassName`. Keeping it "dumb" is what allows it to
173
+ * be reused in the stat tiles AND in the Decision Factors panel without
174
+ * duplicating code.
175
+ */
176
+ declare function ProgressBar({ value, fillClassName, heightClassName, segments, className, }: ProgressBarProps): react.JSX.Element;
177
+
178
+ type SnackbarTone = "success" | "danger" | "neutral";
179
+ interface SnackbarProps {
180
+ message: string;
181
+ tone?: SnackbarTone;
182
+ onClose?: () => void;
183
+ className?: string;
184
+ }
185
+ /**
186
+ * Toast/confirmation bar (e.g. "Datos de mercado actualizados
187
+ * correctamente" after a refresh). Fully controlled - it has no timer
188
+ * or visibility state of its own; whoever uses it decides when it's on
189
+ * screen (mount/unmount it, or drive a boolean) and for how long, same
190
+ * philosophy as ConfirmDialog's `open`.
191
+ */
192
+ declare function Snackbar({ message, tone, onClose, className }: SnackbarProps): react.JSX.Element;
193
+
194
+ interface SegmentedTabOption {
195
+ value: string;
196
+ label: string;
197
+ }
198
+ interface SegmentedTabsProps {
199
+ options: SegmentedTabOption[];
200
+ value: string;
201
+ onChange: (value: string) => void;
202
+ className?: string;
203
+ }
204
+ /**
205
+ * The Simulator's "Regular | Premium | Diesel" control. It receives the
206
+ * options via props instead of having them fixed - the same component
207
+ * works for any list of tabs, in any future screen.
208
+ */
209
+ declare function SegmentedTabs({ options, value, onChange, className }: SegmentedTabsProps): react.JSX.Element;
210
+
211
+ interface NavItemProps {
212
+ icon: ReactNode;
213
+ label: string;
214
+ active?: boolean;
215
+ href?: string;
216
+ }
217
+ /**
218
+ * A sidebar navigation link. `active` is the only variant -
219
+ * the rest of the appearance (icon, text) is composed from outside.
220
+ */
221
+ declare function NavItem({ icon, label, active, href }: NavItemProps): react.JSX.Element;
222
+
223
+ interface StatTileProps {
224
+ icon: ReactNode;
225
+ label: string;
226
+ value: string;
227
+ /** unit suffix rendered next to `value` in a lighter/smaller style, e.g. "L" */
228
+ unit?: string;
229
+ /** delta badge text, e.g. "+2.4% vs prom. diario" */
230
+ delta?: string;
231
+ deltaTone?: BadgeTone;
232
+ /** optional 0-100 value for the mini bar below (not every tile has one) */
233
+ progressValue?: number;
234
+ progressClassName?: string;
235
+ }
236
+ /**
237
+ * One of the result cards ("Volumen Proyectado", "Margen Bruto Proy.",
238
+ * "Ganancia Proyectada"). Purely presentational - it doesn't know where
239
+ * the numbers come from, it just renders them (Dependency Inversion:
240
+ * StatTile depends on props, not on the API or the model).
241
+ */
242
+ declare function StatTile({ icon, label, value, unit, delta, deltaTone, progressValue, progressClassName, }: StatTileProps): react.JSX.Element;
243
+
244
+ interface DecisionFactorRowProps {
245
+ label: string;
246
+ /** the short value on the right, e.g. "Alta", "Estable", "65%" */
247
+ statusText: string;
248
+ statusClassName?: string;
249
+ /** 0-100, the weight/strength of this factor */
250
+ value: number;
251
+ fillClassName?: string;
252
+ segments?: number;
253
+ description: string;
254
+ }
255
+ /**
256
+ * A row in the "Decision Factors" panel (Elasticity, Supply Cost,
257
+ * Inventory...). Designed to be populated directly from the `factors`
258
+ * array returned by the backend's POST /predict - each factor from the
259
+ * API maps 1 to 1 to one of these rows.
260
+ */
261
+ declare function DecisionFactorRow({ label, statusText, statusClassName, value, fillClassName, segments, description, }: DecisionFactorRowProps): react.JSX.Element;
262
+
263
+ type CompetitorTrend = "up" | "down" | "stable";
264
+ interface CompetitorRowProps {
265
+ name: string;
266
+ /** initial shown in the avatar, e.g. "O" */
267
+ avatarLetter: string;
268
+ avatarBgClassName: string;
269
+ avatarTextClassName: string;
270
+ distanceKm: number;
271
+ price: string;
272
+ trend: CompetitorTrend;
273
+ /** e.g. "Hace 2h" or "Sin cambios (24h)" */
274
+ trendLabel: string;
275
+ }
276
+ /**
277
+ * A row in the "Nearby Competitors" panel. Designed to be populated
278
+ * directly from the array returned by the backend's GET /competitors.
279
+ */
280
+ declare function CompetitorRow({ name, avatarLetter, avatarBgClassName, avatarTextClassName, distanceKm, price, trend, trendLabel, }: CompetitorRowProps): react.JSX.Element;
281
+
282
+ interface PriceRangeSliderProps {
283
+ min: number;
284
+ max: number;
285
+ step?: number;
286
+ /** the value the user is currently dragging */
287
+ value: number;
288
+ onChange: (value: number) => void;
289
+ /** reference "current" price, shown at the top left */
290
+ currentPrice: number;
291
+ /** price suggested by the model, shown at the top right */
292
+ recommendedPrice: number;
293
+ currencyFormatter: (value: number) => string;
294
+ }
295
+ /**
296
+ * The Simulator's "Adjust the price to evaluate the projected impact"
297
+ * slider. Fully controlled (value/onChange come from the parent) - it
298
+ * holds no state of its own, so it doesn't need "use client" by itself:
299
+ * it inherits it from the screen component that does manage the state.
300
+ */
301
+ declare function PriceRangeSlider({ min, max, step, value, onChange, currentPrice, recommendedPrice, currencyFormatter, }: PriceRangeSliderProps): react.JSX.Element;
302
+
303
+ interface MiniMapCardProps {
304
+ overlayTitle: string;
305
+ /** text already assembled with the count included, e.g. "Basado en 3 competidores clave" - don't repeat it in competitorCount. */
306
+ overlayLinkLabel: string;
307
+ /** used only for accessibility (aria-label), not rendered on screen again - overlayLinkLabel already has the number in the right place in the sentence. */
308
+ competitorCount: number;
309
+ }
310
+ /**
311
+ * The decorative map card below "Decision Factors".
312
+ *
313
+ * *** PLACEHOLDER ***: the design uses a screenshot of a real map; here an
314
+ * abstract SVG map is drawn instead (no dependencies or API key) that
315
+ * occupies the same space and keeps the text overlay identical. Once a
316
+ * real integration (Google Maps / Mapbox) exists with the location and
317
+ * geolocated competitors, this is the component that gets replaced -
318
+ * the rest of the screen doesn't change because it only depends on this
319
+ * interface.
320
+ */
321
+ declare function MiniMapCard({ overlayTitle, overlayLinkLabel, competitorCount }: MiniMapCardProps): react.JSX.Element;
322
+
323
+ interface SidebarStation {
324
+ id: string;
325
+ label: string;
326
+ }
327
+ interface SidebarNavItem {
328
+ key: string;
329
+ label: string;
330
+ icon: ReactNode;
331
+ active?: boolean;
332
+ href?: string;
333
+ }
334
+ interface SidebarUser {
335
+ name: string;
336
+ role: string;
337
+ }
338
+ interface SidebarProps {
339
+ brandName?: string;
340
+ stations: SidebarStation[];
341
+ selectedStationId: string;
342
+ onSelectStation: (stationId: string) => void;
343
+ navItems: SidebarNavItem[];
344
+ user: SidebarUser;
345
+ onLogout?: () => void;
346
+ }
347
+ /**
348
+ * The fixed 260px sidebar. It receives EVERYTHING via props - stations,
349
+ * nav items, user - instead of having anything hardcoded (beyond the
350
+ * default brand name). This is what lets it serve "multiple users":
351
+ * whoever uses it decides which user/stations to show, this component
352
+ * just knows how to render them. The station list currently uses a
353
+ * native <select> instead of a custom dropdown - it's the simplest and
354
+ * most accessible option; it can be replaced by a more elaborate menu
355
+ * without touching the rest of the screen.
356
+ */
357
+ declare function Sidebar({ brandName, stations, selectedStationId, onSelectStation, navItems, user, onLogout, }: SidebarProps): react.JSX.Element;
358
+
359
+ interface TopBarProps {
360
+ stationName: string;
361
+ lastUpdatedLabel: string;
362
+ onRefresh?: () => void;
363
+ refreshing?: boolean;
364
+ /** optional content rendered between the station info and the refresh button - e.g. product tabs. */
365
+ middleContent?: ReactNode;
366
+ }
367
+ /**
368
+ * The fixed top bar (80px, with blur), to the right of the sidebar.
369
+ * It only knows the active station's name and the "last updated"
370
+ * state - the concrete data comes from the screen that uses it.
371
+ */
372
+ declare function TopBar({ stationName, lastUpdatedLabel, onRefresh, refreshing, middleContent, }: TopBarProps): react.JSX.Element;
373
+
374
+ interface ConfirmDialogProps {
375
+ open: boolean;
376
+ onClose: () => void;
377
+ onConfirm: () => void;
378
+ title: string;
379
+ productLabel: string;
380
+ description: ReactNode;
381
+ currentPrice: string;
382
+ newPrice: string;
383
+ marginImpactLabel: string;
384
+ volumeImpactLabel: string;
385
+ confirming?: boolean;
386
+ }
387
+ /**
388
+ * The "Aplicar precio recomendado" / "Confirmar y aplicar" modal. Fully
389
+ * controlled by `open` - it has no state of its own, so the screen that
390
+ * uses it decides when to show it (e.g. after a successful /simulate
391
+ * response) and what happens on confirm (calling /prices/apply).
392
+ */
393
+ declare function ConfirmDialog({ open, onClose, onConfirm, title, productLabel, description, currentPrice, newPrice, marginImpactLabel, volumeImpactLabel, confirming, }: ConfirmDialogProps): react.JSX.Element | null;
394
+
395
+ export { ArrowDownIcon, ArrowUpIcon, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, BoltIcon, Button, type ButtonProps, type ButtonVariant, Card, type CardProps, CheckCircleIcon, ChevronDownIcon, type ColorTokens, CompetitorRow, type CompetitorRowProps, type CompetitorTrend, ConfirmDialog, type ConfirmDialogProps, DecisionFactorRow, type DecisionFactorRowProps, FuelIcon, FuelPumpSolidIcon, LogOutIcon, MapPinIcon, MiniMapCard, type MiniMapCardProps, NavItem, type NavItemProps, PriceRangeSlider, type PriceRangeSliderProps, ProgressBar, type ProgressBarProps, RefreshIcon, type SegmentedTabOption, SegmentedTabs, type SegmentedTabsProps, Sidebar, type SidebarNavItem, type SidebarProps, type SidebarStation, type SidebarUser, Snackbar, type SnackbarProps, type SnackbarTone, StatTile, type StatTileProps, TagIcon, TargetIcon, TopBar, type TopBarProps, TrendDownIcon, TrendUpIcon, TrendUpSolidIcon, UserCircleIcon, WalletIcon, WalletSolidIcon, XIcon, colors };