@axzydev/axzy_ui_system 1.5.1 → 1.5.3
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 +43 -0
- package/dist/index.cjs +24 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +23 -6
- package/dist/index.js.map +1 -1
- package/dist/layered.css +1 -1
- package/package.json +4 -2
- package/src/components/atoms/button/button.props.ts +3 -3
- package/src/components/atoms/button/button.tsx +27 -7
- package/src/sandbox/i18n.ts +2 -0
- package/src/showcases/DataShowcases.tsx +41 -0
- package/src/showcases/FeedbackShowcases.tsx +82 -0
- package/src/showcases/FormShowcases.tsx +320 -0
- package/src/showcases/LayoutPrimitivesShowcases.tsx +110 -0
- package/src/showcases/NavigationShowcases.tsx +94 -0
- package/src/showcases/PageShowcases.tsx +62 -0
- package/src/showcases/StructureShowcases.tsx +48 -0
- package/src/types/button.types.ts +19 -1
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"packageManager": "pnpm@10.34.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"version": "1.5.
|
|
7
|
+
"version": "1.5.3",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"sideEffects": [
|
|
10
10
|
"*.css"
|
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
"ext:build": "node vscode-extension/esbuild.mjs",
|
|
43
43
|
"ext:typecheck": "tsc --noEmit -p vscode-extension/tsconfig.json",
|
|
44
44
|
"ext:package": "node vscode-extension/esbuild.mjs && cd vscode-extension && npx --yes @vscode/vsce package --no-dependencies",
|
|
45
|
-
"audit:sandbox": "node scripts/audit-sandbox.mjs"
|
|
45
|
+
"audit:sandbox": "node scripts/audit-sandbox.mjs",
|
|
46
|
+
"docs:llm": "pnpm ext:catalog && node scripts/build-llm-docs.mjs && node scripts/build-ai-bundle.mjs",
|
|
47
|
+
"docs:ai": "node scripts/build-ai-bundle.mjs"
|
|
46
48
|
},
|
|
47
49
|
"dependencies": {
|
|
48
50
|
"clsx": "^2.1.1",
|
|
@@ -7,15 +7,15 @@ export interface ITButtonProps {
|
|
|
7
7
|
label?: string
|
|
8
8
|
/** Custom content to render inside the button. Takes precedence over `label`. */
|
|
9
9
|
children?: React.ReactNode;
|
|
10
|
-
/** Icon element rendered before the label. */
|
|
10
|
+
/** Icon element rendered before the label. With `variant="icon-only"` and no `label`/`children` it becomes the only content and is centered in the square button. */
|
|
11
11
|
icon?: React.ReactNode;
|
|
12
12
|
/** Click handler for the button. */
|
|
13
13
|
onClick?: () => void;
|
|
14
14
|
/** Color theme key. Values come from the semantic color palette (e.g. `"primary"`, `"secondary"`, `"success"`, `"danger"`, `"warning"`, `"info"`, `"purple"`, `"error"`, `"gray"`). @default "primary" */
|
|
15
15
|
color?: ColorsTypes;
|
|
16
|
-
/** Button size. Valid values: `"sm"`, `"md"`, `"lg"`. @default "md" */
|
|
16
|
+
/** Button size. Valid values: `"sm"`, `"md"`, `"lg"`. In `variant="icon-only"` it also scales the square padding (`buttonIconPadding`: `sm` 0.5rem, `md` 0.625rem, `lg` 0.75rem). @default "md" */
|
|
17
17
|
size?: SizesTypes;
|
|
18
|
-
/** Button visual style. Valid values: `"filled"`, `"outlined"`, `"raised"`, `"rounded"`, `"text"`, `"raised-text"`, `"icon-only"`, `"link"`. @default "filled" */
|
|
18
|
+
/** Button visual style. Valid values: `"filled"`, `"outlined"`, `"raised"`, `"rounded"`, `"text"`, `"raised-text"`, `"icon-only"`, `"link"`. `"icon-only"` renders a centered square icon button; combine it with `icon` and `ariaLabel`. @default "filled" */
|
|
19
19
|
variant?: keyof typeof buttonVariants;
|
|
20
20
|
/** Disables the button interaction and applies reduced opacity. @default false */
|
|
21
21
|
disabled?: boolean;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
+
buttonIconPadding,
|
|
3
|
+
buttonIconSizes,
|
|
2
4
|
buttonSizes,
|
|
3
5
|
buttonVariants,
|
|
4
6
|
} from "@/types/button.types";
|
|
@@ -35,6 +37,16 @@ export default function ITButton({
|
|
|
35
37
|
}: ITButtonProps) {
|
|
36
38
|
const [isHovered, setIsHovered] = useState(false);
|
|
37
39
|
const [isFocused, setIsFocused] = useState(false);
|
|
40
|
+
|
|
41
|
+
/** La variante `icon-only` siempre es un cuadrado con padding simétrico. */
|
|
42
|
+
const isIconOnly = variant === "icon-only";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Contenido "solo icono": hay `icon` y no hay `children`.
|
|
46
|
+
* En ese caso NO se envuelve el icono en el wrapper con `gap-1.5` ni en un `ITText` vacío,
|
|
47
|
+
* que lo desplazaban a la izquierda del centro.
|
|
48
|
+
*/
|
|
49
|
+
const isIconOnlyContent = isIconOnly && Boolean(icon) && !children;
|
|
38
50
|
|
|
39
51
|
// Get specific button config from theme
|
|
40
52
|
const themeBtn = theme.button as any;
|
|
@@ -64,8 +76,11 @@ export default function ITButton({
|
|
|
64
76
|
style.borderRadius = "9999px";
|
|
65
77
|
}
|
|
66
78
|
|
|
67
|
-
if (
|
|
68
|
-
|
|
79
|
+
if (isIconOnly) {
|
|
80
|
+
// Padding simétrico explícito: con `aspect-square` define un cuadrado exacto
|
|
81
|
+
// y evita que las utilidades `px-*`/`py-*` de `buttonSizes` desalineen el icono.
|
|
82
|
+
style.padding = buttonIconPadding[size] ?? "0.5rem";
|
|
83
|
+
style.lineHeight = 1;
|
|
69
84
|
}
|
|
70
85
|
|
|
71
86
|
if (isSolid) {
|
|
@@ -103,7 +118,7 @@ export default function ITButton({
|
|
|
103
118
|
// Apply variant base styles (border, shadow, rounded-full)
|
|
104
119
|
buttonVariants[variant || "filled"],
|
|
105
120
|
// Apply size classes (padding/font-size)
|
|
106
|
-
buttonSizes[size],
|
|
121
|
+
isIconOnly ? buttonIconSizes[size] : buttonSizes[size],
|
|
107
122
|
className,
|
|
108
123
|
{ "opacity-50 cursor-not-allowed": disabled }
|
|
109
124
|
)}
|
|
@@ -118,10 +133,15 @@ export default function ITButton({
|
|
|
118
133
|
title={title || ariaLabel || label}
|
|
119
134
|
>
|
|
120
135
|
{children || (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
<
|
|
124
|
-
|
|
136
|
+
isIconOnlyContent ? (
|
|
137
|
+
// Icono solo: sin wrapper con `gap` ni `ITText` vacío, que lo desplazaban a la izquierda.
|
|
138
|
+
<span className="inline-flex items-center justify-center leading-none">{icon}</span>
|
|
139
|
+
) : (
|
|
140
|
+
<span className={clsx("inline-flex items-center gap-1.5 font-semibold", { "opacity-50": disabled })}>
|
|
141
|
+
{icon && <span className="flex-shrink-0">{icon}</span>}
|
|
142
|
+
<ITText as="span">{label}</ITText>
|
|
143
|
+
</span>
|
|
144
|
+
)
|
|
125
145
|
)}
|
|
126
146
|
</button>
|
|
127
147
|
);
|
package/src/sandbox/i18n.ts
CHANGED
|
@@ -772,6 +772,7 @@ export const sandboxMessages: ITMessagesByLocale = {
|
|
|
772
772
|
"showcase.forms.clicksRegistered": "Clicks registrados: {count}",
|
|
773
773
|
"showcase.forms.withIcon": "Con Icono",
|
|
774
774
|
"showcase.forms.galleryWithIcon": "Con Icono",
|
|
775
|
+
"showcase.forms.galleryIconOnly": "Solo Icono (centrado)",
|
|
775
776
|
"showcase.forms.galleryVariants": "Variantes",
|
|
776
777
|
"showcase.forms.gallerySemanticColors": "Colores Semánticos",
|
|
777
778
|
"showcase.forms.sync": "Sincronizar",
|
|
@@ -1714,6 +1715,7 @@ export const sandboxMessages: ITMessagesByLocale = {
|
|
|
1714
1715
|
"showcase.forms.clicksRegistered": "Clicks registered: {count}",
|
|
1715
1716
|
"showcase.forms.withIcon": "With icon",
|
|
1716
1717
|
"showcase.forms.galleryWithIcon": "With icon",
|
|
1718
|
+
"showcase.forms.galleryIconOnly": "Icon only (centered)",
|
|
1717
1719
|
"showcase.forms.galleryVariants": "Variants",
|
|
1718
1720
|
"showcase.forms.gallerySemanticColors": "Semantic colors",
|
|
1719
1721
|
"showcase.forms.sync": "Sync",
|
|
@@ -1309,6 +1309,27 @@ export const BadgetShowcase = () => {
|
|
|
1309
1309
|
<ITBadget label="Large" color="primary" size="lg" />
|
|
1310
1310
|
</div>
|
|
1311
1311
|
}
|
|
1312
|
+
doc={{
|
|
1313
|
+
summary: "Etiqueta compacta de estado o categoría, con colores del tema y variantes filled/outlined.",
|
|
1314
|
+
examples: [
|
|
1315
|
+
'<ITBadget label="Activo" color="success" />',
|
|
1316
|
+
'<ITBadget label="Pendiente" color="warning" variant="outlined" size="sm" />',
|
|
1317
|
+
'<ITBadget color="danger" size="lg">\n <span className="flex items-center gap-1">3 nuevos</span>\n</ITBadget>',
|
|
1318
|
+
],
|
|
1319
|
+
props: [
|
|
1320
|
+
{ name: "label", type: "string", description: "Texto mostrado dentro del badge. Se ignora si se pasa children." },
|
|
1321
|
+
{ name: "children", type: "ReactNode", description: "Contenido personalizado; tiene prioridad sobre label." },
|
|
1322
|
+
{ name: "color", type: "ColorsTypes", default: '"primary"', description: 'Clave de color semántica: "primary", "secondary", "success", "danger", "warning", "info", "purple", "error" o "gray".' },
|
|
1323
|
+
{ name: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "Tamaño del badge (padding y tamaño de fuente)." },
|
|
1324
|
+
{ name: "variant", type: '"filled" | "outlined"', default: '"filled"', description: "Estilo visual: fondo sólido del tema o fondo transparente con borde y texto del color." },
|
|
1325
|
+
{ name: "className", type: "string", description: "Clases CSS adicionales del elemento." },
|
|
1326
|
+
],
|
|
1327
|
+
notes: [
|
|
1328
|
+
"Renderiza un <span> inline-flex centrado; la apariencia sale de theme.badge[color] (radio completo, borde de 1px), no de clases Tailwind. Una clave inexistente en el tema cae en la configuración de primary.",
|
|
1329
|
+
"Para color=\"primary\" el color se resuelve con utilidades (primary-700 en claro, primary-500 en oscuro) para que el contraste pase AA; el resto de colores del tema se respetan tal cual.",
|
|
1330
|
+
"No declara role ni atributos ARIA propios: es un span textual/decorativo, no un control.",
|
|
1331
|
+
],
|
|
1332
|
+
}}
|
|
1312
1333
|
/>
|
|
1313
1334
|
);
|
|
1314
1335
|
};
|
|
@@ -1349,6 +1370,26 @@ export const ImageShowcase = () => {
|
|
|
1349
1370
|
</div>
|
|
1350
1371
|
</>
|
|
1351
1372
|
}
|
|
1373
|
+
doc={{
|
|
1374
|
+
summary: "Imagen con fallback automático: si el src principal falla al cargar, muestra la imagen de respaldo.",
|
|
1375
|
+
description: "El tamaño y la forma se controlan por completo desde className; las imágenes internas ocupan el 100% del contenedor con object-cover.",
|
|
1376
|
+
examples: [
|
|
1377
|
+
'<ITImage src="/foto.jpg" alt="Foto de usuario" />',
|
|
1378
|
+
'<ITImage\n src={user.avatar}\n alt={user.name}\n fallback="/placeholder.png"\n className="h-32 w-32 rounded-full"\n/>',
|
|
1379
|
+
'<ITImage src={logo} alt="Logo" fallback="/logo-fallback.svg" className="h-40 w-full rounded-xl object-cover" />',
|
|
1380
|
+
],
|
|
1381
|
+
props: [
|
|
1382
|
+
{ name: "src", type: "string", required: true, description: "URL de la imagen principal." },
|
|
1383
|
+
{ name: "alt", type: "string", description: "Texto alternativo de la imagen principal (accesibilidad)." },
|
|
1384
|
+
{ name: "fallback", type: "string", default: '""', description: "URL de la imagen mostrada cuando el src principal dispara onError." },
|
|
1385
|
+
{ name: "className", type: "string", default: '""', description: "Clases del contenedor externo; definen el tamaño de la imagen." },
|
|
1386
|
+
],
|
|
1387
|
+
notes: [
|
|
1388
|
+
"El className se aplica al <div> contenedor (flex items-center justify-center) y las <img> internas usan w-full h-full object-cover: sin alto/ancho en className la imagen no toma tamaño propio.",
|
|
1389
|
+
"El fallback se renderiza con alt fijo traducido (image.fallbackAlt, \"Fallback\") en lugar del alt del componente, y el cambio a fallback es permanente: si el src vuelve a estar disponible hay que remontar el componente.",
|
|
1390
|
+
"No reenvía atributos nativos del <img> (loading, width, height, onClick…): onClick está declarado en las props, pero la implementación actual no lo conecta.",
|
|
1391
|
+
],
|
|
1392
|
+
}}
|
|
1352
1393
|
/>
|
|
1353
1394
|
);
|
|
1354
1395
|
};
|
|
@@ -74,6 +74,28 @@ export const DialogShowcase = () => {
|
|
|
74
74
|
</div>
|
|
75
75
|
</>
|
|
76
76
|
}
|
|
77
|
+
doc={{
|
|
78
|
+
summary: "Modal montado en un portal sobre `document.body` para confirmaciones, formularios o detalle, con cierre por Escape o clic fuera.",
|
|
79
|
+
description: "Con `title` dibuja una barra propia o un `ITFormHeader` si `useFormHeader` está activo, y con `fullScreen` ocupa todo el viewport.",
|
|
80
|
+
examples: [
|
|
81
|
+
'<ITDialog isOpen={open} onClose={() => setOpen(false)} title="Confirmación de auditoría"><p>¿Registrar la auditoría?</p></ITDialog>',
|
|
82
|
+
'<ITDialog isOpen={open} onClose={close} title="Nuevo usuario" useFormHeader>…</ITDialog>',
|
|
83
|
+
'<ITDialog isOpen={open} onClose={close} fullScreen><ITDataTable columns={cols} fetchData={api.page} /></ITDialog>',
|
|
84
|
+
],
|
|
85
|
+
props: [
|
|
86
|
+
{ name: "isOpen", type: "boolean", required: true, description: "Controla la visibilidad; con `false` no renderiza nada." },
|
|
87
|
+
{ name: "onClose", type: "() => void", required: true, description: "Se dispara al pulsar Escape, hacer clic fuera del panel o usar el botón de cerrar." },
|
|
88
|
+
{ name: "children", type: "React.ReactNode", required: true, description: "Contenido del cuerpo del diálogo." },
|
|
89
|
+
{ name: "className", type: "string", description: "Clases adicionales del panel; se ignoran en `fullScreen`." },
|
|
90
|
+
{ name: "title", type: "string", description: "Encabezado opcional mostrado en la parte superior." },
|
|
91
|
+
{ name: "useFormHeader", type: "boolean", default: "false", description: "Con `title`, renderiza un `ITFormHeader` en lugar de la barra por defecto." },
|
|
92
|
+
{ name: "fullScreen", type: "boolean", default: "false", description: "Hace que el diálogo ocupe todo el viewport (sin radio, borde ni sombra)." },
|
|
93
|
+
],
|
|
94
|
+
notes: [
|
|
95
|
+
"Los paneles flotantes que abre su contenido (selects, pickers, menús) viven en su propio portal, así que elegir una opción no cierra el diálogo.",
|
|
96
|
+
"No trae `role=\"dialog\"`, `aria-modal`, focus trap ni retorno de foco: esa gestión queda en el consumidor.",
|
|
97
|
+
],
|
|
98
|
+
}}
|
|
77
99
|
/>
|
|
78
100
|
);
|
|
79
101
|
};
|
|
@@ -130,6 +152,24 @@ export const ToastShowcase = () => {
|
|
|
130
152
|
<ITButton label={t("showcase.feedback.toastInfo")} color="info" size="sm" onClick={() => { setMsg(t("showcase.feedback.msgInfo")); setType("info"); setVisible(true); }} />
|
|
131
153
|
</div>
|
|
132
154
|
}
|
|
155
|
+
doc={{
|
|
156
|
+
summary: "Notificación flotante que se autocierra y colorea según su severidad, colocada en una de seis posiciones.",
|
|
157
|
+
examples: [
|
|
158
|
+
'<ITToast message="Cambios guardados" type="success" duration={2000} position="bottom-right" onClose={() => setVisible(false)} />',
|
|
159
|
+
'<ITToast message="No se pudo conectar" type="error" />',
|
|
160
|
+
],
|
|
161
|
+
props: [
|
|
162
|
+
{ name: "message", type: "string", required: true, description: "Texto que muestra el toast." },
|
|
163
|
+
{ name: "type", type: '"success" | "error" | "warning" | "info" | "primary" | "danger" | string', default: '"info"', description: "Severidad; define el icono y el color de fondo." },
|
|
164
|
+
{ name: "duration", type: "number", default: "1500", description: "Milisegundos antes del autocierre." },
|
|
165
|
+
{ name: "position", type: '"top-right" | "top-center" | "top-left" | "bottom-right" | "bottom-center" | "bottom-left"', default: '"top-right"', description: "Posición fija en pantalla." },
|
|
166
|
+
{ name: "onClose", type: "() => void", description: "Se invoca 300 ms después del autocierre o del cierre manual, al terminar la transición." },
|
|
167
|
+
],
|
|
168
|
+
notes: [
|
|
169
|
+
"`type` acepta cualquier string: se resuelve contra `theme.colors`; si la clave no existe, cae al `primary[500]` del tema.",
|
|
170
|
+
"El contenedor lleva `role=\"alert\"`, `fixed z-[80]` y un ancho mínimo de 300 px. Sin `onClose` el toast se desvanece igual, pero el padre no puede desmontarlo.",
|
|
171
|
+
],
|
|
172
|
+
}}
|
|
133
173
|
/>
|
|
134
174
|
);
|
|
135
175
|
};
|
|
@@ -204,6 +244,25 @@ export const LoaderShowcase = () => {
|
|
|
204
244
|
<ITLoader variant="pulse" size="md" color="purple" />
|
|
205
245
|
</div>
|
|
206
246
|
}
|
|
247
|
+
doc={{
|
|
248
|
+
summary: "Indicador de carga con cuatro animaciones (spinner, dots, bar y pulse) y color semántico del tema o valor CSS.",
|
|
249
|
+
examples: [
|
|
250
|
+
'<ITLoader variant="spinner" size="md" color="primary" />',
|
|
251
|
+
'<ITLoader variant="dots" size="sm" color="success" />',
|
|
252
|
+
'<div className="w-48"><ITLoader variant="bar" color="#06b6d4" /></div>',
|
|
253
|
+
],
|
|
254
|
+
props: [
|
|
255
|
+
{ name: "size", type: '"sm" | "md" | "lg" | "xl"', default: '"md"', description: "Tamaño del indicador." },
|
|
256
|
+
{ name: "variant", type: '"spinner" | "dots" | "bar" | "pulse"', default: '"spinner"', description: "Tipo de animación." },
|
|
257
|
+
{ name: "color", type: "string", default: '"primary"', description: "Clave semántica del tema, valor CSS (`#rrggbb`, `rgb(...)`) o clase CSS por compatibilidad." },
|
|
258
|
+
{ name: "className", type: "string", default: '""', description: "Clases adicionales (en `bar` define el ancho)." },
|
|
259
|
+
],
|
|
260
|
+
notes: [
|
|
261
|
+
"Resolución de color: si es clave de `theme.colors` usa el tono 500; si empieza por `#` o `rgb` lo usa tal cual; cualquier otro string se inyecta como clase.",
|
|
262
|
+
"`spinner` lleva `role=\"status\"` con un texto oculto para lectores de pantalla; `bar` ocupa todo el ancho de su contenedor, así que conviene acotarlo con `className`.",
|
|
263
|
+
"En `dots` el tamaño se deriva quitando la primera `l`/`g` a `size`, por lo que solo `sm` y `md` producen clases válidas.",
|
|
264
|
+
],
|
|
265
|
+
}}
|
|
207
266
|
/>
|
|
208
267
|
);
|
|
209
268
|
};
|
|
@@ -285,6 +344,29 @@ export const ThemeProviderShowcase = () => {
|
|
|
285
344
|
</p>
|
|
286
345
|
</ITStack>
|
|
287
346
|
}
|
|
347
|
+
doc={{
|
|
348
|
+
summary: "Provider raíz del tema: inyecta las variables CSS de color, resuelve el modo claro/oscuro y aplica densidad, radio y sombras globales.",
|
|
349
|
+
description: "Envuelve la app (o la parte que usa componentes AXZY) una sola vez. Expone `useITTheme()` con `palette`, `colors`, `setPalette`, `updateColor`, `resetTheme`, `applyPreset`, `resolvedTheme`, `darkModeMode`, `setDarkModeMode` y `appearance`, y opcionalmente un FAB con diseñador de temas en vivo.",
|
|
350
|
+
examples: [
|
|
351
|
+
'<ITThemeProvider><App /></ITThemeProvider>',
|
|
352
|
+
'<ITThemeProvider theme={{ primary: "#3b82f6", layout: { sidebarBg: "#0f172a" } }} showFab={false}><App /></ITThemeProvider>',
|
|
353
|
+
'<ITThemeProvider density={0.9} radius={8} shadow={1} appearance="flat"><App /></ITThemeProvider>',
|
|
354
|
+
],
|
|
355
|
+
props: [
|
|
356
|
+
{ name: "theme", type: "Partial<ITThemePalette>", description: "Overrides de paleta fusionados (deep merge) con el tema por defecto: `primary`, `secondary`, `ternary`, `danger`, `success`, `info`, `alert`, `warning`, `layout` y `table`." },
|
|
357
|
+
{ name: "children", type: "React.ReactNode", required: true, description: "Subárbol que recibe el contexto y las variables CSS." },
|
|
358
|
+
{ name: "showFab", type: "boolean", default: "true", description: "Muestra el botón flotante que abre el diseñador de temas y persiste la paleta; en producción se suele poner en `false`." },
|
|
359
|
+
{ name: "density", type: "number", default: "1", description: "Factor de densidad global: escala el `font-size` raíz para compactar todo lo basado en `rem`. Se limita a `0.5`–`1.5`." },
|
|
360
|
+
{ name: "radius", type: "number", default: "—", description: "Radio global en px aplicado a toda la escala `--radius-*`; `0` deja las esquinas totalmente cuadradas." },
|
|
361
|
+
{ name: "shadow", type: "number", default: "—", description: "Fuerza global de sombras: `0` ninguna, `1` sutiles, `2` look actual, `3` marcadas." },
|
|
362
|
+
{ name: "appearance", type: '"soft" | "flat"', default: '"soft"', description: "Lenguaje visual del sistema; `\"flat\"` usa superficies planas con bordes de 1 px y fija `data-it-appearance` en `<html>`." },
|
|
363
|
+
],
|
|
364
|
+
notes: [
|
|
365
|
+
"`useITTheme()` lanza error si se usa fuera del provider; `useITThemeSafe()` devuelve `undefined` en ese caso.",
|
|
366
|
+
"Con `showFab` la paleta, el modo y los presets se persisten en `localStorage` (`it-theme-palette`, `it-theme-dark-mode`, `it-theme-custom-presets`); con `showFab={false}` el estado se sincroniza con la prop `theme`.",
|
|
367
|
+
"`radius={0}` no significa «sin override»: deja todas las esquinas cuadradas. Para no tocar la escala hay que omitir la prop.",
|
|
368
|
+
],
|
|
369
|
+
}}
|
|
288
370
|
/>
|
|
289
371
|
);
|
|
290
372
|
};
|