@axzydev/axzy_ui_system 1.4.8 → 1.5.1
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 +2 -11
- package/dist/index.cjs +1826 -1351
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +115 -14
- package/dist/index.d.ts +115 -14
- package/dist/index.js +1481 -1010
- package/dist/index.js.map +1 -1
- package/dist/layered.css +1 -1
- package/package.json +3 -16
- package/src/App.tsx +90 -73
- package/src/components/atoms/badget/badget.tsx +23 -5
- package/src/components/atoms/chip/chip.tsx +3 -1
- package/src/components/atoms/image/image.tsx +3 -1
- package/src/components/atoms/input/input.tsx +8 -5
- package/src/components/atoms/loader/loader.tsx +3 -1
- package/src/components/atoms/slide/slide.props.ts +12 -0
- package/src/components/atoms/slide/slide.tsx +3 -0
- package/src/components/molecules/alert/alert.tsx +3 -1
- package/src/components/molecules/breadcrumbs/breadcrumbs.tsx +3 -1
- package/src/components/molecules/calendar/calendar.tsx +9 -6
- package/src/components/molecules/card/card.tsx +5 -2
- package/src/components/molecules/chip-input/chip-input.tsx +9 -7
- package/src/components/molecules/date-picker/datePicker.tsx +9 -9
- package/src/components/molecules/dropdown-menu/dropdown-menu.props.ts +1 -1
- package/src/components/molecules/dropdown-menu/dropdown-menu.tsx +6 -3
- package/src/components/molecules/field/field.tsx +3 -1
- package/src/components/molecules/form-header/form-header.tsx +3 -1
- package/src/components/molecules/line-bar-chart/line-bar-chart.props.ts +2 -2
- package/src/components/molecules/line-bar-chart/line-bar-chart.tsx +20 -15
- package/src/components/molecules/masked-input/masked-input.tsx +3 -1
- package/src/components/molecules/multi-select/multi-select.tsx +12 -9
- package/src/components/molecules/pagination/pagination.tsx +11 -5
- package/src/components/molecules/search-select/search-select.tsx +11 -9
- package/src/components/molecules/select/select.tsx +6 -4
- package/src/components/molecules/stepper/stepper.tsx +6 -4
- package/src/components/molecules/table/table.tsx +30 -28
- package/src/components/molecules/time-picker/timePicker.tsx +8 -6
- package/src/components/molecules/wysiwyg/wysiwyg.tsx +23 -12
- package/src/components/organisms/confirm-dialog/confirm-dialog.props.ts +4 -4
- package/src/components/organisms/confirm-dialog/confirm-dialog.tsx +16 -8
- package/src/components/organisms/data-table/dataTable.tsx +31 -25
- package/src/components/organisms/dialog/dialog.tsx +3 -0
- package/src/components/organisms/drawer/drawer.tsx +3 -0
- package/src/components/organisms/dropfile/dropfile.tsx +19 -17
- package/src/components/organisms/form-builder/fieldRenderer.tsx +4 -2
- package/src/components/organisms/navbar/navbar.tsx +4 -2
- package/src/components/organisms/page-header/page-header.tsx +3 -1
- package/src/components/organisms/searchTable/components/PaginationControls.tsx +8 -6
- package/src/components/organisms/searchTable/components/PaginationInfo.tsx +3 -1
- package/src/components/organisms/searchTable/components/SearchAndSortBar.tsx +7 -5
- package/src/components/organisms/searchTable/components/SearchInput.tsx +4 -2
- package/src/components/organisms/searchTable/components/SortButton.tsx +13 -11
- package/src/components/organisms/searchTable/components/TableEmptyState.tsx +4 -2
- package/src/components/organisms/searchTable/components/TableRow.tsx +6 -4
- package/src/components/organisms/searchTable/searchTable.tsx +4 -2
- package/src/components/organisms/sidebar/sidebar.props.ts +1 -1
- package/src/components/organisms/sidebar/sidebar.tsx +9 -4
- package/src/components/organisms/toast/toast.tsx +3 -1
- package/src/components/organisms/topbar/topbar.tsx +4 -2
- package/src/components/templates/page/page.props.ts +4 -4
- package/src/components/templates/page/page.tsx +6 -4
- package/src/components/theme-provider/themeProvider.tsx +28 -17
- package/src/dev.css +61 -2
- package/src/global.d.ts +10 -0
- package/src/i18n/context.ts +9 -0
- package/src/i18n/index.ts +22 -0
- package/src/i18n/locales/en.ts +163 -0
- package/src/i18n/locales/es.ts +165 -0
- package/src/i18n/provider.tsx +44 -0
- package/src/i18n/translator.ts +82 -0
- package/src/i18n/types.ts +54 -0
- package/src/i18n/useITTranslation.ts +20 -0
- package/src/index.css +0 -1
- package/src/index.ts +7 -1
- package/src/sandbox/LandingShowcase.tsx +224 -580
- package/src/sandbox/NotFoundShowcase.tsx +51 -36
- package/src/sandbox/TopbarSearch.tsx +6 -4
- package/src/sandbox/controls.tsx +180 -0
- package/src/sandbox/i18n.ts +1939 -0
- package/src/sandbox/icons.tsx +25 -0
- package/src/sandbox/locale.tsx +72 -0
- package/src/sandbox/ui.tsx +474 -0
- package/src/sandbox/useHashRoute.ts +46 -10
- package/src/showcases/DataShowcases.tsx +103 -87
- package/src/showcases/FeedbackShowcases.tsx +37 -30
- package/src/showcases/FormShowcases.tsx +303 -215
- package/src/showcases/GettingStartedShowcase.tsx +151 -145
- package/src/showcases/HelpShowcase.tsx +126 -128
- package/src/showcases/HomeShowcase.tsx +678 -482
- package/src/showcases/LayoutPrimitivesShowcases.tsx +129 -114
- package/src/showcases/NavigationShowcases.tsx +45 -37
- package/src/showcases/PageShowcases.tsx +171 -152
- package/src/showcases/ProductoDetalle.tsx +272 -129
- package/src/showcases/ShowcaseLayout.tsx +256 -219
- package/src/showcases/SizesShowcase.tsx +123 -64
- package/src/showcases/StructureShowcases.tsx +203 -138
- package/src/showcases/portfolioUi.tsx +153 -0
- package/src/showcases/techIcons.ts +3 -0
- package/src/utils/table.utils.ts +10 -2
- package/src/components/atoms/avatar/avatar.doc.mdx +0 -48
- package/src/components/atoms/avatar/avatar.stories.tsx +0 -64
- package/src/components/atoms/badget/badget.doc.mdx +0 -46
- package/src/components/atoms/badget/badget.stories.tsx +0 -75
- package/src/components/atoms/button/button.doc.mdx +0 -59
- package/src/components/atoms/button/button.stories.tsx +0 -173
- package/src/components/atoms/checkbox/checkbox.doc.mdx +0 -54
- package/src/components/atoms/checkbox/checkbox.stories.tsx +0 -53
- package/src/components/atoms/chip/chip.doc.mdx +0 -47
- package/src/components/atoms/chip/chip.stories.tsx +0 -64
- package/src/components/atoms/divider/divider.doc.mdx +0 -42
- package/src/components/atoms/divider/divider.stories.tsx +0 -33
- package/src/components/atoms/flex/flex.doc.mdx +0 -59
- package/src/components/atoms/flex/flex.stories.tsx +0 -70
- package/src/components/atoms/grid/grid.doc.mdx +0 -54
- package/src/components/atoms/grid/grid.stories.tsx +0 -71
- package/src/components/atoms/image/image.doc.mdx +0 -44
- package/src/components/atoms/image/image.stories.tsx +0 -48
- package/src/components/atoms/input/input.doc.mdx +0 -70
- package/src/components/atoms/input/input.stories.tsx +0 -113
- package/src/components/atoms/input-number/input-number.doc.mdx +0 -74
- package/src/components/atoms/input-number/input-number.stories.tsx +0 -111
- package/src/components/atoms/loader/loader.doc.mdx +0 -43
- package/src/components/atoms/loader/loader.stories.tsx +0 -104
- package/src/components/atoms/popover/popover.doc.mdx +0 -53
- package/src/components/atoms/popover/popover.stories.tsx +0 -24
- package/src/components/atoms/progress/progress.doc.mdx +0 -48
- package/src/components/atoms/progress/progress.stories.tsx +0 -39
- package/src/components/atoms/radio/radio.doc.mdx +0 -62
- package/src/components/atoms/radio/radio.stories.tsx +0 -48
- package/src/components/atoms/segmented-control/segmented-control.doc.mdx +0 -60
- package/src/components/atoms/segmented-control/segmented-control.stories.tsx +0 -62
- package/src/components/atoms/skeleton/skeleton.doc.mdx +0 -43
- package/src/components/atoms/skeleton/skeleton.stories.tsx +0 -29
- package/src/components/atoms/slide/slide.doc.mdx +0 -46
- package/src/components/atoms/slide/slide.stories.tsx +0 -120
- package/src/components/atoms/slider/slider.doc.mdx +0 -49
- package/src/components/atoms/slider/slider.stories.tsx +0 -29
- package/src/components/atoms/stack/stack.doc.mdx +0 -126
- package/src/components/atoms/stack/stack.stories.tsx +0 -283
- package/src/components/atoms/text/text.doc.mdx +0 -41
- package/src/components/atoms/text/text.stories.tsx +0 -66
- package/src/components/atoms/textarea/textarea.doc.mdx +0 -64
- package/src/components/atoms/textarea/textarea.stories.tsx +0 -26
- package/src/components/atoms/tooltip/tooltip.doc.mdx +0 -47
- package/src/components/atoms/tooltip/tooltip.stories.tsx +0 -19
- package/src/components/isolation/isolation.stories.tsx +0 -202
- package/src/components/molecules/accordion/accordion.doc.mdx +0 -47
- package/src/components/molecules/accordion/accordion.stories.tsx +0 -68
- package/src/components/molecules/alert/alert.doc.mdx +0 -48
- package/src/components/molecules/alert/alert.stories.tsx +0 -40
- package/src/components/molecules/breadcrumbs/breadcrumbs.doc.mdx +0 -57
- package/src/components/molecules/breadcrumbs/breadcrumbs.stories.tsx +0 -20
- package/src/components/molecules/calendar/calendar.doc.mdx +0 -76
- package/src/components/molecules/calendar/calendar.stories.tsx +0 -90
- package/src/components/molecules/card/card.doc.mdx +0 -54
- package/src/components/molecules/card/card.stories.tsx +0 -57
- package/src/components/molecules/chip-input/chip-input.doc.mdx +0 -44
- package/src/components/molecules/chip-input/chip-input.stories.tsx +0 -52
- package/src/components/molecules/date-picker/date-picker.doc.mdx +0 -65
- package/src/components/molecules/date-picker/date-picker.stories.tsx +0 -88
- package/src/components/molecules/dropdown-menu/dropdown-menu.doc.mdx +0 -46
- package/src/components/molecules/dropdown-menu/dropdown-menu.stories.tsx +0 -41
- package/src/components/molecules/empty-state/empty-state.doc.mdx +0 -50
- package/src/components/molecules/empty-state/empty-state.stories.tsx +0 -19
- package/src/components/molecules/field/field.doc.mdx +0 -43
- package/src/components/molecules/field/field.stories.tsx +0 -42
- package/src/components/molecules/form-header/form-header.doc.mdx +0 -38
- package/src/components/molecules/form-header/form-header.stories.tsx +0 -52
- package/src/components/molecules/masked-input/masked-input.doc.mdx +0 -78
- package/src/components/molecules/masked-input/masked-input.stories.tsx +0 -126
- package/src/components/molecules/multi-select/multi-select.doc.mdx +0 -55
- package/src/components/molecules/multi-select/multi-select.stories.tsx +0 -59
- package/src/components/molecules/pagination/pagination.doc.mdx +0 -62
- package/src/components/molecules/pagination/pagination.stories.tsx +0 -110
- package/src/components/molecules/search-select/search-select.doc.mdx +0 -77
- package/src/components/molecules/search-select/search-select.stories.tsx +0 -146
- package/src/components/molecules/select/select.doc.mdx +0 -75
- package/src/components/molecules/select/select.stories.tsx +0 -85
- package/src/components/molecules/stat-card/stat-card.doc.mdx +0 -48
- package/src/components/molecules/stat-card/stat-card.stories.tsx +0 -40
- package/src/components/molecules/stepper/stepper.doc.mdx +0 -54
- package/src/components/molecules/stepper/stepper.stories.tsx +0 -154
- package/src/components/molecules/table/table.doc.mdx +0 -140
- package/src/components/molecules/table/table.stories.tsx +0 -402
- package/src/components/molecules/tabs/tabs.doc.mdx +0 -52
- package/src/components/molecules/tabs/tabs.stories.tsx +0 -31
- package/src/components/molecules/time-picker/timePicker.doc.mdx +0 -68
- package/src/components/molecules/time-picker/timePicker.stories.tsx +0 -130
- package/src/components/molecules/wysiwyg/wysiwyg.stories.tsx +0 -95
- package/src/components/organisms/confirm-dialog/confirm-dialog.doc.mdx +0 -60
- package/src/components/organisms/confirm-dialog/confirm-dialog.stories.tsx +0 -32
- package/src/components/organisms/data-table/ITDataTable.doc.mdx +0 -132
- package/src/components/organisms/data-table/ITDataTable.stories.tsx +0 -353
- package/src/components/organisms/dialog/dialog.doc.mdx +0 -54
- package/src/components/organisms/dialog/dialog.stories.tsx +0 -126
- package/src/components/organisms/drawer/drawer.doc.mdx +0 -59
- package/src/components/organisms/drawer/drawer.stories.tsx +0 -40
- package/src/components/organisms/dropfile/dropfile.doc.mdx +0 -74
- package/src/components/organisms/dropfile/dropfile.stories.tsx +0 -93
- package/src/components/organisms/form-builder/formBuilder.doc.mdx +0 -251
- package/src/components/organisms/form-builder/formBuilder.stories.tsx +0 -914
- package/src/components/organisms/navbar/navbar.doc.mdx +0 -77
- package/src/components/organisms/navbar/navbar.stories.tsx +0 -83
- package/src/components/organisms/page-header/page-header.doc.mdx +0 -54
- package/src/components/organisms/page-header/page-header.stories.tsx +0 -74
- package/src/components/organisms/searchTable/searchTable.doc.mdx +0 -79
- package/src/components/organisms/searchTable/searchTable.stories.tsx +0 -73
- package/src/components/organisms/sidebar/sidebar.doc.mdx +0 -176
- package/src/components/organisms/sidebar/sidebar.stories.tsx +0 -234
- package/src/components/organisms/toast/toast.doc.mdx +0 -50
- package/src/components/organisms/toast/toast.stories.tsx +0 -137
- package/src/components/organisms/topbar/topbar.doc.mdx +0 -72
- package/src/components/organisms/topbar/topbar.stories.tsx +0 -79
- package/src/components/organisms/triple-filter/tripleFilter.doc.mdx +0 -62
- package/src/components/organisms/triple-filter/tripleFilter.stories.tsx +0 -31
- package/src/components/templates/layout/layout.doc.mdx +0 -53
- package/src/components/templates/layout/layout.stories.tsx +0 -113
- package/src/components/templates/page/page.doc.mdx +0 -86
- package/src/components/templates/page/page.stories.tsx +0 -97
- package/src/components/theme-provider/themeProvider.doc.mdx +0 -85
- package/src/components/theme-provider/themeProvider.stories.tsx +0 -58
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Íconos de las familias del catálogo, compartidos por la barra lateral
|
|
3
|
+
* (`App.tsx`) y la landing (`LandingShowcase`) para que la misma familia se vea
|
|
4
|
+
* igual en los dos sitios.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ReactNode } from "react";
|
|
8
|
+
import {
|
|
9
|
+
FaBell,
|
|
10
|
+
FaCreditCard,
|
|
11
|
+
FaHome,
|
|
12
|
+
FaKeyboard,
|
|
13
|
+
FaSlidersH,
|
|
14
|
+
FaTable,
|
|
15
|
+
} from "react-icons/fa";
|
|
16
|
+
|
|
17
|
+
/** Ícono por id de grupo (`navigation.ts`). */
|
|
18
|
+
export const GROUP_ICONS: Record<string, ReactNode> = {
|
|
19
|
+
general: <FaHome />,
|
|
20
|
+
struc: <FaCreditCard />,
|
|
21
|
+
forms: <FaKeyboard />,
|
|
22
|
+
data: <FaTable />,
|
|
23
|
+
nav: <FaSlidersH />,
|
|
24
|
+
feed: <FaBell />,
|
|
25
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idioma del sandbox, compartido entre el chrome (ITTopBar) y las páginas del
|
|
3
|
+
* portafolio.
|
|
4
|
+
*
|
|
5
|
+
* La preferencia vivía solo en `App`, así que el portafolio no podía cambiarla:
|
|
6
|
+
* ahora se expone por contexto para que el selector del header del portafolio y
|
|
7
|
+
* el de la barra del sandbox muevan el mismo estado (y persistan en
|
|
8
|
+
* `localStorage`).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
|
|
12
|
+
import type { ITLocale } from "@/i18n";
|
|
13
|
+
|
|
14
|
+
/** Valor del contexto de idioma del sandbox. */
|
|
15
|
+
export interface SandboxLocaleValue {
|
|
16
|
+
/** Idioma activo. */
|
|
17
|
+
locale: ITLocale;
|
|
18
|
+
/** Cambia el idioma y lo persiste. */
|
|
19
|
+
setLocale: (locale: ITLocale) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Clave de `localStorage` donde se recuerda la preferencia. */
|
|
23
|
+
const STORAGE_KEY = "it-locale";
|
|
24
|
+
|
|
25
|
+
const SandboxLocaleContext = createContext<SandboxLocaleValue | undefined>(undefined);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Lee la preferencia guardada. Cualquier valor distinto de `"en"` cae en `"es"`,
|
|
29
|
+
* que es el idioma por defecto del sandbox.
|
|
30
|
+
*/
|
|
31
|
+
function readStoredLocale(): ITLocale {
|
|
32
|
+
if (typeof window === "undefined") return "es";
|
|
33
|
+
return window.localStorage.getItem(STORAGE_KEY) === "en" ? "en" : "es";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Provee el idioma del sandbox a todo el árbol (chrome + páginas).
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* <SandboxLocaleProvider>
|
|
42
|
+
* <ITI18nProvider locale={locale} messages={sandboxMessages}>…</ITI18nProvider>
|
|
43
|
+
* </SandboxLocaleProvider>
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function SandboxLocaleProvider({ children }: { children: ReactNode }) {
|
|
47
|
+
const [locale, setLocale] = useState<ITLocale>(readStoredLocale);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
window.localStorage.setItem(STORAGE_KEY, locale);
|
|
51
|
+
}, [locale]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<SandboxLocaleContext.Provider value={{ locale, setLocale }}>
|
|
55
|
+
{children}
|
|
56
|
+
</SandboxLocaleContext.Provider>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Acceso al idioma del sandbox. Fuera del provider devuelve español y un cambio
|
|
62
|
+
* de idioma sin efecto, para que una página se pueda renderizar aislada (tests,
|
|
63
|
+
* Storybook futuro) sin explotar.
|
|
64
|
+
*/
|
|
65
|
+
export function useSandboxLocale(): SandboxLocaleValue {
|
|
66
|
+
return (
|
|
67
|
+
useContext(SandboxLocaleContext) ?? {
|
|
68
|
+
locale: "es",
|
|
69
|
+
setLocale: () => undefined,
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kit de UI de las superficies dev (sandbox `#ui-system` + portafolio `/`).
|
|
3
|
+
*
|
|
4
|
+
* Son las piezas que **no** se usan de la librería publicada porque esta no
|
|
5
|
+
* garantiza contraste AA con la paleta por defecto: `ITButton` relleno queda en
|
|
6
|
+
* 2.4:1, `ITButton color="secondary"` en 3.7:1, `ITBadget` outlined en 2.6:1 y
|
|
7
|
+
* `ITSegmentedControl` en 4.34:1. Aquí el acento se usa en `primary-700`
|
|
8
|
+
* (claro) / `primary-500` (oscuro), que sí pasan.
|
|
9
|
+
*
|
|
10
|
+
* Vive en `src/sandbox/` a propósito: es dev-only y nunca entra en `dist/`.
|
|
11
|
+
* Sigue `DESIGN.md` (AXZY Console): superficies planas con borde de 1px,
|
|
12
|
+
* metadatos en mono, filetes para separar y sombra solo al hover.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { useState, type ReactNode } from "react";
|
|
16
|
+
import { FaCheck, FaCode, FaCopy } from "react-icons/fa";
|
|
17
|
+
import { useITTranslation } from "@/i18n";
|
|
18
|
+
|
|
19
|
+
/** Eyebrow mono apagado: encabezados de sección y etiquetas de metadato. */
|
|
20
|
+
export const EYEBROW =
|
|
21
|
+
"font-mono !text-[11px] !font-medium uppercase !tracking-[0.14em] text-slate-500 dark:text-slate-400";
|
|
22
|
+
|
|
23
|
+
/** Filete de 1px: la separación entre bloques. */
|
|
24
|
+
export const FILETE = "border-slate-200 dark:border-slate-800";
|
|
25
|
+
|
|
26
|
+
/** Superficie de tarjeta: borde 1px, radio 14px, sin sombra en reposo. */
|
|
27
|
+
export const CARD =
|
|
28
|
+
"rounded-[14px] border border-slate-200 bg-white transition-all duration-200 dark:border-slate-800 dark:bg-slate-900";
|
|
29
|
+
|
|
30
|
+
/** Refuerzo al hover de las tarjetas: borde acento, lift de 2px y sombra suave. */
|
|
31
|
+
export const CARD_HOVER =
|
|
32
|
+
"hover:-translate-y-0.5 hover:border-primary-400 hover:shadow-[0_8px_30px_-12px_rgba(15,23,42,0.25)] dark:hover:border-primary-500";
|
|
33
|
+
|
|
34
|
+
/** Chip mono chico para stack y metadatos. */
|
|
35
|
+
export const CHIP =
|
|
36
|
+
"rounded-[6px] border border-slate-200 bg-slate-50 px-2 py-0.5 font-mono text-[10px] text-slate-500 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-400";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Variantes visuales de los CTA. `outlined` es el borde de acento (segundo CTA
|
|
40
|
+
* de un bloque) y `neutral` el ghost de tinta para acciones que no compiten
|
|
41
|
+
* (volver, cerrar).
|
|
42
|
+
*/
|
|
43
|
+
export type VarianteCta = "filled" | "outlined" | "neutral" | "text";
|
|
44
|
+
|
|
45
|
+
/** Tamaños de los CTA (mismos paddings y tipografía que `ITButton`). */
|
|
46
|
+
export type TamanoCta = "sm" | "md" | "lg";
|
|
47
|
+
|
|
48
|
+
/** Padding y tipografía por tamaño (idénticos a `ITButton`). */
|
|
49
|
+
export const TAMANO_CTA: Record<TamanoCta, string> = {
|
|
50
|
+
sm: "text-xs px-2.5 py-1",
|
|
51
|
+
md: "text-[13px] px-3.5 py-1.5",
|
|
52
|
+
lg: "text-sm px-4 py-2",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Color por variante. El relleno usa `primary-700` (5.05:1 con blanco) porque
|
|
57
|
+
* el `primary-600` de la paleta resuelta se queda en 3.3:1.
|
|
58
|
+
*/
|
|
59
|
+
export const VARIANTE_CTA: Record<VarianteCta, string> = {
|
|
60
|
+
filled:
|
|
61
|
+
"border border-transparent bg-primary-700 text-white shadow-sm hover:bg-primary-800",
|
|
62
|
+
outlined:
|
|
63
|
+
"border border-primary-700 text-primary-700 hover:bg-primary-700/10 dark:border-primary-500 dark:text-primary-500",
|
|
64
|
+
neutral:
|
|
65
|
+
"border border-slate-300 text-slate-700 hover:bg-slate-100 dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800",
|
|
66
|
+
text: "border border-transparent text-primary-700 hover:bg-primary-700/10 dark:text-primary-500",
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** Foco compartido por todos los CTA (anillo del acento, offset del lienzo). */
|
|
70
|
+
export const FOCO_CTA =
|
|
71
|
+
"focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/60 focus-visible:ring-offset-2 focus-visible:ring-offset-[#fafafa] dark:focus-visible:ring-offset-[#0b0d10]";
|
|
72
|
+
|
|
73
|
+
/** Props de `EnlaceBoton`. */
|
|
74
|
+
export interface EnlaceBotonProps {
|
|
75
|
+
/** Destino. Interno (`#...`, `/...`) o externo (`https://...`). */
|
|
76
|
+
href: string;
|
|
77
|
+
/** Texto visible del enlace. */
|
|
78
|
+
children: ReactNode;
|
|
79
|
+
/** Estilo visual. @default "filled" */
|
|
80
|
+
variante?: VarianteCta;
|
|
81
|
+
/** Tamaño. @default "md" */
|
|
82
|
+
tamano?: TamanoCta;
|
|
83
|
+
/** Ícono a la izquierda del texto. */
|
|
84
|
+
icono?: ReactNode;
|
|
85
|
+
/** Fuerza abrir en pestaña nueva. Por defecto se deduce de `href`. */
|
|
86
|
+
externo?: boolean;
|
|
87
|
+
/** Marca la descarga (`download`) para archivos propios. */
|
|
88
|
+
descargar?: boolean;
|
|
89
|
+
/** Clases extra. */
|
|
90
|
+
className?: string;
|
|
91
|
+
/** Nombre accesible cuando el texto visible no alcanza. */
|
|
92
|
+
ariaLabel?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* CTA que **es un enlace de verdad**: `ITButton` renderiza un `<button>`, así que
|
|
97
|
+
* envolverlo en `<a>` producía interactivos anidados (HTML inválido y fallo
|
|
98
|
+
* `nested-interactive` en axe). Esta pieza conserva el aspecto del botón, pero
|
|
99
|
+
* mantiene la semántica de enlace: clic central, "abrir en pestaña nueva", menú
|
|
100
|
+
* contextual y URL en la barra de estado.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```tsx
|
|
104
|
+
* <EnlaceBoton href={GITHUB_URL} variante="outlined" icono={<FaGithub size={12} />}>
|
|
105
|
+
* GitHub
|
|
106
|
+
* </EnlaceBoton>
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export const EnlaceBoton = ({
|
|
110
|
+
href,
|
|
111
|
+
children,
|
|
112
|
+
variante = "filled",
|
|
113
|
+
tamano = "md",
|
|
114
|
+
icono,
|
|
115
|
+
externo,
|
|
116
|
+
descargar = false,
|
|
117
|
+
className = "",
|
|
118
|
+
ariaLabel,
|
|
119
|
+
}: EnlaceBotonProps) => {
|
|
120
|
+
const esExterno = externo ?? /^https?:\/\//.test(href);
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<a
|
|
124
|
+
href={href}
|
|
125
|
+
{...(esExterno ? { target: "_blank", rel: "noopener noreferrer" } : {})}
|
|
126
|
+
{...(descargar ? { download: "" } : {})}
|
|
127
|
+
aria-label={ariaLabel}
|
|
128
|
+
className={`inline-flex shrink-0 items-center justify-center gap-1.5 rounded-[10px] font-semibold transition-colors duration-200 ${FOCO_CTA} ${TAMANO_CTA[tamano]} ${VARIANTE_CTA[variante]} ${className}`}
|
|
129
|
+
>
|
|
130
|
+
{icono && <span className="flex-shrink-0">{icono}</span>}
|
|
131
|
+
<span>{children}</span>
|
|
132
|
+
</a>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/** Props de `BotonAccion`. */
|
|
137
|
+
export interface BotonAccionProps {
|
|
138
|
+
/** Acción al pulsar. */
|
|
139
|
+
onClick: () => void;
|
|
140
|
+
/** Texto visible. */
|
|
141
|
+
children: ReactNode;
|
|
142
|
+
/** Estilo visual. @default "neutral" */
|
|
143
|
+
variante?: VarianteCta;
|
|
144
|
+
/** Tamaño. @default "md" */
|
|
145
|
+
tamano?: TamanoCta;
|
|
146
|
+
/** Ícono a la izquierda. */
|
|
147
|
+
icono?: ReactNode;
|
|
148
|
+
/** Clases extra. */
|
|
149
|
+
className?: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Acción que **no** navega (volver, abrir una pestaña interna): mismo aspecto que
|
|
154
|
+
* `EnlaceBoton` pero con semántica de botón.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```tsx
|
|
158
|
+
* <BotonAccion onClick={onBack} icono={<FaArrowLeft size={12} />}>Volver</BotonAccion>
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export const BotonAccion = ({
|
|
162
|
+
onClick,
|
|
163
|
+
children,
|
|
164
|
+
variante = "neutral",
|
|
165
|
+
tamano = "md",
|
|
166
|
+
icono,
|
|
167
|
+
className = "",
|
|
168
|
+
}: BotonAccionProps) => (
|
|
169
|
+
<button
|
|
170
|
+
type="button"
|
|
171
|
+
onClick={onClick}
|
|
172
|
+
className={`inline-flex shrink-0 items-center justify-center gap-1.5 rounded-[10px] font-semibold transition-colors duration-200 ${FOCO_CTA} ${TAMANO_CTA[tamano]} ${VARIANTE_CTA[variante]} ${className}`}
|
|
173
|
+
>
|
|
174
|
+
{icono && <span className="flex-shrink-0">{icono}</span>}
|
|
175
|
+
<span>{children}</span>
|
|
176
|
+
</button>
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
/** Props de `Etiqueta`. */
|
|
180
|
+
export interface EtiquetaProps {
|
|
181
|
+
/** Texto de la etiqueta (tipo, estado…). */
|
|
182
|
+
children: ReactNode;
|
|
183
|
+
/** Clases extra. */
|
|
184
|
+
className?: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Píldora de metadato con el acento del proyecto.
|
|
189
|
+
*
|
|
190
|
+
* Sustituye a `ITBadget`: el badge del tema resuelve su color desde la paleta y
|
|
191
|
+
* con la paleta por defecto queda en 2.6:1 sobre las tarjetas oscuras.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```tsx
|
|
195
|
+
* <Etiqueta>Librería</Etiqueta>
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
export const Etiqueta = ({ children, className = "" }: EtiquetaProps) => (
|
|
199
|
+
<span
|
|
200
|
+
className={`shrink-0 rounded-full border border-primary-800/45 px-2 py-[3px] text-[11px] font-semibold text-primary-800 dark:border-primary-500/45 dark:text-primary-500 ${className}`}
|
|
201
|
+
>
|
|
202
|
+
{children}
|
|
203
|
+
</span>
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Encabezado de sección de la hoja: `NN — ETIQUETA`, título display y apoyo,
|
|
208
|
+
* con acciones opcionales a la derecha.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```tsx
|
|
212
|
+
* <SectionHeading index={2} label="Catálogo" title="Explora por familia" subtitle="…" />
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
export const SectionHeading = ({
|
|
216
|
+
index,
|
|
217
|
+
label,
|
|
218
|
+
title,
|
|
219
|
+
subtitle,
|
|
220
|
+
actions,
|
|
221
|
+
className = "",
|
|
222
|
+
}: {
|
|
223
|
+
/** Índice de sección (0-based; se pinta con dos dígitos). */
|
|
224
|
+
index: number;
|
|
225
|
+
/** Etiqueta mono corta. */
|
|
226
|
+
label: string;
|
|
227
|
+
/** Título display. */
|
|
228
|
+
title: string;
|
|
229
|
+
/** Frase de apoyo. */
|
|
230
|
+
subtitle?: string;
|
|
231
|
+
/** Contenido alineado a la derecha. */
|
|
232
|
+
actions?: ReactNode;
|
|
233
|
+
/** Clases extra. */
|
|
234
|
+
className?: string;
|
|
235
|
+
}) => (
|
|
236
|
+
<div
|
|
237
|
+
className={`flex flex-col gap-5 md:flex-row md:items-end md:justify-between ${className}`}
|
|
238
|
+
>
|
|
239
|
+
<div className="min-w-0">
|
|
240
|
+
<p className={EYEBROW}>
|
|
241
|
+
<span className="tabular-nums">{String(index + 1).padStart(2, "0")}</span>
|
|
242
|
+
{` — ${label}`}
|
|
243
|
+
</p>
|
|
244
|
+
<h2 className="mt-2.5 font-display text-[26px] font-bold tracking-[-0.02em] text-slate-900 dark:text-white sm:text-[32px]">
|
|
245
|
+
{title}
|
|
246
|
+
</h2>
|
|
247
|
+
{subtitle && (
|
|
248
|
+
<p className="mt-2 max-w-[62ch] text-[15px] leading-relaxed text-slate-500 dark:text-slate-400">
|
|
249
|
+
{subtitle}
|
|
250
|
+
</p>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
{actions && <div className="shrink-0">{actions}</div>}
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Enlace externo mono en línea (GitHub, npm, docs). Mantiene el acento AA y se
|
|
259
|
+
* subraya al hover.
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```tsx
|
|
263
|
+
* <EnlaceMono href={GITHUB_URL} icono={<FaGithub size={11} />}>GitHub</EnlaceMono>
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
export const EnlaceMono = ({
|
|
267
|
+
href,
|
|
268
|
+
children,
|
|
269
|
+
icono,
|
|
270
|
+
className = "",
|
|
271
|
+
}: {
|
|
272
|
+
/** Destino (normalmente externo). */
|
|
273
|
+
href: string;
|
|
274
|
+
/** Texto visible. */
|
|
275
|
+
children: ReactNode;
|
|
276
|
+
/** Ícono a la izquierda. */
|
|
277
|
+
icono?: ReactNode;
|
|
278
|
+
/** Clases extra. */
|
|
279
|
+
className?: string;
|
|
280
|
+
}) => (
|
|
281
|
+
<a
|
|
282
|
+
href={href}
|
|
283
|
+
{...(/^https?:\/\//.test(href) ? { target: "_blank", rel: "noopener noreferrer" } : {})}
|
|
284
|
+
className={`inline-flex items-center gap-1.5 font-mono !text-[11px] uppercase !tracking-[0.14em] !text-primary-700 transition-colors hover:underline dark:!text-primary-500 ${className}`}
|
|
285
|
+
>
|
|
286
|
+
{icono}
|
|
287
|
+
{children}
|
|
288
|
+
</a>
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Botón mono de «copiar al portapapeles» con confirmación de dos segundos.
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```tsx
|
|
296
|
+
* <BotonCopiar texto="pnpm add @axzydev/axzy_ui_system" />
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
export const BotonCopiar = ({
|
|
300
|
+
texto,
|
|
301
|
+
className = "",
|
|
302
|
+
}: {
|
|
303
|
+
/** Texto que se copia. */
|
|
304
|
+
texto: string;
|
|
305
|
+
/** Clases extra. */
|
|
306
|
+
className?: string;
|
|
307
|
+
}) => {
|
|
308
|
+
const { t } = useITTranslation();
|
|
309
|
+
const [copiado, setCopiado] = useState(false);
|
|
310
|
+
|
|
311
|
+
const copiar = () => {
|
|
312
|
+
navigator.clipboard?.writeText(texto);
|
|
313
|
+
setCopiado(true);
|
|
314
|
+
window.setTimeout(() => setCopiado(false), 2000);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<button
|
|
319
|
+
type="button"
|
|
320
|
+
onClick={copiar}
|
|
321
|
+
className={`inline-flex items-center gap-1.5 rounded-[8px] px-2.5 py-1 font-mono text-[10px] uppercase tracking-[0.12em] text-slate-500 transition-colors hover:bg-slate-100 hover:text-slate-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/60 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-white ${className}`}
|
|
322
|
+
>
|
|
323
|
+
{copiado ? (
|
|
324
|
+
<FaCheck size={10} className="text-primary-700 dark:text-primary-500" />
|
|
325
|
+
) : (
|
|
326
|
+
<FaCopy size={10} />
|
|
327
|
+
)}
|
|
328
|
+
{copiado ? t("showcase.layout.copied") : t("showcase.layout.copy")}
|
|
329
|
+
</button>
|
|
330
|
+
);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Bloque de código: fondo tinta, mono, etiqueta del archivo y botón de copiar.
|
|
335
|
+
* El acento de señal (`cyan-400`) solo se usa aquí, como manda el spec.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```tsx
|
|
339
|
+
* <BloqueCodigo filename="vite.config.ts" code={`export default defineConfig({})`} />
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
export const BloqueCodigo = ({
|
|
343
|
+
code,
|
|
344
|
+
filename,
|
|
345
|
+
compact = false,
|
|
346
|
+
className = "",
|
|
347
|
+
}: {
|
|
348
|
+
/** Código a mostrar. */
|
|
349
|
+
code: string;
|
|
350
|
+
/** Etiqueta mono de la esquina (archivo o lenguaje). */
|
|
351
|
+
filename?: string;
|
|
352
|
+
/** Limita la altura con scroll vertical. */
|
|
353
|
+
compact?: boolean;
|
|
354
|
+
/** Clases extra. */
|
|
355
|
+
className?: string;
|
|
356
|
+
}) => (
|
|
357
|
+
<div
|
|
358
|
+
className={`overflow-hidden rounded-[10px] border border-slate-800 bg-slate-950 ${className}`}
|
|
359
|
+
>
|
|
360
|
+
<div className="flex items-center justify-between gap-3 border-b border-slate-800 px-4 py-2.5">
|
|
361
|
+
<span className="font-mono text-[10px] uppercase tracking-[0.14em] text-cyan-400">
|
|
362
|
+
{filename ?? "code"}
|
|
363
|
+
</span>
|
|
364
|
+
<BotonCopiar
|
|
365
|
+
texto={code}
|
|
366
|
+
className="text-slate-400 hover:bg-slate-800 hover:text-white dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-white"
|
|
367
|
+
/>
|
|
368
|
+
</div>
|
|
369
|
+
<pre
|
|
370
|
+
tabIndex={0}
|
|
371
|
+
className={`overflow-x-auto whitespace-pre-wrap p-4 font-mono text-xs leading-relaxed text-slate-200 ${
|
|
372
|
+
compact ? "max-h-[420px] overflow-y-auto" : ""
|
|
373
|
+
}`}
|
|
374
|
+
>
|
|
375
|
+
<code>{code}</code>
|
|
376
|
+
</pre>
|
|
377
|
+
</div>
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
/** Props de `EjemploContenedor`. */
|
|
381
|
+
export interface EjemploContenedorProps {
|
|
382
|
+
/** Número del ejemplo (`01`, `02`…). Opcional. */
|
|
383
|
+
numero?: string;
|
|
384
|
+
/** Título del ejemplo (mono, mayúsculas). */
|
|
385
|
+
titulo: string;
|
|
386
|
+
/** El demo. */
|
|
387
|
+
children: ReactNode;
|
|
388
|
+
/** Código fuente del ejemplo: si se pasa, aparece el botón «Código». */
|
|
389
|
+
codigo?: string;
|
|
390
|
+
/** Nota al pie, bajo el demo. */
|
|
391
|
+
nota?: string;
|
|
392
|
+
/** Superficie del demo sin padding (layouts completos). */
|
|
393
|
+
sangrado?: boolean;
|
|
394
|
+
/** Clases extra de la superficie del demo. */
|
|
395
|
+
className?: string;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Contenedor de un ejemplo del sandbox: cabecera con número, título y la acción
|
|
400
|
+
* de ver el código (siempre visible), superficie del demo y nota opcional. El
|
|
401
|
+
* código se despliega **dentro** del mismo contenedor, no en una pestaña aparte.
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* ```tsx
|
|
405
|
+
* <EjemploContenedor numero="01" titulo="Con icono" codigo={code}>
|
|
406
|
+
* <ITButton icon={<FaSave />}>Guardar</ITButton>
|
|
407
|
+
* </EjemploContenedor>
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
export const EjemploContenedor = ({
|
|
411
|
+
numero,
|
|
412
|
+
titulo,
|
|
413
|
+
children,
|
|
414
|
+
codigo,
|
|
415
|
+
nota,
|
|
416
|
+
sangrado = false,
|
|
417
|
+
className = "",
|
|
418
|
+
}: EjemploContenedorProps) => {
|
|
419
|
+
const { t } = useITTranslation();
|
|
420
|
+
const [verCodigo, setVerCodigo] = useState(false);
|
|
421
|
+
|
|
422
|
+
return (
|
|
423
|
+
<section className={`overflow-hidden rounded-[10px] border ${FILETE}`}>
|
|
424
|
+
<header
|
|
425
|
+
className={`flex flex-wrap items-center gap-x-3 gap-y-2 border-b bg-slate-50/70 px-4 py-2.5 dark:bg-slate-950/40 ${FILETE}`}
|
|
426
|
+
>
|
|
427
|
+
{numero && (
|
|
428
|
+
<span className="font-mono text-[10px] tabular-nums text-slate-400 dark:text-slate-500">
|
|
429
|
+
{numero}
|
|
430
|
+
</span>
|
|
431
|
+
)}
|
|
432
|
+
<h2 className="min-w-0 flex-1 truncate font-mono text-[11px] font-medium uppercase tracking-[0.14em] text-slate-700 dark:text-slate-200">
|
|
433
|
+
{titulo}
|
|
434
|
+
</h2>
|
|
435
|
+
|
|
436
|
+
{codigo && (
|
|
437
|
+
<button
|
|
438
|
+
type="button"
|
|
439
|
+
onClick={() => setVerCodigo(!verCodigo)}
|
|
440
|
+
aria-expanded={verCodigo}
|
|
441
|
+
className={`inline-flex items-center gap-1.5 rounded-[8px] border px-2.5 py-1 font-mono text-[10px] uppercase tracking-[0.12em] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/60 ${
|
|
442
|
+
verCodigo
|
|
443
|
+
? "border-primary-700 text-primary-700 dark:border-primary-500 dark:text-primary-500"
|
|
444
|
+
: "border-slate-200 text-slate-500 hover:text-slate-900 dark:border-slate-700 dark:text-slate-400 dark:hover:text-white"
|
|
445
|
+
}`}
|
|
446
|
+
>
|
|
447
|
+
<FaCode size={10} />
|
|
448
|
+
{verCodigo ? t("showcase.layout.hideCode") : t("showcase.layout.viewCode")}
|
|
449
|
+
</button>
|
|
450
|
+
)}
|
|
451
|
+
</header>
|
|
452
|
+
|
|
453
|
+
<div
|
|
454
|
+
className={`bg-white dark:bg-slate-950 ${sangrado ? "" : "p-5 sm:p-6"} ${className}`}
|
|
455
|
+
>
|
|
456
|
+
{children}
|
|
457
|
+
</div>
|
|
458
|
+
|
|
459
|
+
{nota && (
|
|
460
|
+
<p
|
|
461
|
+
className={`border-t px-4 py-2.5 font-mono text-[11px] leading-relaxed text-slate-500 dark:text-slate-400 ${FILETE}`}
|
|
462
|
+
>
|
|
463
|
+
{nota}
|
|
464
|
+
</p>
|
|
465
|
+
)}
|
|
466
|
+
|
|
467
|
+
{codigo && verCodigo && (
|
|
468
|
+
<div className={`border-t p-3 ${FILETE}`}>
|
|
469
|
+
<BloqueCodigo code={codigo} compact />
|
|
470
|
+
</div>
|
|
471
|
+
)}
|
|
472
|
+
</section>
|
|
473
|
+
);
|
|
474
|
+
};
|