@axzydev/axzy_ui_system 1.4.2 → 1.4.4

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.
@@ -4,6 +4,7 @@ import ITTopBar from "@/components/organisms/topbar/topbar";
4
4
  import ITSidebar from "@/components/organisms/sidebar/sidebar";
5
5
  import { ITLayoutProps } from "./layout.props";
6
6
  import { theme } from "@/theme/theme";
7
+ import { useITFlatAppearance } from "@/theme/theme-context";
7
8
 
8
9
  /**
9
10
  * Main application shell with sidebar, topbar, and content area.
@@ -27,6 +28,16 @@ import { theme } from "@/theme/theme";
27
28
  * >
28
29
  * <SettingsPage />
29
30
  * </ITLayout>
31
+ *
32
+ * @example
33
+ * // Full-height sidebar with the brand on top; the top bar covers only the content.
34
+ * <ITLayout
35
+ * sidebarFullHeight
36
+ * topBar={{ logo: <Logo />, logoText: "Acme", centerContent: <SearchBox /> }}
37
+ * sidebar={{ navigationItems, isCollapsed: false }}
38
+ * >
39
+ * <Dashboard />
40
+ * </ITLayout>
30
41
  */
31
42
  export default function ITLayout({
32
43
  topBar,
@@ -34,77 +45,123 @@ export default function ITLayout({
34
45
  children,
35
46
  className = "",
36
47
  contentClassName = "",
48
+ sidebarFullHeight = false,
37
49
  }: ITLayoutProps) {
38
50
  const [internalCollapsed, setInternalCollapsed] = useState(true);
39
51
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
52
+ const flat = useITFlatAppearance();
40
53
 
41
54
  const isControlled = sidebar.isCollapsed !== undefined;
42
55
  const desktopCollapsed = isControlled ? sidebar.isCollapsed : internalCollapsed;
43
56
  const handleToggleCollapse = isControlled ? (sidebar.onToggleCollapse ?? (() => {})) : () => setInternalCollapsed(v => !v);
44
57
 
45
58
  const layoutTokens = theme.layout;
59
+ // Must match ITSidebar's own at-rest width (88px rail / 280px; 72px / 248px when flat).
60
+ const reservedWidth = desktopCollapsed
61
+ ? flat ? "w-[72px]" : "w-[88px]"
62
+ : flat ? "w-[248px]" : "w-[280px]";
63
+ // The sidebar collapses by button instead of hover: the top bar's menu
64
+ // button toggles the rail on desktop and still opens the drawer on mobile.
65
+ const toggleByButton = sidebar.expandOnHover === false;
66
+ const handleMenuButton = () => {
67
+ if (toggleByButton && window.matchMedia("(min-width: 1024px)").matches) handleToggleCollapse();
68
+ else setMobileSidebarOpen(v => !v);
69
+ };
70
+ // With a full-height sidebar the brand moves from the top bar to the sidebar.
71
+ const brand = sidebarFullHeight
72
+ ? (sidebar.brand ?? { logo: topBar.logo, text: topBar.logoText })
73
+ : sidebar.brand;
46
74
 
47
- return (
48
- <div className={`flex flex-col h-screen overflow-hidden w-full ${className}`}>
49
- <ITTopBar
50
- {...topBar}
51
- showMobileMenuButton
52
- onToggleMobileMenu={() => setMobileSidebarOpen(v => !v)}
53
- />
75
+ const topBarNode = (
76
+ <ITTopBar
77
+ {...topBar}
78
+ hideBrandOnDesktop={sidebarFullHeight || topBar.hideBrandOnDesktop}
79
+ showMobileMenuButton
80
+ showDesktopMenuButton={toggleByButton || topBar.showDesktopMenuButton}
81
+ onToggleMobileMenu={handleMenuButton}
82
+ />
83
+ );
54
84
 
85
+ const desktopSidebar = (
86
+ // Full-height: the sidebar sits beside the top bar, so it needs to win the
87
+ // stacking order when the collapsed rail expands over the content on hover.
88
+ <div className={`hidden lg:block relative h-full ${sidebarFullHeight ? "z-50" : "z-40"}`}>
89
+ {/* Reserves the sidebar's at-rest width. When the sidebar is collapsed
90
+ (default), this is the 88px rail and hover expands over the content.
91
+ When controlled-expanded (`sidebar.isCollapsed === false`), it
92
+ reserves the full width so the sidebar never overlays content. */}
93
+ <div className={`${reservedWidth} h-full flex-shrink-0`} />
94
+ <div className="absolute top-0 left-0 h-full">
95
+ <ITSidebar
96
+ {...sidebar}
97
+ brand={brand}
98
+ isCollapsed={desktopCollapsed}
99
+ onToggleCollapse={handleToggleCollapse}
100
+ visibleOnMobile={false}
101
+ className={`h-full ${flat ? "" : "drop-shadow-2xl"} transition-all duration-400 ease-[cubic-bezier(0.2,0,0,1)] flex-shrink-0`}
102
+ />
103
+ </div>
104
+ </div>
105
+ );
106
+
107
+ const mobileSidebar = mobileSidebarOpen && (
108
+ <div
109
+ className="lg:hidden fixed inset-0 z-[40] transition-opacity duration-300 backdrop-blur-sm bg-black/40"
110
+ onClick={() => setMobileSidebarOpen(false)}
111
+ >
55
112
  <div
56
- className="flex flex-1 overflow-hidden relative"
113
+ className="h-full w-fit flex transform transition-transform duration-300 ease-[cubic-bezier(0.2,0,0,1)]"
114
+ onClick={(e) => e.stopPropagation()}
115
+ >
116
+ <ITSidebar
117
+ {...sidebar}
118
+ brand={brand}
119
+ isCollapsed={false}
120
+ visibleOnMobile={true}
121
+ className="h-full shadow-2xl relative z-[60]"
122
+ onToggleCollapse={() => setMobileSidebarOpen(false)}
123
+ onItemClick={() => setMobileSidebarOpen(false)}
124
+ onSubItemClick={() => setMobileSidebarOpen(false)}
125
+ />
126
+ </div>
127
+ </div>
128
+ );
129
+
130
+ const main = (
131
+ <main className="flex-1 overflow-y-auto w-full custom-scrollbar relative z-0">
132
+ <div className={clsx("mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-6 h-full", contentClassName)}>
133
+ {children}
134
+ </div>
135
+ </main>
136
+ );
137
+
138
+ if (sidebarFullHeight) {
139
+ return (
140
+ <div
141
+ className={`flex h-screen overflow-hidden w-full relative ${className}`}
57
142
  style={{ backgroundColor: layoutTokens.backgroundColor }}
58
143
  >
59
- {/* DESKTOP SIDEBAR */}
60
- <div className="hidden lg:block relative z-40 h-full">
61
- {/* Reserves the sidebar's at-rest width. When the sidebar is collapsed
62
- (default), this is the 88px rail and hover expands over the content.
63
- When controlled-expanded (`sidebar.isCollapsed === false`), it
64
- reserves the full 280px so the sidebar never overlays content. */}
65
- <div
66
- className={`${desktopCollapsed ? "w-[88px]" : "w-[280px]"} h-full flex-shrink-0`}
67
- />
68
- <div className="absolute top-0 left-0 h-full">
69
- <ITSidebar
70
- {...sidebar}
71
- isCollapsed={desktopCollapsed}
72
- onToggleCollapse={handleToggleCollapse}
73
- visibleOnMobile={false}
74
- className="h-full drop-shadow-2xl transition-all duration-400 ease-[cubic-bezier(0.2,0,0,1)] flex-shrink-0"
75
- />
76
- </div>
144
+ {desktopSidebar}
145
+ <div className="flex flex-col flex-1 min-w-0 overflow-hidden">
146
+ {topBarNode}
147
+ {main}
77
148
  </div>
149
+ {mobileSidebar}
150
+ </div>
151
+ );
152
+ }
78
153
 
79
- {/* MOBILE SIDEBAR PANE */}
80
- {mobileSidebarOpen && (
81
- <div
82
- className="lg:hidden fixed inset-0 z-[40] transition-opacity duration-300 backdrop-blur-sm bg-black/40"
83
- onClick={() => setMobileSidebarOpen(false)}
84
- >
85
- <div
86
- className="h-full w-fit flex transform transition-transform duration-300 ease-[cubic-bezier(0.2,0,0,1)]"
87
- onClick={(e) => e.stopPropagation()}
88
- >
89
- <ITSidebar
90
- {...sidebar}
91
- isCollapsed={false}
92
- visibleOnMobile={true}
93
- className="h-full shadow-2xl relative z-[60]"
94
- onToggleCollapse={() => setMobileSidebarOpen(false)}
95
- onItemClick={() => setMobileSidebarOpen(false)}
96
- onSubItemClick={() => setMobileSidebarOpen(false)}
97
- />
98
- </div>
99
- </div>
100
- )}
154
+ return (
155
+ <div className={`flex flex-col h-screen overflow-hidden w-full ${className}`}>
156
+ {topBarNode}
101
157
 
102
- {/* MAIN CONTENT AREA */}
103
- <main className="flex-1 overflow-y-auto w-full custom-scrollbar relative z-0">
104
- <div className={clsx("mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-6 h-full", contentClassName)}>
105
- {children}
106
- </div>
107
- </main>
158
+ <div
159
+ className="flex flex-1 overflow-hidden relative"
160
+ style={{ backgroundColor: layoutTokens.backgroundColor }}
161
+ >
162
+ {desktopSidebar}
163
+ {mobileSidebar}
164
+ {main}
108
165
  </div>
109
166
  </div>
110
167
  );
@@ -17,6 +17,7 @@ Root theme context that injects CSS custom properties into the entire component
17
17
  | `density` | `number` | `1` | Global density/compactness factor: values below `1` shrink base sizes (fonts, paddings, gaps), above `1` grow them. Applies by scaling the root font-size so every `rem`-based token compacts together while the layout stays fluid. Clamped to `0.5`–`1.5`. |
18
18
  | `radius` | `number` | — | Global border radius in **px** applied to the whole rounding scale. `2` ⇒ 2px corners (near-square), `8` ⇒ 8px, `0` ⇒ square. No override when omitted (Tailwind defaults). |
19
19
  | `shadow` | `number` | `2` | Global shadow strength level: `0` ⇒ none, `1` ⇒ subtle, `2` ⇒ default current look, `3` ⇒ pronounced. No override when omitted (keeps defaults). |
20
+ | `appearance` | `"soft" \| "flat"` | `"soft"` | Visual language. `"flat"` switches `ITCard`, `ITSidebar`, `ITTopBar` and `ITPageHeader` to flat surfaces (hairline borders, no shadows, no header icon tile) and sets `data-it-appearance` on `<html>`. Read it in your own components with `useITFlatAppearance()`. |
20
21
 
21
22
  ### ITThemePalette Shape
22
23
 
@@ -1,3 +1,7 @@
1
+ import type { ITThemeAppearance } from "@/theme/theme.types";
2
+
3
+ export type { ITThemeAppearance };
4
+
1
5
  export interface ITThemePalette {
2
6
  /** Main brand accent color (hex). Drives primary buttons, active nav items, focus rings, links, and the `--color-primary` CSS variable. @default "#06b6d4" */
3
7
  primary: string;
@@ -54,4 +58,6 @@ export interface ITThemeProviderProps {
54
58
  radius?: number;
55
59
  /** Global shadow strength level. `0` ⇒ no shadows, `1` ⇒ subtle, `2` ⇒ default current look, `3` ⇒ pronounced. Shadows are injected as `--shadow-*`/`.shadow-*` overrides so cards, dropdowns and dialogs follow the level. @default 2 (keeps current default when omitted) */
56
60
  shadow?: number;
61
+ /** Visual language of the whole system. `"soft"` keeps the current look (floating cards, tinted shadows, glass sidebar/top bar). `"flat"` switches the app shell and surfaces to flat ones: hairline borders instead of shadows on `ITCard`, a plain `ITSidebar` (no glow, no accent bar, narrower), a plain `ITTopBar` (no shadow, initials avatar) and a compact `ITPageHeader` without the icon tile. It also sets `data-it-appearance` on `<html>` so app CSS can follow it. Combine it with `shadow` and `radius` to finish the look. @default "soft" */
62
+ appearance?: ITThemeAppearance;
57
63
  }
@@ -73,7 +73,7 @@ import { DEFAULT_PALETTE, PRESETS } from "./themePresets";
73
73
  // Contexto y hooks (useITTheme / useITThemeSafe) viven en @/theme/theme-context.
74
74
  // Re-exportados desde aquí para mantener compatibilidad de la API pública.
75
75
  // eslint-disable-next-line react-refresh/only-export-components
76
- export { useITTheme, useITThemeSafe } from "@/theme/theme-context";
76
+ export { useITTheme, useITThemeSafe, useITFlatAppearance } from "@/theme/theme-context";
77
77
 
78
78
  const getNestedValue = (obj: any, path: string) => {
79
79
  return path.split(".").reduce((acc, part) => acc && acc[part], obj);
@@ -143,6 +143,7 @@ export default function ITThemeProvider({
143
143
  density = 1,
144
144
  radius,
145
145
  shadow,
146
+ appearance = "soft",
146
147
  }: ITThemeProviderProps) {
147
148
  const [palette, setPaletteState] = useState<ITThemePalette>(() => {
148
149
  const basePalette = {
@@ -276,6 +277,14 @@ export default function ITThemeProvider({
276
277
  };
277
278
  }, [density]);
278
279
 
280
+ // Lenguaje visual: se publica en <html> para que el CSS de la app pueda
281
+ // seguirlo; los componentes lo leen del contexto (useITFlatAppearance).
282
+ useLayoutEffect(() => {
283
+ const root = document.documentElement;
284
+ root.setAttribute("data-it-appearance", appearance);
285
+ return () => root.removeAttribute("data-it-appearance");
286
+ }, [appearance]);
287
+
279
288
  // Overrides globales de radius y sombras.
280
289
  // Radius: en Tailwind v4 cada `rounded-*` resuelve a var(--radius-*), así que
281
290
  // setear los vars en :root (en px) cuadra/redondea todo el sistema sin tocar
@@ -1611,6 +1620,7 @@ export default function ITThemeProvider({
1611
1620
  resolvedTheme,
1612
1621
  darkModeMode,
1613
1622
  setDarkModeMode,
1623
+ appearance,
1614
1624
  }}
1615
1625
  >
1616
1626
  {children}
package/src/index.ts CHANGED
@@ -103,7 +103,7 @@ import type { ITSearchSelectProps } from "./components/molecules/search-select/s
103
103
  import type { ITSelectProps } from "./components/molecules/select/select.props";
104
104
  import type { ITSearchTableProps } from "./components/organisms/searchTable/searchTable.props";
105
105
  import type { ITSegmentedControlProps } from "./components/atoms/segmented-control/segmented-control.props";
106
- import type { ITSidebarProps, ITSidebarNotification, ITNavigationItem, ITNavigationSubItem, ITNavigationSubItemEntry, ITNavigationSubItemGroup } from "./components/organisms/sidebar/sidebar.props";
106
+ import type { ITSidebarProps, ITSidebarNotification, ITSidebarBrand, ITNavigationItem, ITNavigationSubItem, ITNavigationSubItemEntry, ITNavigationSubItemGroup } from "./components/organisms/sidebar/sidebar.props";
107
107
  import type { ITSkeletonProps } from "./components/atoms/skeleton/skeleton.props";
108
108
  import type { ITSlideToggleProps } from "./components/atoms/slide/slide.props";
109
109
  import type { ITSliderProps } from "./components/atoms/slider/slider.props";
@@ -127,8 +127,8 @@ import ITLoader from "./components/atoms/loader/loader";
127
127
  import type { LoaderProps as ITLoaderProps } from "./components/atoms/loader/loader.props";
128
128
  import ITStepper from "./components/molecules/stepper/stepper";
129
129
  import type { ITStepperProps } from "./components/molecules/stepper/stepper.props";
130
- import ITThemeProvider, { useITTheme, useITThemeSafe } from "./components/theme-provider/themeProvider";
131
- import type { ITThemeProviderProps, ITThemePalette } from "./components/theme-provider/themeProvider.props";
130
+ import ITThemeProvider, { useITTheme, useITThemeSafe, useITFlatAppearance } from "./components/theme-provider/themeProvider";
131
+ import type { ITThemeProviderProps, ITThemePalette, ITThemeAppearance } from "./components/theme-provider/themeProvider.props";
132
132
  import ITTimePicker from "./components/molecules/time-picker/timePicker";
133
133
  import type { ITTimePickerProps } from "./components/molecules/time-picker/timePicker.props";
134
134
  import type { ITThemeConfig } from "./theme/theme.types";
@@ -153,7 +153,7 @@ export {
153
153
  ITSearchTable,
154
154
  ITSegmentedControl, ITSidebar, ITSkeleton, ITSlideToggle, ITSlider, ITStack, ITStatCard, ITStepper,
155
155
  ITTable, ITTabs, ITText, ITTextarea, ITTripleFilter, ITThemeProvider,
156
- useITTheme, useITThemeSafe, ITTimePicker, ITToast, ITWysiwyg,
156
+ useITTheme, useITThemeSafe, useITFlatAppearance, ITTimePicker, ITToast, ITWysiwyg,
157
157
  UploadStatus, FileTypeEnum,
158
158
  getContrastTextColor, isLightColor, resolveCssColor
159
159
  };
@@ -176,7 +176,7 @@ export {
176
176
  ITPageProps, ITPageHeaderProps,
177
177
  ITNavigationItem, ITNavigationSubItem,
178
178
  ITNavigationSubItemEntry, ITNavigationSubItemGroup,
179
- ITSidebarNotification,
179
+ ITSidebarNotification, ITSidebarBrand,
180
180
  ITPaginationProps, ITPopoverProps, ITProgressProps,
181
181
  ITRadioGroupProps, ITRadioOption, ITSelectProps, ITSearchSelectProps,
182
182
  ITSearchTableProps,
@@ -184,7 +184,7 @@ export {
184
184
  ITStackProps, ITStatCardProps, ITStepperProps,
185
185
  ITTableProps, ITTabsProps, ITTabItem, ITTextProps, ITTextareaProps,
186
186
  TableDensity,
187
- ITTripleFilterProps, ITTripleFilterOption, ITThemeConfig, ITThemeProviderProps, ITThemePalette,
187
+ ITTripleFilterProps, ITTripleFilterOption, ITThemeConfig, ITThemeProviderProps, ITThemePalette, ITThemeAppearance,
188
188
  ITTimePickerProps, ITToastProps,
189
189
  ITWysiwygProps,
190
190
  UseTableStateOptions, UseTableStateResult
@@ -1,5 +1,5 @@
1
1
  import { createContext, useContext } from "react";
2
- import type { ITThemePalette } from "./theme.types";
2
+ import type { ITThemeAppearance, ITThemePalette } from "./theme.types";
3
3
 
4
4
  /**
5
5
  * Contexto de theming compartido por ITThemeProvider y componentes que lo
@@ -16,6 +16,8 @@ export interface ITThemeContextType {
16
16
  resolvedTheme: "light" | "dark";
17
17
  darkModeMode: "light" | "dark" | "system";
18
18
  setDarkModeMode: (mode: "light" | "dark" | "system") => void;
19
+ /** Visual language chosen on the provider (`"soft"` unless `appearance` says otherwise). */
20
+ appearance: ITThemeAppearance;
19
21
  }
20
22
 
21
23
  export const ITThemeContext = createContext<ITThemeContextType | undefined>(undefined);
@@ -34,4 +36,12 @@ export const useITTheme = () => {
34
36
  */
35
37
  export const useITThemeSafe = (): ITThemeContextType | undefined => {
36
38
  return useContext(ITThemeContext);
39
+ };
40
+
41
+ /**
42
+ * `true` cuando el provider pide el lenguaje visual plano (`appearance="flat"`).
43
+ * Fuera de un ITThemeProvider devuelve `false` (aspecto por defecto).
44
+ */
45
+ export const useITFlatAppearance = (): boolean => {
46
+ return useContext(ITThemeContext)?.appearance === "flat";
37
47
  };
@@ -12,6 +12,13 @@ export type ColorScale = {
12
12
  950?: string;
13
13
  };
14
14
 
15
+ /**
16
+ * Visual language of the whole system.
17
+ * - `"soft"`: soft depth — floating cards, tinted shadows, glass sidebar and top bar (default).
18
+ * - `"flat"`: flat surfaces — hairline borders instead of shadows, plain sidebar and top bar, compact page headers.
19
+ */
20
+ export type ITThemeAppearance = "soft" | "flat";
21
+
15
22
  export interface ITThemePalette {
16
23
  /** Main brand accent color (hex). Drives primary buttons, active nav items, focus rings, links, and the `--color-primary` CSS variable. @default "#06b6d4" */
17
24
  primary: string;