@axzydev/axzy_ui_system 1.3.5 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +562 -506
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +107 -3
- package/dist/index.d.ts +107 -3
- package/dist/index.js +433 -378
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/App.tsx +6 -0
- package/src/components/atoms/input/input.props.ts +5 -0
- package/src/components/atoms/input/input.tsx +24 -17
- package/src/components/atoms/input-number/input-number.doc.mdx +74 -0
- package/src/components/atoms/input-number/input-number.props.ts +66 -0
- package/src/components/atoms/input-number/input-number.stories.tsx +111 -0
- package/src/components/atoms/input-number/input-number.tsx +86 -0
- package/src/components/molecules/date-picker/datePicker.tsx +1 -0
- package/src/components/molecules/dropdown-menu/dropdown-menu.tsx +1 -0
- package/src/components/molecules/multi-select/multi-select.tsx +1 -0
- package/src/components/molecules/search-select/search-select.tsx +1 -0
- package/src/components/molecules/time-picker/timePicker.tsx +1 -0
- package/src/components/organisms/dialog/dialog.stories.tsx +48 -0
- package/src/components/organisms/sidebar/sidebar.doc.mdx +12 -12
- package/src/hooks/useClickOutside.ts +6 -0
- package/src/hooks/useFloatingPanel.ts +4 -1
- package/src/index.ts +4 -2
- package/src/sandbox/navigation.ts +2 -0
- package/src/showcases/FeedbackShowcases.tsx +16 -1
- package/src/showcases/FormShowcases.tsx +287 -1
- package/src/showcases/HomeShowcase.tsx +552 -331
- package/src/showcases/ProductoDetalle.tsx +181 -0
- package/src/showcases/ProductoIcono.tsx +25 -0
- package/src/showcases/StructureShowcases.tsx +2 -2
- package/src/showcases/productos.ts +179 -0
- package/src/showcases/techIcons.ts +67 -0
|
@@ -13,13 +13,13 @@ Vertical navigation sidebar with collapsible submenus, tooltips when collapsed,
|
|
|
13
13
|
|------|------|---------|-------------|
|
|
14
14
|
| `navigationItems` | `ITNavigationItem[]` | — | Navigation structure: top-level items with optional sub-items. |
|
|
15
15
|
| `isCollapsed` | `boolean` | `false` | Whether the sidebar is collapsed to icon-only mode. |
|
|
16
|
-
| `onToggleCollapse` | `() => void` | — |
|
|
17
|
-
| `visibleOnMobile` | `boolean` | `false` |
|
|
16
|
+
| `onToggleCollapse` | `() => void` | — | Accepted for API compatibility but never invoked by `ITSidebar`: the component renders no collapse toggle button (the rail expands on pointer hover). `ITLayout` still passes it through. |
|
|
17
|
+
| `visibleOnMobile` | `boolean` | `false` | When `true`, the sidebar is always fully expanded (the collapsed 88px rail is disabled) and visible at every breakpoint. |
|
|
18
18
|
| `onItemClick` | `(item: ITNavigationItem) => void` | — | Callback when a top-level navigation item is clicked. |
|
|
19
19
|
| `onSubItemClick` | `(subitem: ITNavigationSubItem) => void` | — | Callback when a sub-navigation item is clicked. |
|
|
20
20
|
| `subitemConnector` | `"dot" \| "\|" \| "none"` | `"dot"` | Visual connector style for sub-items. |
|
|
21
21
|
| `className` | `string` | — | Additional CSS classes on the sidebar `<aside>`. |
|
|
22
|
-
| `header` | `ReactNode` | — | Optional content rendered above the navigation list (e.g. a wordmark). Hidden while the sidebar is collapsed,
|
|
22
|
+
| `header` | `ReactNode` | — | Optional content rendered above the navigation list (e.g. a wordmark). Hidden while the sidebar is collapsed, so it only appears when the hover-expanded rail is shown. |
|
|
23
23
|
|
|
24
24
|
### ITNavigationItem
|
|
25
25
|
|
|
@@ -30,7 +30,7 @@ Vertical navigation sidebar with collapsible submenus, tooltips when collapsed,
|
|
|
30
30
|
| `icon` | `ReactNode` | Icon rendered left of the label. |
|
|
31
31
|
| `action` | `() => void` | Click handler for top-level items without submenus. |
|
|
32
32
|
| `isActive` | `boolean` | Whether this item is active/highlighted. |
|
|
33
|
-
| `subitems` | `ITNavigationSubItemEntry[]` |
|
|
33
|
+
| `subitems` | `ITNavigationSubItemEntry[]` | Array of entries: plain sub-items or titled groups (`ITNavigationSubItemGroup`). |
|
|
34
34
|
| `badge` | `string` | Badge text (e.g. notification count). |
|
|
35
35
|
|
|
36
36
|
### ITNavigationSubItem
|
|
@@ -77,7 +77,6 @@ const items = [
|
|
|
77
77
|
<ITSidebar
|
|
78
78
|
navigationItems={items}
|
|
79
79
|
isCollapsed={false}
|
|
80
|
-
onToggleCollapse={toggleCollapse}
|
|
81
80
|
subitemConnector="dot"
|
|
82
81
|
/>
|
|
83
82
|
```
|
|
@@ -104,17 +103,18 @@ const items = [
|
|
|
104
103
|
];
|
|
105
104
|
```
|
|
106
105
|
|
|
106
|
+
Discriminate entries with `"items" in entry`. The heading style (uppercase, `0.68rem`, weight 700, `0.08em` tracking, `var(--it-sidebar-icon-color, var(--color-secondary-400))`) is applied by the component and cannot be overridden per group.
|
|
107
|
+
|
|
107
108
|
## Notes
|
|
108
109
|
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
-
|
|
112
|
-
- Collapsed mode shrinks to icon-only; tooltips show labels on hover with glassmorphism style.
|
|
113
|
-
- Submenus expand/collapse with smooth transitions when a parent item is clicked.
|
|
110
|
+
- There is no collapse toggle button: the collapsed 88px rail expands and collapses automatically on pointer hover (300 ms leave delay).
|
|
111
|
+
- Collapsed mode shrinks to icon-only; a glassmorphism panel shows the item's sub-items on hover. For an item whose `subitems` contain groups, that panel **flattens** every group into one plain list and renders **no** group headings.
|
|
112
|
+
- Submenus expand/collapse with smooth transitions when a parent item is clicked; the expanded submenu is animated with a `max-h` transition (cap `1000px`).
|
|
114
113
|
- Active item highlighting uses a colored left-border accent and subtle background tint.
|
|
115
114
|
- Badge support renders a notification-style counter on the item.
|
|
116
|
-
- `subitemConnector` styles the visual link between parent and sub-items: `dot` (default), `|` (vertical bar), or `none`.
|
|
117
|
-
-
|
|
115
|
+
- `subitemConnector` styles the visual link between parent and sub-items: `dot` (default), `|` (vertical bar), or `none`. Grouping does not change it: the `|` guide is a single `border-left` on the outer submenu `<ul>` only.
|
|
116
|
+
- An active sub-item inside a group still auto-expands its parent, because auto-expand flattens groups the same way the collapsed flyout does.
|
|
117
|
+
- `visibleOnMobile` forces the sidebar visible at every breakpoint and disables the collapsed rail, so it stays fully expanded.
|
|
118
118
|
- Themed via CSS variables for consistent colors, shadows, and glassmorphism effects across light and dark modes.
|
|
119
119
|
- Accessible by default: the `<nav>` is labelled and carries the `id` referenced by the toggle's `aria-controls`, the toggle exposes `aria-expanded` plus a visible `focus-visible` ring, group toggles expose `aria-expanded`/`aria-controls` and respond to Enter/Space, submenus are labelled `role="group"`, and the active sub-item carries `aria-current="page"`.
|
|
120
120
|
- `subitems` accepts plain sub-items or titled groups (`label` + `items`); the group heading is rendered as a plain `<span>` inside a non-focusable `<div>`, so it is non-interactive and stays out of the tab order.
|
|
@@ -14,6 +14,12 @@ const useClickOutside = (
|
|
|
14
14
|
// child overlay or its contents.
|
|
15
15
|
if (clickedDialog && clickedDialog !== currentDialog) return;
|
|
16
16
|
|
|
17
|
+
// Floating panels (select/date/time pickers, menus) are portaled to
|
|
18
|
+
// <body>, so they sit outside the DOM of the dialog/drawer that opened
|
|
19
|
+
// them. Picking an option there must not count as an outside click.
|
|
20
|
+
const clickedFloating = target?.closest("[data-it-floating]");
|
|
21
|
+
if (clickedFloating && !ref.current?.contains(clickedFloating)) return;
|
|
22
|
+
|
|
17
23
|
if (ref.current && !ref.current.contains(event.target as Node)) {
|
|
18
24
|
callback();
|
|
19
25
|
}
|
|
@@ -57,7 +57,10 @@ export function useFloatingPanel(
|
|
|
57
57
|
const flipAbove = spaceBelow < panelHeight + offset && spaceAbove > spaceBelow;
|
|
58
58
|
|
|
59
59
|
const width = matchWidth ? rect.width : undefined;
|
|
60
|
-
|
|
60
|
+
// With matchWidth the final width is the anchor's. Measuring the panel
|
|
61
|
+
// here would read its pre-style width (a full-width block on first
|
|
62
|
+
// paint) and clamp `left` to the viewport edge until the next update.
|
|
63
|
+
const panelWidth = width ?? panelRef.current?.offsetWidth ?? 0;
|
|
61
64
|
let left =
|
|
62
65
|
align === "end" && panelWidth
|
|
63
66
|
? rect.right - panelWidth
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,7 @@ import ITFormHeader from "./components/molecules/form-header/form-header";
|
|
|
36
36
|
import ITGrid from "./components/atoms/grid/grid";
|
|
37
37
|
import ITImage from "./components/atoms/image/image";
|
|
38
38
|
import ITInput from "./components/atoms/input/input";
|
|
39
|
+
import ITInputNumber from "./components/atoms/input-number/input-number";
|
|
39
40
|
import ITNavbar from "./components/organisms/navbar/navbar";
|
|
40
41
|
import ITPage from "./components/templates/page/page";
|
|
41
42
|
import ITPageHeader from "./components/organisms/page-header/page-header";
|
|
@@ -90,6 +91,7 @@ import type { ITFormHeaderProps } from "./components/molecules/form-header/form-
|
|
|
90
91
|
import type { ITGridProps } from "./components/atoms/grid/grid.props";
|
|
91
92
|
import type { ITImageProps } from "./components/atoms/image/image.props";
|
|
92
93
|
import type { ITInputProps } from "./components/atoms/input/input.props";
|
|
94
|
+
import type { ITInputNumberProps } from "./components/atoms/input-number/input-number.props";
|
|
93
95
|
import type { ITNavbarProps } from "./components/organisms/navbar/navbar.props";
|
|
94
96
|
import type { ITPageProps } from "./components/templates/page/page.props";
|
|
95
97
|
import type { ITPageHeaderProps } from "./components/organisms/page-header/page-header.props";
|
|
@@ -145,7 +147,7 @@ export {
|
|
|
145
147
|
ITCard, ITCheckbox, ITChip, ITChipInput, ITConfirmDialog, ITDataTable, ITDatePicker,
|
|
146
148
|
ITDropdownMenu, ITField, ITMaskedInput, ITMultiSelect,
|
|
147
149
|
ITDialog, ITDivider, ITDrawer,
|
|
148
|
-
ITDropfile, ITEmptyState, ITFlex, ITFormBuilder, ITFormHeader, ITGrid, ITImage, ITInput, ITLayout,
|
|
150
|
+
ITDropfile, ITEmptyState, ITFlex, ITFormBuilder, ITFormHeader, ITGrid, ITImage, ITInput, ITInputNumber, ITLayout,
|
|
149
151
|
ITLoader, ITNavbar, ITPage, ITPageHeader, ITPagination, ITPopover, ITProgress,
|
|
150
152
|
ITRadioGroup, ITSelect, ITSearchSelect,
|
|
151
153
|
ITSearchTable,
|
|
@@ -169,7 +171,7 @@ export {
|
|
|
169
171
|
ITDialogProps, ITDividerProps, ITDrawerProps,
|
|
170
172
|
ITEmptyStateProps, ITFlexProps,
|
|
171
173
|
ITFormBuilderProps, ITFormHeaderProps,
|
|
172
|
-
ITGridProps, ITImageProps, ITInputProps,
|
|
174
|
+
ITGridProps, ITImageProps, ITInputProps, ITInputNumberProps,
|
|
173
175
|
ITLayoutProps, ITLoaderProps, ITNavbarProps,
|
|
174
176
|
ITPageProps, ITPageHeaderProps,
|
|
175
177
|
ITNavigationItem, ITNavigationSubItem,
|
|
@@ -56,6 +56,7 @@ export const SANDBOX_GROUPS: SandboxGroup[] = [
|
|
|
56
56
|
subitems: [
|
|
57
57
|
{ id: "button", label: "ITButton" },
|
|
58
58
|
{ id: "input", label: "ITInput" },
|
|
59
|
+
{ id: "inputnumber", label: "ITInputNumber" },
|
|
59
60
|
{ id: "select", label: "ITSelect" },
|
|
60
61
|
{ id: "searchselect", label: "ITSearchSelect" },
|
|
61
62
|
{ id: "multiselect", label: "ITMultiSelect" },
|
|
@@ -66,6 +67,7 @@ export const SANDBOX_GROUPS: SandboxGroup[] = [
|
|
|
66
67
|
{ id: "maskedinput", label: "ITMaskedInput" },
|
|
67
68
|
{ id: "calendar", label: "ITCalendar" },
|
|
68
69
|
{ id: "slidetoggle", label: "ITSlideToggle" },
|
|
70
|
+
{ id: "segmentedcontrol", label: "ITSegmentedControl" },
|
|
69
71
|
{ id: "dropfile", label: "ITDropfile" },
|
|
70
72
|
{ id: "wysiwyg", label: "ITWysiwyg" },
|
|
71
73
|
{ id: "formbuilder", label: "ITFormBuilder" },
|
|
@@ -2,6 +2,7 @@ import React, { useState } from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
ITDialog,
|
|
4
4
|
ITButton,
|
|
5
|
+
ITSearchSelect,
|
|
5
6
|
ITSlideToggle,
|
|
6
7
|
ITToast,
|
|
7
8
|
ITInput,
|
|
@@ -18,8 +19,9 @@ import { ShowcaseLayout } from "./ShowcaseLayout";
|
|
|
18
19
|
export const DialogShowcase = () => {
|
|
19
20
|
const [isOpen, setIsOpen] = useState(false);
|
|
20
21
|
const [useFormHeader, setUseFormHeader] = useState(true);
|
|
22
|
+
const [auditor, setAuditor] = useState("");
|
|
21
23
|
|
|
22
|
-
const code = `<ITDialog\n isOpen={${isOpen}}\n onClose={() => setIsOpen(false)}\n title="Confirmación de Auditoría"\n useFormHeader={${useFormHeader}}\n>\n <p>¿Estás seguro de registrar esta auditoría?</p>\n</ITDialog>`;
|
|
24
|
+
const code = `<ITDialog\n isOpen={${isOpen}}\n onClose={() => setIsOpen(false)}\n title="Confirmación de Auditoría"\n useFormHeader={${useFormHeader}}\n>\n <p>¿Estás seguro de registrar esta auditoría?</p>\n <ITSearchSelect placeholder="Selecciona un auditor" ... />\n</ITDialog>`;
|
|
23
25
|
|
|
24
26
|
return (
|
|
25
27
|
<ShowcaseLayout
|
|
@@ -39,6 +41,19 @@ export const DialogShowcase = () => {
|
|
|
39
41
|
<p className="text-sm text-slate-600 dark:text-slate-300">
|
|
40
42
|
Se guardará un registro inmutable en los logs de la blockchain corporativa. Esta acción es irreversible.
|
|
41
43
|
</p>
|
|
44
|
+
{/* Los paneles flotantes (selects, pickers, menús) se renderizan en
|
|
45
|
+
un portal fuera del diálogo: elegir una opción no debe cerrarlo. */}
|
|
46
|
+
<ITSearchSelect
|
|
47
|
+
label="Auditor responsable"
|
|
48
|
+
placeholder="Busca un auditor..."
|
|
49
|
+
options={[
|
|
50
|
+
{ value: "ana", label: "Ana Torres" },
|
|
51
|
+
{ value: "luis", label: "Luis Paredes" },
|
|
52
|
+
{ value: "marta", label: "Marta Ruiz" },
|
|
53
|
+
]}
|
|
54
|
+
value={auditor}
|
|
55
|
+
onChange={(next) => setAuditor(String(next))}
|
|
56
|
+
/>
|
|
42
57
|
<div className="flex justify-end gap-3 pt-4 border-t">
|
|
43
58
|
<ITButton label="Cancelar" variant="ghost" onClick={() => setIsOpen(false)} />
|
|
44
59
|
<ITButton label="Autorizar" color="success" onClick={() => { alert("Transacción firmada!"); setIsOpen(false); }} />
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useState, useEffect, useMemo } from "react";
|
|
2
|
-
import { FaSync, FaSave, FaTrash, FaEdit, FaCode, FaCheck } from "react-icons/fa";
|
|
2
|
+
import { FaSync, FaSave, FaTrash, FaEdit, FaCode, FaCheck, FaCalendar } from "react-icons/fa";
|
|
3
3
|
import {
|
|
4
4
|
ITButton,
|
|
5
5
|
ITInput,
|
|
6
|
+
ITInputNumber,
|
|
6
7
|
ITSelect,
|
|
7
8
|
ITSearchSelect,
|
|
8
9
|
ITDatePicker,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
ITMultiSelect,
|
|
18
19
|
ITChipInput,
|
|
19
20
|
ITBadget,
|
|
21
|
+
ITSegmentedControl,
|
|
20
22
|
UploadStatus
|
|
21
23
|
} from "../index";
|
|
22
24
|
import { ShowcaseLayout, CodeViewer } from "./ShowcaseLayout";
|
|
@@ -2091,3 +2093,287 @@ export const MaskedInputShowcase = () => {
|
|
|
2091
2093
|
/>
|
|
2092
2094
|
);
|
|
2093
2095
|
};
|
|
2096
|
+
|
|
2097
|
+
// 17. ITSegmentedControl Showcase
|
|
2098
|
+
export const SegmentedControlShowcase = () => {
|
|
2099
|
+
const [view, setView] = useState("day");
|
|
2100
|
+
const [size, setSize] = useState<any>("md");
|
|
2101
|
+
const [disabled, setDisabled] = useState(false);
|
|
2102
|
+
const [withIcons, setWithIcons] = useState(false);
|
|
2103
|
+
|
|
2104
|
+
const options = withIcons
|
|
2105
|
+
? [
|
|
2106
|
+
{ value: "day", label: "Día", icon: <FaCheck size={10} /> },
|
|
2107
|
+
{ value: "week", label: "Semana", icon: <FaCalendar size={10} /> },
|
|
2108
|
+
{ value: "month", label: "Mes", icon: <FaSave size={10} /> },
|
|
2109
|
+
]
|
|
2110
|
+
: [
|
|
2111
|
+
{ value: "day", label: "Día" },
|
|
2112
|
+
{ value: "week", label: "Semana" },
|
|
2113
|
+
{ value: "month", label: "Mes" },
|
|
2114
|
+
];
|
|
2115
|
+
|
|
2116
|
+
const code = `<ITSegmentedControl\n options={[\n { value: "day", label: "Día" },\n { value: "week", label: "Semana" },\n { value: "month", label: "Mes" },\n ]}\n value="${view}"\n onChange={(value) => setView(value)}\n size="${size}"\n disabled={${disabled}}\n/>`;
|
|
2117
|
+
|
|
2118
|
+
return (
|
|
2119
|
+
<ShowcaseLayout
|
|
2120
|
+
title="ITSegmentedControl"
|
|
2121
|
+
description="Grupo de botones mutuamente excluyentes estilo iOS para alternar vistas o filtros."
|
|
2122
|
+
code={code}
|
|
2123
|
+
demo={
|
|
2124
|
+
<div className="flex flex-col items-center gap-3">
|
|
2125
|
+
<ITSegmentedControl
|
|
2126
|
+
options={options}
|
|
2127
|
+
value={view}
|
|
2128
|
+
onChange={setView}
|
|
2129
|
+
size={size}
|
|
2130
|
+
disabled={disabled}
|
|
2131
|
+
/>
|
|
2132
|
+
<span className="text-xs font-mono text-slate-500">Seleccionado: "{view}"</span>
|
|
2133
|
+
</div>
|
|
2134
|
+
}
|
|
2135
|
+
controls={
|
|
2136
|
+
<>
|
|
2137
|
+
<ITSelect
|
|
2138
|
+
name="seg_size_ctrl"
|
|
2139
|
+
label="Tamaño"
|
|
2140
|
+
value={size}
|
|
2141
|
+
onChange={(e: any) => setSize(e.target.value)}
|
|
2142
|
+
options={[
|
|
2143
|
+
{ label: "Small", value: "sm" },
|
|
2144
|
+
{ label: "Medium", value: "md" },
|
|
2145
|
+
]}
|
|
2146
|
+
/>
|
|
2147
|
+
<div className="flex items-center justify-between">
|
|
2148
|
+
<span className="text-sm font-semibold text-gray-700">Con iconos</span>
|
|
2149
|
+
<ITSlideToggle isOn={withIcons} onToggle={setWithIcons} size="sm" />
|
|
2150
|
+
</div>
|
|
2151
|
+
<div className="flex items-center justify-between">
|
|
2152
|
+
<span className="text-sm font-semibold text-gray-700">Deshabilitado</span>
|
|
2153
|
+
<ITSlideToggle isOn={disabled} onToggle={setDisabled} activeColor="danger" size="sm" />
|
|
2154
|
+
</div>
|
|
2155
|
+
</>
|
|
2156
|
+
}
|
|
2157
|
+
gallery={
|
|
2158
|
+
<div className="flex flex-wrap items-center gap-6">
|
|
2159
|
+
<ITSegmentedControl
|
|
2160
|
+
options={[
|
|
2161
|
+
{ value: "list", label: "List" },
|
|
2162
|
+
{ value: "grid", label: "Grid" },
|
|
2163
|
+
]}
|
|
2164
|
+
value="list"
|
|
2165
|
+
onChange={() => {}}
|
|
2166
|
+
size="sm"
|
|
2167
|
+
/>
|
|
2168
|
+
<ITSegmentedControl
|
|
2169
|
+
options={[
|
|
2170
|
+
{ value: "light", label: "☀️" },
|
|
2171
|
+
{ value: "dark", label: "🌙" },
|
|
2172
|
+
{ value: "system", label: "💻" },
|
|
2173
|
+
]}
|
|
2174
|
+
value="dark"
|
|
2175
|
+
onChange={() => {}}
|
|
2176
|
+
size="sm"
|
|
2177
|
+
/>
|
|
2178
|
+
<ITSegmentedControl
|
|
2179
|
+
options={[
|
|
2180
|
+
{ value: "a", label: "Activo" },
|
|
2181
|
+
{ value: "b", label: "Inactivo" },
|
|
2182
|
+
]}
|
|
2183
|
+
value="a"
|
|
2184
|
+
onChange={() => {}}
|
|
2185
|
+
disabled
|
|
2186
|
+
/>
|
|
2187
|
+
</div>
|
|
2188
|
+
}
|
|
2189
|
+
doc={{
|
|
2190
|
+
summary:
|
|
2191
|
+
"Control segmentado: una sola opción activa a la vez. Útil para alternar vistas (lista/grid), rangos (día/semana/mes) o filtros.",
|
|
2192
|
+
description:
|
|
2193
|
+
"Componente totalmente controlado: recibe el valor activo vía value y notifica los cambios vía onChange.",
|
|
2194
|
+
examples: [
|
|
2195
|
+
'<ITSegmentedControl options={[{ value: "day", label: "Day" }, { value: "week", label: "Week" }]} value={view} onChange={setView} />',
|
|
2196
|
+
'<ITSegmentedControl size="sm" options={[{ value: "list", label: "List", icon: <FaList /> }, { value: "grid", label: "Grid", icon: <FaTh /> }]} value={view} onChange={setView} />',
|
|
2197
|
+
],
|
|
2198
|
+
props: [
|
|
2199
|
+
{ name: "options", type: "ISegmentedOption[]", required: true, description: "Opciones excluyentes { value, label, icon? }." },
|
|
2200
|
+
{ name: "value", type: "string", required: true, description: "Valor de la opción activa." },
|
|
2201
|
+
{ name: "onChange", type: "(value: string) => void", required: true, description: "Se dispara al elegir otra opción." },
|
|
2202
|
+
{ name: "size", type: '"sm" | "md"', default: '"md"', description: "Tamaño del control." },
|
|
2203
|
+
{ name: "className", type: "string", description: "Clases adicionales en el contenedor." },
|
|
2204
|
+
{ name: "disabled", type: "boolean", default: "false", description: "Deshabilita todas las opciones." },
|
|
2205
|
+
],
|
|
2206
|
+
notes: [
|
|
2207
|
+
"Solo una opción puede estar activa; es mutuamente excluyente por diseño.",
|
|
2208
|
+
"Cada opción acepta un icon opcional junto al label.",
|
|
2209
|
+
"No expone roles ARIA de radiogroup: si se usa en un formulario accesible habría que agregarlos.",
|
|
2210
|
+
],
|
|
2211
|
+
}}
|
|
2212
|
+
/>
|
|
2213
|
+
);
|
|
2214
|
+
};
|
|
2215
|
+
|
|
2216
|
+
// 18. ITInputNumber Showcase
|
|
2217
|
+
export const InputNumberShowcase = () => {
|
|
2218
|
+
const [amount, setAmount] = useState<number | null>(1234.5);
|
|
2219
|
+
const [quantity, setQuantity] = useState<number | null>(3);
|
|
2220
|
+
const [decimals, setDecimals] = useState<any>("2");
|
|
2221
|
+
const [disabled, setDisabled] = useState(false);
|
|
2222
|
+
const [required, setRequired] = useState(false);
|
|
2223
|
+
const [showError, setShowError] = useState(false);
|
|
2224
|
+
|
|
2225
|
+
const code = `<ITInputNumber\n name="amount"\n label="Monto"\n value={${amount === null ? "null" : amount}}\n prefix="$"\n decimals={${decimals}}\n disabled={${disabled}}\n required={${required}}\n onChange={(value) => setAmount(value)}\n/>
|
|
2226
|
+
|
|
2227
|
+
// El valor que recibes ya es un number (o undefined si está vacío):
|
|
2228
|
+
onChange={(value) => console.log(value + 1)} // ${amount === null ? "undefined" : amount + 1}`;
|
|
2229
|
+
|
|
2230
|
+
return (
|
|
2231
|
+
<ShowcaseLayout
|
|
2232
|
+
title="ITInputNumber"
|
|
2233
|
+
description="Campo numérico con formato de moneda que emite un number real (entero o decimal) en onChange."
|
|
2234
|
+
code={code}
|
|
2235
|
+
demo={
|
|
2236
|
+
<div className="w-full max-w-sm space-y-3">
|
|
2237
|
+
<ITInputNumber
|
|
2238
|
+
name="amount"
|
|
2239
|
+
label="Monto"
|
|
2240
|
+
placeholder="0.00"
|
|
2241
|
+
prefix="$"
|
|
2242
|
+
value={amount}
|
|
2243
|
+
onChange={setAmount}
|
|
2244
|
+
decimals={Number(decimals)}
|
|
2245
|
+
disabled={disabled}
|
|
2246
|
+
required={required}
|
|
2247
|
+
touched={showError}
|
|
2248
|
+
error={showError ? "El monto es obligatorio" : undefined}
|
|
2249
|
+
/>
|
|
2250
|
+
<p className="text-xs font-mono text-slate-500">
|
|
2251
|
+
value: {amount === null ? "null" : `${amount} (${typeof amount})`}
|
|
2252
|
+
</p>
|
|
2253
|
+
<div className="grid grid-cols-2 gap-3">
|
|
2254
|
+
<ITInputNumber
|
|
2255
|
+
name="quantity"
|
|
2256
|
+
label="Cantidad (enteros)"
|
|
2257
|
+
value={quantity}
|
|
2258
|
+
decimals={0}
|
|
2259
|
+
min={1}
|
|
2260
|
+
max={99}
|
|
2261
|
+
onChange={setQuantity}
|
|
2262
|
+
/>
|
|
2263
|
+
<ITInputNumber
|
|
2264
|
+
name="bounded"
|
|
2265
|
+
label="Entre 10 y 500"
|
|
2266
|
+
min={10}
|
|
2267
|
+
max={500}
|
|
2268
|
+
value={null}
|
|
2269
|
+
onChange={() => {}}
|
|
2270
|
+
/>
|
|
2271
|
+
</div>
|
|
2272
|
+
</div>
|
|
2273
|
+
}
|
|
2274
|
+
controls={
|
|
2275
|
+
<>
|
|
2276
|
+
<ITSelect
|
|
2277
|
+
name="in_decimals"
|
|
2278
|
+
label="Decimales"
|
|
2279
|
+
value={decimals}
|
|
2280
|
+
onChange={(e: any) => setDecimals(e.target.value)}
|
|
2281
|
+
options={[
|
|
2282
|
+
{ label: "0 (enteros)", value: "0" },
|
|
2283
|
+
{ label: "1 decimal", value: "1" },
|
|
2284
|
+
{ label: "2 decimales", value: "2" },
|
|
2285
|
+
]}
|
|
2286
|
+
/>
|
|
2287
|
+
<div className="flex items-center justify-between">
|
|
2288
|
+
<span className="text-sm font-semibold text-gray-700">Requerido</span>
|
|
2289
|
+
<ITSlideToggle isOn={required} onToggle={setRequired} size="sm" />
|
|
2290
|
+
</div>
|
|
2291
|
+
<div className="flex items-center justify-between">
|
|
2292
|
+
<span className="text-sm font-semibold text-gray-700">Simular error</span>
|
|
2293
|
+
<ITSlideToggle isOn={showError} onToggle={setShowError} activeColor="danger" size="sm" />
|
|
2294
|
+
</div>
|
|
2295
|
+
<div className="flex items-center justify-between">
|
|
2296
|
+
<span className="text-sm font-semibold text-gray-700">Deshabilitado</span>
|
|
2297
|
+
<ITSlideToggle isOn={disabled} onToggle={setDisabled} activeColor="danger" size="sm" />
|
|
2298
|
+
</div>
|
|
2299
|
+
</>
|
|
2300
|
+
}
|
|
2301
|
+
gallery={
|
|
2302
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
2303
|
+
<div className="w-full max-w-sm">
|
|
2304
|
+
<ITInputNumber
|
|
2305
|
+
name="g_amount"
|
|
2306
|
+
label="Prefijo $"
|
|
2307
|
+
prefix="$"
|
|
2308
|
+
value={98765.43}
|
|
2309
|
+
readOnly
|
|
2310
|
+
onChange={() => {}}
|
|
2311
|
+
/>
|
|
2312
|
+
</div>
|
|
2313
|
+
<div className="w-full max-w-sm">
|
|
2314
|
+
<ITInputNumber
|
|
2315
|
+
name="g_price"
|
|
2316
|
+
label="Precio unitario"
|
|
2317
|
+
prefix="$"
|
|
2318
|
+
value={null}
|
|
2319
|
+
onChange={() => {}}
|
|
2320
|
+
/>
|
|
2321
|
+
</div>
|
|
2322
|
+
<div className="w-full max-w-sm">
|
|
2323
|
+
<ITInputNumber
|
|
2324
|
+
name="g_qty"
|
|
2325
|
+
label="Cantidad (decimals=0)"
|
|
2326
|
+
decimals={0}
|
|
2327
|
+
value={12}
|
|
2328
|
+
onChange={() => {}}
|
|
2329
|
+
/>
|
|
2330
|
+
</div>
|
|
2331
|
+
<div className="w-full max-w-sm">
|
|
2332
|
+
<ITInputNumber
|
|
2333
|
+
name="g_disabled"
|
|
2334
|
+
label="Deshabilitado"
|
|
2335
|
+
prefix="$"
|
|
2336
|
+
value={1500}
|
|
2337
|
+
disabled
|
|
2338
|
+
onChange={() => {}}
|
|
2339
|
+
/>
|
|
2340
|
+
</div>
|
|
2341
|
+
</div>
|
|
2342
|
+
}
|
|
2343
|
+
doc={{
|
|
2344
|
+
summary:
|
|
2345
|
+
"Input numérico con formato de moneda: agrupa miles, fija decimales y entrega un number en onChange/onBlur.",
|
|
2346
|
+
description:
|
|
2347
|
+
"Es un ITInput especializado (misma label, validación, tamaños e iconos). La diferencia es que siempre formatea con currencyFormat y parsea el valor antes de emitirlo, así no hay que hacer parseFloat en el consumidor.",
|
|
2348
|
+
examples: [
|
|
2349
|
+
'<ITInputNumber name="amount" label="Monto" prefix="$" value={amount} onChange={(value) => setAmount(value)} />',
|
|
2350
|
+
'<ITInputNumber name="qty" label="Cantidad" decimals={0} min={1} max={99} value={qty} onChange={setQty} />',
|
|
2351
|
+
'<ITInputNumber name="total" label="Total" value={total} onChange={(value) => setTotal((value ?? 0) + 1)} />',
|
|
2352
|
+
],
|
|
2353
|
+
props: [
|
|
2354
|
+
{ name: "name", type: "string", required: true, description: "Nombre del campo." },
|
|
2355
|
+
{ name: "value", type: "number | null", description: "Valor numérico actual." },
|
|
2356
|
+
{ name: "onChange", type: "(value, event) => void", required: true, description: "Recibe el number parseado (undefined si está vacío)." },
|
|
2357
|
+
{ name: "onBlur", type: "(value, event) => void", description: "Recibe el number parseado y clampeado a min/max." },
|
|
2358
|
+
{ name: "decimals", type: "number", default: "2", description: "Decimales permitidos; 0 = solo enteros." },
|
|
2359
|
+
{ name: "prefix", type: "string", description: "Prefijo dentro del campo (ej. \"$\"). Se ignora si hay iconLeft." },
|
|
2360
|
+
{ name: "min", type: "number", description: "Valor mínimo; se ajusta al salir del campo." },
|
|
2361
|
+
{ name: "max", type: "number", description: "Valor máximo; se ajusta al salir del campo." },
|
|
2362
|
+
{ name: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "Tamaño del campo." },
|
|
2363
|
+
{ name: "disabled", type: "boolean", default: "false", description: "Deshabilita el campo." },
|
|
2364
|
+
{ name: "readOnly", type: "boolean", default: "false", description: "Modo solo lectura." },
|
|
2365
|
+
{ name: "required", type: "boolean", default: "false", description: "Marca el campo como requerido." },
|
|
2366
|
+
{ name: "touched", type: "boolean", description: "Indica que el campo fue tocado." },
|
|
2367
|
+
{ name: "error", type: "string | boolean", description: "Error de validación (true = mensaje genérico)." },
|
|
2368
|
+
{ name: "iconLeft / iconRight", type: "ReactNode", description: "Iconos dentro del campo." },
|
|
2369
|
+
],
|
|
2370
|
+
notes: [
|
|
2371
|
+
"El valor emitido ya es un number; ya no hace falta parseFloat.",
|
|
2372
|
+
"Formatea en blur (miles + decimales) y muestra crudo mientras escribes.",
|
|
2373
|
+
"Usa decimals={0} para forzar enteros.",
|
|
2374
|
+
"min/max se aplican al salir del campo (clamp), no mientras escribes.",
|
|
2375
|
+
],
|
|
2376
|
+
}}
|
|
2377
|
+
/>
|
|
2378
|
+
);
|
|
2379
|
+
};
|