@axzydev/axzy_ui_system 1.3.0 → 1.3.2
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 +6 -2
- package/dist/index.cjs +2342 -1715
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +286 -9
- package/dist/index.d.ts +286 -9
- package/dist/index.js +2164 -1538
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/App.tsx +153 -153
- package/src/components/molecules/calendar/calendar.doc.mdx +2 -1
- package/src/components/molecules/calendar/calendar.tsx +102 -24
- package/src/components/molecules/masked-input/masked-input.doc.mdx +78 -0
- package/src/components/molecules/masked-input/masked-input.props.ts +58 -0
- package/src/components/molecules/masked-input/masked-input.stories.tsx +126 -0
- package/src/components/molecules/masked-input/masked-input.tsx +244 -0
- package/src/components/molecules/table/table.doc.mdx +64 -21
- package/src/components/molecules/table/table.props.ts +81 -2
- package/src/components/molecules/table/table.stories.tsx +155 -0
- package/src/components/molecules/table/table.tsx +177 -36
- package/src/components/organisms/data-table/ITDataTable.doc.mdx +37 -3
- package/src/components/organisms/data-table/ITDataTable.stories.tsx +43 -0
- package/src/components/organisms/data-table/dataTable.props.ts +64 -0
- package/src/components/organisms/data-table/dataTable.tsx +240 -82
- package/src/components/organisms/sidebar/sidebar.doc.mdx +5 -0
- package/src/components/organisms/sidebar/sidebar.props.ts +5 -0
- package/src/components/organisms/sidebar/sidebar.stories.tsx +1 -1
- package/src/components/organisms/sidebar/sidebar.tsx +27 -7
- package/src/components/organisms/topbar/topbar.doc.mdx +3 -0
- package/src/components/organisms/topbar/topbar.props.ts +16 -0
- package/src/components/organisms/topbar/topbar.tsx +133 -98
- package/src/components/templates/layout/layout.doc.mdx +2 -0
- package/src/components/templates/layout/layout.tsx +7 -1
- package/src/components/templates/page/page.doc.mdx +8 -2
- package/src/components/templates/page/page.props.ts +11 -1
- package/src/components/templates/page/page.tsx +11 -1
- package/src/dev.css +58 -1
- package/src/hooks/useElementSize.ts +46 -0
- package/src/hooks/useVirtualRows.ts +123 -0
- package/src/index.ts +6 -2
- package/src/sandbox/LandingShowcase.tsx +465 -0
- package/src/sandbox/NotFoundShowcase.tsx +70 -0
- package/src/sandbox/TopbarSearch.tsx +85 -0
- package/src/sandbox/navigation.ts +148 -0
- package/src/sandbox/useHashRoute.ts +149 -0
- package/src/showcases/DataShowcases.tsx +321 -24
- package/src/showcases/FormShowcases.tsx +178 -0
- package/src/showcases/PageShowcases.tsx +31 -0
- package/src/showcases/StructureShowcases.tsx +42 -4
- package/src/types/table.types.ts +48 -0
- package/src/utils/styles.ts +9 -4
- package/src/utils/table.utils.ts +31 -0
|
@@ -397,6 +397,15 @@ export const PageShowcase = () => {
|
|
|
397
397
|
const [screen, setScreen] = useState<"list" | "detail">("list");
|
|
398
398
|
const [selectedUser, setSelectedUser] = useState<Record<string, unknown> | null>(null);
|
|
399
399
|
const [showIcon, setShowIcon] = useState(true);
|
|
400
|
+
const [hPadding, setHPadding] = useState<"default" | "none" | "tight" | "wide">("default");
|
|
401
|
+
|
|
402
|
+
// `undefined` lets ITPage fall back to its own default ("px-4 sm:px-6 lg:px-8").
|
|
403
|
+
const hPaddingValue: string | undefined = {
|
|
404
|
+
default: undefined,
|
|
405
|
+
none: "px-0",
|
|
406
|
+
tight: "px-2 sm:px-3",
|
|
407
|
+
wide: "px-8 sm:px-12 lg:px-16",
|
|
408
|
+
}[hPadding];
|
|
400
409
|
|
|
401
410
|
const isNormal = state === "normal";
|
|
402
411
|
const isDetail = screen === "detail";
|
|
@@ -551,6 +560,7 @@ export const PageShowcase = () => {
|
|
|
551
560
|
error={state === "error" ? "Could not load user details. Please try again." : null}
|
|
552
561
|
errorTitle="Details error"
|
|
553
562
|
onRetry={() => setState("normal")}
|
|
563
|
+
horizontalPadding={hPaddingValue}
|
|
554
564
|
empty={state === "empty"}
|
|
555
565
|
emptyTitle="No user data"
|
|
556
566
|
emptyDescription="No information available for this user."
|
|
@@ -601,6 +611,7 @@ export const PageShowcase = () => {
|
|
|
601
611
|
error={state === "error" ? "Could not load team members. Check your connection and try again." : null}
|
|
602
612
|
errorTitle="Connection error"
|
|
603
613
|
onRetry={() => setState("normal")}
|
|
614
|
+
horizontalPadding={hPaddingValue}
|
|
604
615
|
empty={state === "empty"}
|
|
605
616
|
emptyTitle="No team members yet"
|
|
606
617
|
emptyDescription="Invite your first team member to get started."
|
|
@@ -640,6 +651,21 @@ export const PageShowcase = () => {
|
|
|
640
651
|
<ITSlideToggle isOn={showIcon} onToggle={setShowIcon} size="sm" />
|
|
641
652
|
</ITFlex>
|
|
642
653
|
|
|
654
|
+
<div>
|
|
655
|
+
<p className="text-xs font-bold uppercase tracking-wider text-slate-400 dark:text-slate-500 mb-2">Horizontal padding</p>
|
|
656
|
+
<ITFlex gap={2} wrap="wrap">
|
|
657
|
+
{(["default", "none", "tight", "wide"] as const).map((p) => (
|
|
658
|
+
<ITButton
|
|
659
|
+
key={p}
|
|
660
|
+
label={p === "default" ? "Default" : p === "none" ? "px-0" : p === "tight" ? "px-2" : "px-8"}
|
|
661
|
+
variant={hPadding === p ? "filled" : "outlined"}
|
|
662
|
+
size="sm"
|
|
663
|
+
onClick={() => setHPadding(p)}
|
|
664
|
+
/>
|
|
665
|
+
))}
|
|
666
|
+
</ITFlex>
|
|
667
|
+
</div>
|
|
668
|
+
|
|
643
669
|
{isNormal && (
|
|
644
670
|
<div>
|
|
645
671
|
<p className="text-xs font-bold uppercase tracking-wider text-slate-400 dark:text-slate-500 mb-2">Screen</p>
|
|
@@ -674,6 +700,7 @@ export const PageShowcase = () => {
|
|
|
674
700
|
<h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Users — table with actions</h4>
|
|
675
701
|
<div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden">
|
|
676
702
|
<ITPage
|
|
703
|
+
horizontalPadding={hPaddingValue}
|
|
677
704
|
title="Users"
|
|
678
705
|
description="1,245 registered · 38 active now"
|
|
679
706
|
icon={<FaUsers size={20} />}
|
|
@@ -701,6 +728,7 @@ export const PageShowcase = () => {
|
|
|
701
728
|
<h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Product detail</h4>
|
|
702
729
|
<div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden">
|
|
703
730
|
<ITPage
|
|
731
|
+
horizontalPadding={hPaddingValue}
|
|
704
732
|
title="MacBook Pro 16'' M3 Max"
|
|
705
733
|
description="SKU: MBP-16-M3MAX-2024 · Stock: 24"
|
|
706
734
|
icon={<FaBoxOpen size={20} />}
|
|
@@ -748,6 +776,7 @@ export const PageShowcase = () => {
|
|
|
748
776
|
<h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Edit profile</h4>
|
|
749
777
|
<div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden">
|
|
750
778
|
<ITPage
|
|
779
|
+
horizontalPadding={hPaddingValue}
|
|
751
780
|
title="Edit profile"
|
|
752
781
|
description="Update your personal information and contact details"
|
|
753
782
|
icon={<FaEdit size={20} />}
|
|
@@ -798,6 +827,7 @@ export const PageShowcase = () => {
|
|
|
798
827
|
<h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Orders — status table</h4>
|
|
799
828
|
<div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden">
|
|
800
829
|
<ITPage
|
|
830
|
+
horizontalPadding={hPaddingValue}
|
|
801
831
|
title="Orders"
|
|
802
832
|
description="89 pending · 1,247 completed this month"
|
|
803
833
|
icon={<FaShoppingCart size={20} />}
|
|
@@ -850,6 +880,7 @@ export const PageShowcase = () => {
|
|
|
850
880
|
<h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Settings</h4>
|
|
851
881
|
<div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden">
|
|
852
882
|
<ITPage
|
|
883
|
+
horizontalPadding={hPaddingValue}
|
|
853
884
|
title="Settings"
|
|
854
885
|
description="Manage your account and workspace preferences"
|
|
855
886
|
icon={<FaCog size={20} />}
|
|
@@ -901,13 +901,20 @@ export const TextShowcase = () => {
|
|
|
901
901
|
|
|
902
902
|
// 3. ITLayout & ITNavbar Showcase
|
|
903
903
|
export const LayoutShowcase = () => {
|
|
904
|
-
const [collapsed, setCollapsed] = useState(
|
|
904
|
+
const [collapsed, setCollapsed] = useState(true);
|
|
905
905
|
const [activeId, setActiveId] = useState("dashboard");
|
|
906
906
|
const [navActiveId, setNavActiveId] = useState<string | null>("1");
|
|
907
907
|
|
|
908
908
|
const topBar = {
|
|
909
909
|
logo: <div className="w-9 h-9 rounded-lg bg-primary-500 flex items-center justify-center text-white font-bold">A</div>,
|
|
910
910
|
logoText: "AXZY Console",
|
|
911
|
+
centerContent: (
|
|
912
|
+
<input
|
|
913
|
+
type="search"
|
|
914
|
+
placeholder="Buscar componente…"
|
|
915
|
+
className="w-full rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 px-3 py-1.5 text-xs text-slate-700 dark:text-slate-200 outline-none focus:ring-2 focus:ring-primary-500/40"
|
|
916
|
+
/>
|
|
917
|
+
),
|
|
911
918
|
navItems: [
|
|
912
919
|
{ id: "home", label: "Inicio", icon: <FaHome />, action: () => setActiveId("home") },
|
|
913
920
|
{ id: "docs", label: "Documentos", icon: <FaFileAlt />, action: () => setActiveId("docs") },
|
|
@@ -942,19 +949,29 @@ export const LayoutShowcase = () => {
|
|
|
942
949
|
{ id: "settings", label: "Configuración", icon: <FaCog />, isActive: activeId === "settings", action: () => setActiveId("settings") },
|
|
943
950
|
],
|
|
944
951
|
isCollapsed: collapsed,
|
|
945
|
-
|
|
952
|
+
header: (
|
|
953
|
+
<ITText
|
|
954
|
+
className="text-xs font-bold tracking-wide"
|
|
955
|
+
style={{ color: "var(--it-sidebar-label-color, var(--color-secondary-700))" }}
|
|
956
|
+
>
|
|
957
|
+
AXZY v1.3
|
|
958
|
+
</ITText>
|
|
959
|
+
),
|
|
946
960
|
};
|
|
947
961
|
|
|
948
962
|
const code = `<ITLayout
|
|
949
963
|
topBar={{
|
|
950
964
|
logo: <Logo />,
|
|
951
965
|
logoText: "AXZY Console",
|
|
966
|
+
// Slot central opcional (visible desde lg) — ideal para un buscador global
|
|
967
|
+
centerContent: <SearchBox value={term} onChange={setTerm} />,
|
|
952
968
|
userMenu: { userName, userEmail, menuItems }
|
|
953
969
|
}}
|
|
954
970
|
sidebar={{
|
|
955
971
|
navigationItems: [...],
|
|
956
972
|
isCollapsed: ${collapsed},
|
|
957
|
-
|
|
973
|
+
// Slot opcional sobre la navegación (oculto al colapsar)
|
|
974
|
+
header: <span>AXZY v1.3</span>
|
|
958
975
|
}}
|
|
959
976
|
>
|
|
960
977
|
{/* Tu contenido */}
|
|
@@ -1025,7 +1042,7 @@ export const LayoutShowcase = () => {
|
|
|
1025
1042
|
<ITSlideToggle isOn={collapsed} onToggle={setCollapsed} size="sm" />
|
|
1026
1043
|
</div>
|
|
1027
1044
|
<div className="text-xs text-slate-500">
|
|
1028
|
-
En móvil (<lg) el sidebar se abre como drawer con un fondo oscuro. Usa el botón ☰ del topbar.
|
|
1045
|
+
En móvil (<lg) el sidebar se abre como drawer con un fondo oscuro. Usa el botón ☰ del topbar. El botón ⟩ del propio sidebar también colapsa/expande.
|
|
1029
1046
|
</div>
|
|
1030
1047
|
</ITStack>
|
|
1031
1048
|
}
|
|
@@ -1088,6 +1105,27 @@ export const LayoutShowcase = () => {
|
|
|
1088
1105
|
</div>
|
|
1089
1106
|
</ITStack>
|
|
1090
1107
|
}
|
|
1108
|
+
doc={{
|
|
1109
|
+
summary: "Chasis estructural del portal: topbar + sidebar colapsable + área de contenido.",
|
|
1110
|
+
examples: [
|
|
1111
|
+
'<ITLayout topBar={{ centerContent: <SearchBox /> }} sidebar={{ navigationItems }}>',
|
|
1112
|
+
" <Dashboard />",
|
|
1113
|
+
"</ITLayout>",
|
|
1114
|
+
],
|
|
1115
|
+
props: [
|
|
1116
|
+
{ name: "topBar", type: "ITTopBarProps", required: true, description: "Configuración de la barra superior." },
|
|
1117
|
+
{ name: "sidebar", type: "ITSidebarProps", required: true, description: "Configuración del sidebar (navigationItems, isCollapsed, header…)." },
|
|
1118
|
+
{ name: "children", type: "React.ReactNode", required: true, description: "Contenido principal." },
|
|
1119
|
+
{ name: "className", type: "string", description: "Clases del wrapper externo." },
|
|
1120
|
+
{ name: "contentClassName", type: "string", description: "Clases del contenedor de contenido." },
|
|
1121
|
+
{ name: "sidebar.header", type: "React.ReactNode", description: "Slot opcional sobre la navegación (ej. wordmark o filtro). Oculto al colapsar." },
|
|
1122
|
+
],
|
|
1123
|
+
notes: [
|
|
1124
|
+
"El sidebar hereda aria-current/aria-expanded y acepta un slot header opcional.",
|
|
1125
|
+
"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.",
|
|
1126
|
+
"El riel colapsado se expande automáticamente al pasar el cursor por encima; no hay botón de colapso.",
|
|
1127
|
+
],
|
|
1128
|
+
}}
|
|
1091
1129
|
/>
|
|
1092
1130
|
);
|
|
1093
1131
|
};
|
package/src/types/table.types.ts
CHANGED
|
@@ -13,3 +13,51 @@ export const sizeStyles: Record<TableSize, string> = {
|
|
|
13
13
|
md: "text-sm",
|
|
14
14
|
lg: "text-lg",
|
|
15
15
|
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Cell padding / row-height preset for tables. Independent of `TableSize`
|
|
19
|
+
* (which only controls font-size, and shifts the row height via the line box).
|
|
20
|
+
*/
|
|
21
|
+
export type TableDensity = "compact" | "normal" | "comfortable";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Padding classes and target row height per density. `normal` MUST stay
|
|
25
|
+
* byte-identical to the historical hard-coded `px-4 py-4` / `px-4 py-3`.
|
|
26
|
+
*
|
|
27
|
+
* The `rowHeight` values are the `size="md"` baseline:
|
|
28
|
+
* rowHeight = cell vertical padding + text-sm line box (20px) + 1px divide-y border.
|
|
29
|
+
* compact: py-1.5 (12) + 20 + 1 = 33 · normal: py-3 (24) + 20 + 1 = 45 · comfortable: py-4 (32) + 20 + 1 = 53
|
|
30
|
+
*
|
|
31
|
+
* `size` shifts that baseline via the font-size line box; use
|
|
32
|
+
* `getRowHeight(size, density)` for the real rendered row height.
|
|
33
|
+
*/
|
|
34
|
+
export const densityStyles: Record<
|
|
35
|
+
TableDensity,
|
|
36
|
+
{ header: string; cell: string; rowHeight: number }
|
|
37
|
+
> = {
|
|
38
|
+
compact: { header: "px-3 py-2", cell: "px-3 py-1.5", rowHeight: 33 },
|
|
39
|
+
normal: { header: "px-4 py-4", cell: "px-4 py-3", rowHeight: 45 },
|
|
40
|
+
comfortable: { header: "px-4 py-5", cell: "px-4 py-4", rowHeight: 53 },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Line box (px) contributed by the `size` prop's font-size class (`text-xs|sm|lg`). */
|
|
44
|
+
export const sizeLineHeights: Record<TableSize, number> = { sm: 16, md: 20, lg: 28 };
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Row height (px) for a given `size` + `density`, matching the real rendered
|
|
48
|
+
* row: cell vertical padding + the size line box + 1px divide-y border.
|
|
49
|
+
* `densityStyles[density].rowHeight` is the `md` baseline.
|
|
50
|
+
*/
|
|
51
|
+
export function getRowHeight(size: TableSize, density: TableDensity): number {
|
|
52
|
+
return densityStyles[density].rowHeight - sizeLineHeights.md + sizeLineHeights[size];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Static horizontal alignment classes for columns. Kept as full literal
|
|
57
|
+
* strings so Tailwind's scanner can see them (never build them dynamically).
|
|
58
|
+
*/
|
|
59
|
+
export const tableAlignClasses: Record<"left" | "center" | "right", string> = {
|
|
60
|
+
left: "text-left",
|
|
61
|
+
center: "text-center",
|
|
62
|
+
right: "text-right",
|
|
63
|
+
};
|
package/src/utils/styles.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
2
|
import type { CSSProperties } from "react";
|
|
3
3
|
import { inputSizeTokens } from "@/theme/theme";
|
|
4
|
+
import { densityStyles, type TableDensity } from "@/types/table.types";
|
|
4
5
|
|
|
5
6
|
/** Theme shape expected from `theme.input` (used by input-like components). */
|
|
6
7
|
export type InputThemeLike = {
|
|
@@ -76,8 +77,8 @@ export const tableContainer = "rounded-xl shadow-sm border border-secondary-200
|
|
|
76
77
|
|
|
77
78
|
export const tableHeaderRow = "bg-secondary-50 border-b border-secondary-200 text-xs uppercase tracking-wider font-semibold text-secondary-500";
|
|
78
79
|
|
|
79
|
-
export function tableHeaderCell(className?: string) {
|
|
80
|
-
return clsx("
|
|
80
|
+
export function tableHeaderCell(className?: string, density: TableDensity = "normal") {
|
|
81
|
+
return clsx(densityStyles[density].header, "align-top", className);
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
export const tableSortButtonActive = "bg-secondary-200 text-secondary-900";
|
|
@@ -87,8 +88,12 @@ export const tableBody = "divide-y divide-secondary-100";
|
|
|
87
88
|
|
|
88
89
|
export const tableRow = "hover:bg-secondary-50/50 transition-colors duration-150 group";
|
|
89
90
|
|
|
90
|
-
export
|
|
91
|
-
|
|
91
|
+
export const tableRowClickable = "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-inset";
|
|
92
|
+
|
|
93
|
+
export const tableCardClickable = "cursor-pointer rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500";
|
|
94
|
+
|
|
95
|
+
export function tableCell(className?: string, density: TableDensity = "normal") {
|
|
96
|
+
return clsx(densityStyles[density].cell, "align-middle", className);
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
export const tableActionsCell = "flex items-center justify-center gap-2";
|
package/src/utils/table.utils.ts
CHANGED
|
@@ -8,3 +8,34 @@ export const formatCurrencyMX = (value: number) => {
|
|
|
8
8
|
export const getNestedValue = (obj: any, path: string) => {
|
|
9
9
|
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
|
|
10
10
|
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* CSS selector matching any element that should swallow a row/card click
|
|
14
|
+
* instead of letting it bubble up to the row activation handler. Covers native
|
|
15
|
+
* interactive elements plus anything explicitly opted out with
|
|
16
|
+
* `data-row-click-ignore`.
|
|
17
|
+
*/
|
|
18
|
+
export const ROW_CLICK_IGNORE_SELECTOR =
|
|
19
|
+
'button, a, input, select, textarea, [role="button"], [role="link"], [data-row-click-ignore]';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns `true` when the event target (or one of its ancestors) is an
|
|
23
|
+
* interactive element that must not trigger the row/card `onRowClick` handler.
|
|
24
|
+
*
|
|
25
|
+
* @param target - The event target to inspect (usually `event.target`).
|
|
26
|
+
* @param boundary - Optional activation container (usually `event.currentTarget`).
|
|
27
|
+
* When the matched interactive element is the boundary itself it is ignored,
|
|
28
|
+
* because the cards-view wrapper carries `role="button"` for accessibility and
|
|
29
|
+
* would otherwise swallow its own activation. Only interactive descendants
|
|
30
|
+
* block the handler.
|
|
31
|
+
*/
|
|
32
|
+
export function isInteractiveTarget(
|
|
33
|
+
target: EventTarget | null,
|
|
34
|
+
boundary: EventTarget | null = null
|
|
35
|
+
): boolean {
|
|
36
|
+
if (!(target instanceof Element)) return false;
|
|
37
|
+
const interactive = target.closest(ROW_CLICK_IGNORE_SELECTOR);
|
|
38
|
+
if (!interactive) return false;
|
|
39
|
+
if (boundary instanceof Node && interactive === boundary) return false;
|
|
40
|
+
return true;
|
|
41
|
+
}
|