@axzydev/axzy_ui_system 1.5.2 → 1.6.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 +54 -18
- 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 +59 -23
- package/dist/index.js.map +1 -1
- package/dist/layered.css +1 -1
- package/package.json +1 -1
- package/src/components/atoms/button/button.props.ts +3 -3
- package/src/components/atoms/button/button.tsx +27 -7
- package/src/components/organisms/sidebar/sidebar.tsx +51 -14
- package/src/sandbox/i18n.ts +2 -0
- package/src/showcases/FormShowcases.tsx +9 -0
- package/src/showcases/StructureShowcases.tsx +3 -2
- package/src/types/button.types.ts +19 -1
package/package.json
CHANGED
|
@@ -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
|
);
|
|
@@ -29,6 +29,28 @@ function flattenSubItems(
|
|
|
29
29
|
);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/** Caracteres que caben en el badge del riel (`max-w-[2.1rem]`). */
|
|
33
|
+
const RAIL_BADGE_MAX_CHARS = 3;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Contenido del badge cuando el sidebar está colapsado.
|
|
37
|
+
*
|
|
38
|
+
* En el riel solo cabe un valor corto (contadores tipo `12` o `99+`). Un texto
|
|
39
|
+
* largo deformaría el riel y taparía el icono, así que se degrada a un punto que
|
|
40
|
+
* conserva el aviso de color; el nombre completo sigue disponible en el tooltip.
|
|
41
|
+
*/
|
|
42
|
+
function RailBadge({ value }: { value: number | string }) {
|
|
43
|
+
const text = String(value);
|
|
44
|
+
if (text.length <= RAIL_BADGE_MAX_CHARS) return <>{text}</>;
|
|
45
|
+
return (
|
|
46
|
+
<span
|
|
47
|
+
aria-hidden="true"
|
|
48
|
+
className="block w-1.5 h-1.5 rounded-full"
|
|
49
|
+
style={{ backgroundColor: "currentColor" }}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
32
54
|
/**
|
|
33
55
|
* Vertical navigation sidebar with submenu expand/collapse, hover tooltips in collapsed mode,
|
|
34
56
|
* and glassmorphism styling. The collapsed rail expands automatically on pointer hover — there
|
|
@@ -137,9 +159,9 @@ export default function ITSidebar({
|
|
|
137
159
|
}, [navigationItems]);
|
|
138
160
|
|
|
139
161
|
const isSidebarCollapsed = visibleOnMobile ? false : isCollapsed && !(expandOnHover && isHovering);
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
162
|
+
const collapsedWidth = flat ? "w-[72px]" : "w-[88px]";
|
|
163
|
+
const expandedWidth = flat ? "w-[248px]" : "w-[280px]";
|
|
164
|
+
const sidebarWidth = isSidebarCollapsed ? collapsedWidth : expandedWidth;
|
|
143
165
|
// Icon-only rail that never expands on hover: names show in a tooltip.
|
|
144
166
|
const fixedRail = isSidebarCollapsed && !expandOnHover;
|
|
145
167
|
|
|
@@ -266,7 +288,7 @@ export default function ITSidebar({
|
|
|
266
288
|
{...badgeRest}
|
|
267
289
|
className={clsx("shadow-md", badgeClassName)}
|
|
268
290
|
>
|
|
269
|
-
{String(count)}
|
|
291
|
+
{isSidebarCollapsed ? <RailBadge value={count} /> : String(count)}
|
|
270
292
|
</ITBadget>
|
|
271
293
|
);
|
|
272
294
|
|
|
@@ -277,7 +299,7 @@ export default function ITSidebar({
|
|
|
277
299
|
aria-label={label}
|
|
278
300
|
title={label}
|
|
279
301
|
onClick={onClick}
|
|
280
|
-
className=
|
|
302
|
+
className={`relative flex items-center w-full h-10 mb-1 rounded-xl transition-all duration-300 ${flat ? "px-3" : "px-5"}`}
|
|
281
303
|
style={{ color: "var(--it-sidebar-icon-color, var(--color-secondary-500))" }}
|
|
282
304
|
onMouseEnter={(e) => {
|
|
283
305
|
e.currentTarget.style.backgroundColor = "var(--it-sidebar-hover-bg, var(--color-secondary-100))";
|
|
@@ -289,7 +311,7 @@ export default function ITSidebar({
|
|
|
289
311
|
{icon && (
|
|
290
312
|
<span className="flex items-center justify-center text-[1.05rem]">{icon}</span>
|
|
291
313
|
)}
|
|
292
|
-
<span className="absolute top-0.5 right-0.5 pointer-events-none">{badge}</span>
|
|
314
|
+
<span className="absolute top-0.5 right-0.5 max-w-[2.1rem] overflow-hidden pointer-events-none">{badge}</span>
|
|
293
315
|
</button>
|
|
294
316
|
);
|
|
295
317
|
}
|
|
@@ -300,7 +322,7 @@ export default function ITSidebar({
|
|
|
300
322
|
aria-label={label}
|
|
301
323
|
title={label}
|
|
302
324
|
onClick={onClick}
|
|
303
|
-
className=
|
|
325
|
+
className={`flex items-center gap-2.5 w-full text-left h-10 mb-1 rounded-xl transition-all duration-300 ${flat ? "px-3" : "px-5"}`}
|
|
304
326
|
style={{ color: "var(--it-sidebar-label-color, var(--color-secondary-600))" }}
|
|
305
327
|
onMouseEnter={(e) => {
|
|
306
328
|
e.currentTarget.style.backgroundColor = "var(--it-sidebar-hover-bg, var(--color-secondary-100))";
|
|
@@ -364,8 +386,19 @@ export default function ITSidebar({
|
|
|
364
386
|
</div>
|
|
365
387
|
)}
|
|
366
388
|
|
|
367
|
-
{header
|
|
368
|
-
|
|
389
|
+
{/* El header reserva su altura también en el riel para que la navegación no
|
|
390
|
+
salte en vertical al expandir. Se maqueta con el ancho expandido (oculto
|
|
391
|
+
y recortado) para que el texto no se reparta en más líneas en el riel. */}
|
|
392
|
+
{header && (
|
|
393
|
+
<div className={`px-4 pt-4 pb-1 flex-shrink-0 ${isSidebarCollapsed ? "overflow-hidden" : ""}`}>
|
|
394
|
+
{isSidebarCollapsed ? (
|
|
395
|
+
<div className={`${expandedWidth} invisible`} aria-hidden="true">
|
|
396
|
+
{header}
|
|
397
|
+
</div>
|
|
398
|
+
) : (
|
|
399
|
+
header
|
|
400
|
+
)}
|
|
401
|
+
</div>
|
|
369
402
|
)}
|
|
370
403
|
|
|
371
404
|
{notification && (
|
|
@@ -373,7 +406,7 @@ export default function ITSidebar({
|
|
|
373
406
|
)}
|
|
374
407
|
|
|
375
408
|
{/* Navigation Items */}
|
|
376
|
-
<nav aria-label={t("sidebar.navAria")} className={`flex-1 py-4 custom-scrollbar ${fixedRail ? "overflow-visible" : "overflow-y-auto overflow-x-hidden"}
|
|
409
|
+
<nav aria-label={t("sidebar.navAria")} className={`flex-1 py-4 px-4 custom-scrollbar ${fixedRail ? "overflow-visible" : "overflow-y-auto overflow-x-hidden"}`}>
|
|
377
410
|
<ul className="space-y-1">
|
|
378
411
|
{navigationItems.map((item) => {
|
|
379
412
|
const hasSubmenu = !!item.subitems && item.subitems.length > 0;
|
|
@@ -396,7 +429,9 @@ export default function ITSidebar({
|
|
|
396
429
|
className={`flex items-center cursor-pointer
|
|
397
430
|
transition-all duration-300 ease-[cubic-bezier(0.2,0,0,1)]
|
|
398
431
|
rounded-xl relative overflow-visible
|
|
399
|
-
|
|
432
|
+
h-10
|
|
433
|
+
${flat ? "px-3" : "px-5"}
|
|
434
|
+
${isSidebarCollapsed ? "justify-start" : "justify-between"}
|
|
400
435
|
`}
|
|
401
436
|
style={{
|
|
402
437
|
backgroundColor: active ? "var(--it-sidebar-active-bg, var(--color-secondary-50))" : 'transparent',
|
|
@@ -424,7 +459,7 @@ export default function ITSidebar({
|
|
|
424
459
|
/>
|
|
425
460
|
)}
|
|
426
461
|
|
|
427
|
-
<div className=
|
|
462
|
+
<div className="flex items-center gap-2.5 relative z-10 w-full">
|
|
428
463
|
{item.icon && (
|
|
429
464
|
<div
|
|
430
465
|
className={`transition-all duration-300 flex-shrink-0 flex items-center justify-center`}
|
|
@@ -467,13 +502,15 @@ export default function ITSidebar({
|
|
|
467
502
|
{...badgeRest}
|
|
468
503
|
className={clsx(
|
|
469
504
|
"absolute shadow-md",
|
|
505
|
+
// En el riel no hay ancho para una etiqueta larga: el badge se
|
|
506
|
+
// ancla a la esquina y se recorta, así nunca desborda ni tapa el icono.
|
|
470
507
|
isSidebarCollapsed
|
|
471
|
-
? "top-0.5 right-0.5"
|
|
508
|
+
? "top-0.5 right-0.5 max-w-[2.1rem] overflow-hidden"
|
|
472
509
|
: "right-3 top-1/2 -translate-y-1/2",
|
|
473
510
|
badgeClassName,
|
|
474
511
|
)}
|
|
475
512
|
>
|
|
476
|
-
{String(item.badge)}
|
|
513
|
+
{isSidebarCollapsed ? <RailBadge value={item.badge} /> : String(item.badge)}
|
|
477
514
|
</ITBadget>
|
|
478
515
|
)}
|
|
479
516
|
</div>
|
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",
|
|
@@ -158,6 +158,14 @@ export const ButtonShowcase = () => {
|
|
|
158
158
|
<ITButton label={t("showcase.forms.sync")} icon={<FaSync />} variant="text" />
|
|
159
159
|
</div>
|
|
160
160
|
</div>
|
|
161
|
+
<div>
|
|
162
|
+
<h3 className="mb-3 w-fit border-l-2 border-primary-700 pl-2.5 font-mono text-[11px] font-medium uppercase tracking-[0.14em] text-slate-700 dark:border-primary-500 dark:text-slate-200">{t("showcase.forms.galleryIconOnly")}</h3>
|
|
163
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
164
|
+
<ITButton variant="icon-only" size="sm" color="primary" icon={<FaEdit />} ariaLabel={t("showcase.common.edit")} />
|
|
165
|
+
<ITButton variant="icon-only" size="md" color="success" icon={<FaSave />} ariaLabel={t("showcase.common.save")} />
|
|
166
|
+
<ITButton variant="icon-only" size="lg" color="danger" icon={<FaTrash />} ariaLabel={t("showcase.common.delete")} />
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
161
169
|
<div>
|
|
162
170
|
<h3 className="mb-3 w-fit border-l-2 border-primary-700 pl-2.5 font-mono text-[11px] font-medium uppercase tracking-[0.14em] text-slate-700 dark:border-primary-500 dark:text-slate-200">{t("showcase.forms.galleryVariants")}</h3>
|
|
163
171
|
<div className="flex flex-wrap gap-3">
|
|
@@ -210,6 +218,7 @@ export const ButtonShowcase = () => {
|
|
|
210
218
|
notes: [
|
|
211
219
|
"Con children no se renderizan label ni icon.",
|
|
212
220
|
'Usa variant="icon-only" junto a ariaLabel cuando el botón no tenga texto visible.',
|
|
221
|
+
'Con variant="icon-only" + icon (sin label) el botón es cuadrado y el icono queda centrado; el tamaño del cuadrado sale de buttonIconPadding (sm 0.5rem, md 0.625rem, lg 0.75rem).',
|
|
213
222
|
],
|
|
214
223
|
}}
|
|
215
224
|
/>
|
|
@@ -1304,7 +1304,7 @@ export const LayoutShowcase = () => {
|
|
|
1304
1304
|
{ name: "children", type: "React.ReactNode", required: true, description: "Contenido principal." },
|
|
1305
1305
|
{ name: "className", type: "string", description: "Clases del wrapper externo." },
|
|
1306
1306
|
{ name: "contentClassName", type: "string", description: "Clases del contenedor de contenido." },
|
|
1307
|
-
{ name: "sidebar.header", type: "React.ReactNode", description: "Slot opcional sobre la navegación (ej. wordmark o filtro). Oculto al colapsar." },
|
|
1307
|
+
{ name: "sidebar.header", type: "React.ReactNode", description: "Slot opcional sobre la navegación (ej. wordmark o filtro). Oculto al colapsar, pero reserva su altura." },
|
|
1308
1308
|
{ name: "sidebar.notification", type: "ITSidebarNotification", description: "Fila de notificación (icono + label + ITBadget) sobre la navegación: { count, icon?, label?, onClick?, badgeProps? }. Visible también al colapsar." },
|
|
1309
1309
|
{ name: "sidebar.navigationItems[].badge", type: "number | string", description: "Contador que se renderiza con ITBadget en el ítem." },
|
|
1310
1310
|
{ name: "sidebar.navigationItems[].badgeProps", type: "ITBadgetProps", description: "Estilo del badge del ítem (color, size, variant, className para padding)." },
|
|
@@ -1312,9 +1312,10 @@ export const LayoutShowcase = () => {
|
|
|
1312
1312
|
notes: [
|
|
1313
1313
|
"`subitems` acepta entradas planas o grupos con `label`+`items`; el título del grupo no es foco ni un tercer nivel y cada grupo se nombra por `aria-labelledby`. En el riel colapsado el flyout aplana los grupos (sin títulos).",
|
|
1314
1314
|
"El sidebar hereda aria-current/aria-expanded y acepta un slot header opcional.",
|
|
1315
|
-
"Los badges por ítem (`badge` + `badgeProps`) y la fila `notification` se dibujan con el átomo `ITBadget`; el padding se ajusta con `badgeProps.className`.",
|
|
1315
|
+
"Los badges por ítem (`badge` + `badgeProps`) y la fila `notification` se dibujan con el átomo `ITBadget`; el padding se ajusta con `badgeProps.className`. `badge` es un contador: en el riel colapsado solo caben ≤3 caracteres y un texto largo se degrada a un punto.",
|
|
1316
1316
|
"Con `sidebar.isCollapsed` omitido el contenido reserva 88px (hover expande sobre el contenido). Si lo controlas a `false`, reserva 280px y no tapa el contenido.",
|
|
1317
1317
|
"El riel colapsado se expande automáticamente al pasar el cursor por encima; no hay botón de colapso.",
|
|
1318
|
+
"Riel y panel comparten columna de iconos y alto de fila: los iconos no se mueven al expandir/colapsar. El slot `header` reserva su altura (invisible) en el riel por el mismo motivo.",
|
|
1318
1319
|
],
|
|
1319
1320
|
}}
|
|
1320
1321
|
/>
|
|
@@ -5,7 +5,8 @@ export const buttonVariants: Record<string, string> = {
|
|
|
5
5
|
rounded: "border-transparent shadow-sm rounded-full",
|
|
6
6
|
text: "bg-transparent border-transparent shadow-none hover:bg-black/5 dark:hover:bg-white/10",
|
|
7
7
|
"raised-text": "bg-white border border-gray-200 shadow-sm hover:shadow-md",
|
|
8
|
-
|
|
8
|
+
// No lleva utilidades de padding: el cuadrado lo define `buttonIconPadding` por tamaño.
|
|
9
|
+
"icon-only": "aspect-square flex items-center justify-center border-transparent shadow-sm",
|
|
9
10
|
link: "bg-transparent border-transparent shadow-none hover:underline px-0",
|
|
10
11
|
};
|
|
11
12
|
|
|
@@ -14,3 +15,20 @@ export const buttonSizes: Record<string, string> = {
|
|
|
14
15
|
md: "text-[13px] px-3.5 py-1.5",
|
|
15
16
|
lg: "text-sm px-4 py-2",
|
|
16
17
|
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Tipografía de `icon-only`: solo el tamaño de fuente, sin padding horizontal/vertical,
|
|
21
|
+
* para que el padding simétrico de `buttonIconPadding` no compita con `buttonSizes`.
|
|
22
|
+
*/
|
|
23
|
+
export const buttonIconSizes: Record<string, string> = {
|
|
24
|
+
sm: "text-xs",
|
|
25
|
+
md: "text-[13px]",
|
|
26
|
+
lg: "text-sm",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Padding simétrico (px) del botón `icon-only` por tamaño; define el cuadrado con `aspect-square`. */
|
|
30
|
+
export const buttonIconPadding: Record<string, string> = {
|
|
31
|
+
sm: "0.5rem",
|
|
32
|
+
md: "0.625rem",
|
|
33
|
+
lg: "0.75rem",
|
|
34
|
+
};
|