@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.
Files changed (52) hide show
  1. package/README.md +6 -2
  2. package/dist/index.cjs +2342 -1715
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.css +1 -1
  5. package/dist/index.d.cts +286 -9
  6. package/dist/index.d.ts +286 -9
  7. package/dist/index.js +2164 -1538
  8. package/dist/index.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/App.tsx +153 -153
  11. package/src/components/molecules/calendar/calendar.doc.mdx +2 -1
  12. package/src/components/molecules/calendar/calendar.tsx +102 -24
  13. package/src/components/molecules/masked-input/masked-input.doc.mdx +78 -0
  14. package/src/components/molecules/masked-input/masked-input.props.ts +58 -0
  15. package/src/components/molecules/masked-input/masked-input.stories.tsx +126 -0
  16. package/src/components/molecules/masked-input/masked-input.tsx +244 -0
  17. package/src/components/molecules/table/table.doc.mdx +64 -21
  18. package/src/components/molecules/table/table.props.ts +81 -2
  19. package/src/components/molecules/table/table.stories.tsx +155 -0
  20. package/src/components/molecules/table/table.tsx +177 -36
  21. package/src/components/organisms/data-table/ITDataTable.doc.mdx +37 -3
  22. package/src/components/organisms/data-table/ITDataTable.stories.tsx +43 -0
  23. package/src/components/organisms/data-table/dataTable.props.ts +64 -0
  24. package/src/components/organisms/data-table/dataTable.tsx +240 -82
  25. package/src/components/organisms/sidebar/sidebar.doc.mdx +5 -0
  26. package/src/components/organisms/sidebar/sidebar.props.ts +5 -0
  27. package/src/components/organisms/sidebar/sidebar.stories.tsx +1 -1
  28. package/src/components/organisms/sidebar/sidebar.tsx +27 -7
  29. package/src/components/organisms/topbar/topbar.doc.mdx +3 -0
  30. package/src/components/organisms/topbar/topbar.props.ts +16 -0
  31. package/src/components/organisms/topbar/topbar.tsx +133 -98
  32. package/src/components/templates/layout/layout.doc.mdx +2 -0
  33. package/src/components/templates/layout/layout.tsx +7 -1
  34. package/src/components/templates/page/page.doc.mdx +8 -2
  35. package/src/components/templates/page/page.props.ts +11 -1
  36. package/src/components/templates/page/page.tsx +11 -1
  37. package/src/dev.css +58 -1
  38. package/src/hooks/useElementSize.ts +46 -0
  39. package/src/hooks/useVirtualRows.ts +123 -0
  40. package/src/index.ts +6 -2
  41. package/src/sandbox/LandingShowcase.tsx +465 -0
  42. package/src/sandbox/NotFoundShowcase.tsx +70 -0
  43. package/src/sandbox/TopbarSearch.tsx +85 -0
  44. package/src/sandbox/navigation.ts +148 -0
  45. package/src/sandbox/useHashRoute.ts +149 -0
  46. package/src/showcases/DataShowcases.tsx +321 -24
  47. package/src/showcases/FormShowcases.tsx +178 -0
  48. package/src/showcases/PageShowcases.tsx +31 -0
  49. package/src/showcases/StructureShowcases.tsx +42 -4
  50. package/src/types/table.types.ts +48 -0
  51. package/src/utils/styles.ts +9 -4
  52. 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(false);
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
- onToggleCollapse: () => setCollapsed(v => !v),
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
- onToggleCollapse: () => setCollapsed(v => !v)
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 (&lt;lg) el sidebar se abre como drawer con un fondo oscuro. Usa el botón ☰ del topbar.
1045
+ En móvil (&lt;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
  };
@@ -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
+ };
@@ -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("px-4 py-4 align-top", className);
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 function tableCell(className?: string) {
91
- return clsx("px-4 py-3 align-middle", className);
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";
@@ -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
+ }