@misael703/ui 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misael703/ui",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "description": "Generic React + TypeScript UI kit, optimized for Next.js. Tokens, accessible components, runtime-configurable theming via presets.",
5
5
  "author": "Misael Ocas <misael.ocas@gmail.com>",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/AppShell.tsx"],"names":["React","NavItemNode","cx","jsxs","Fragment","jsx","useLocale","ChevronRight","ChevronLeft","MenuIcon"],"mappings":"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","file":"chunk-7LCZ6ABE.js","sourcesContent":["'use client';\nimport * as React from 'react';\nimport { cx } from '../utils/cx';\nimport { ChevronLeft, ChevronRight, MenuIcon } from './Icons';\nimport { useLocale } from '../locale/LocaleProvider';\n\n// ---------- AppShell (Sidebar + Topbar + Content) -----------------------\n// Designed to drop into a Next.js app/layout.tsx as a Client Component shell.\n\nexport interface NavItem {\n id: string;\n label: React.ReactNode;\n icon?: React.ReactNode;\n href?: string;\n active?: boolean;\n badge?: React.ReactNode;\n onSelect?: () => void;\n children?: NavItem[];\n}\n\nexport interface NavSection {\n id?: string;\n label?: React.ReactNode;\n items: NavItem[];\n}\n\nexport type AppShellTheme = 'default' | 'brand';\n\nexport type AppShellHeaderLayout = 'side' | 'top';\n\nexport interface AppShellHeader {\n /** Left slot — typically a menu/hamburger trigger or back action. */\n left?: React.ReactNode;\n /** Center slot — typically the brand (Logo). Lands at the true viewport centre. */\n center?: React.ReactNode;\n /** Right slot — notifications, user avatar, utilities. */\n right?: React.ReactNode;\n}\n\nexport interface AppShellProps {\n brand?: React.ReactNode;\n brandCollapsed?: React.ReactNode;\n sections: NavSection[];\n topbar?: React.ReactNode;\n footer?: React.ReactNode;\n user?: React.ReactNode;\n defaultCollapsed?: boolean;\n collapsed?: boolean;\n onCollapsedChange?: (c: boolean) => void;\n children: React.ReactNode;\n className?: string;\n /**\n * Sidebar color theme:\n * - `default` (light): claro, mejor para apps data-heavy de uso prolongado.\n * - `brand`: sidebar azul de marca con texto blanco. Mayor brand recall.\n */\n theme?: AppShellTheme;\n /** Render-prop for navigation links so the host app can use Next.js Link, etc. */\n linkAs?: (item: NavItem, content: React.ReactNode, className: string) => React.ReactNode;\n /**\n * Where the chrome lives. Default `'side'` is the legacy layout. `'top'`\n * renders a full-width header above the body with three slots\n * (`header.left/center/right`); the centre slot lands at the **true\n * viewport centre** (1fr·auto·1fr column grid). The sidebar has no brand\n * block (brand goes in `header.center`) and `collapsed` hides the\n * sidebar entirely — no 72px rail. The topbar is **invariant** to the\n * collapse (only the sidebar changes width). `theme=\"brand\"` tints both\n * header and sidebar with the same brand colour (single knob). `brand`,\n * `brandCollapsed` and `topbar` are ignored when `'top'`.\n */\n headerLayout?: AppShellHeaderLayout;\n /** Slots for the top-layout header (only used when `headerLayout=\"top\"`). */\n header?: AppShellHeader;\n}\n\n// Recursive nav item, memoized so a single item's parent re-render doesn't\n// churn through every other item in the tree. Stability of `linkAs` and\n// `onCloseMobile` is the parent's responsibility (we stabilize\n// `onCloseMobile` via useCallback below; consumers should memoize `linkAs`\n// if they care about avoiding renders, but for typical Next.js Link usage\n// the inline arrow is rarely a hot path).\ninterface NavItemNodeProps {\n item: NavItem;\n depth: number;\n linkAs?: AppShellProps['linkAs'];\n onCloseMobile: () => void;\n}\n\nconst NavItemNode = React.memo(function NavItemNode({\n item, depth, linkAs, onCloseMobile,\n}: NavItemNodeProps) {\n const klass = cx('appshell__navitem', item.active && 'is-active', `appshell__navitem--depth-${depth}`);\n const inner = (\n <>\n {item.icon && <span className=\"appshell__navicon\" aria-hidden=\"true\">{item.icon}</span>}\n <span className=\"appshell__navlabel\">{item.label}</span>\n {item.badge && <span className=\"appshell__navbadge\">{item.badge}</span>}\n </>\n );\n const node = item.href && linkAs\n ? linkAs(item, inner, klass)\n : (\n <a\n href={item.href ?? '#'}\n className={klass}\n aria-current={item.active ? 'page' : undefined}\n onClick={(e) => {\n if (!item.href) e.preventDefault();\n item.onSelect?.();\n onCloseMobile();\n }}\n >\n {inner}\n </a>\n );\n return (\n <li>\n {node}\n {item.children && item.children.length > 0 && (\n <ul className=\"appshell__navchildren\">\n {item.children.map((c) => (\n <NavItemNode key={c.id} item={c} depth={depth + 1} linkAs={linkAs} onCloseMobile={onCloseMobile} />\n ))}\n </ul>\n )}\n </li>\n );\n});\n\nexport function AppShell({\n brand, brandCollapsed, sections, topbar, footer, user,\n defaultCollapsed = false, collapsed: ctrlCollapsed, onCollapsedChange,\n children, className, theme = 'default', linkAs,\n headerLayout = 'side', header,\n}: AppShellProps) {\n const [internalCollapsed, setInternalCollapsed] = React.useState(defaultCollapsed);\n const [mobileOpen, setMobileOpen] = React.useState(false);\n const t = useLocale();\n const collapsed = ctrlCollapsed ?? internalCollapsed;\n const setCollapsed = (v: boolean) => {\n if (ctrlCollapsed === undefined) setInternalCollapsed(v);\n onCollapsedChange?.(v);\n };\n const closeMobile = React.useCallback(() => setMobileOpen(false), []);\n\n // Top-header variant: full-width header above the body. Topbar is\n // invariant to `collapsed` (only the inner body's columns animate); brand\n // lives in `header.center` at the true viewport centre. Default\n // `headerLayout=\"side\"` falls through to the legacy JSX below\n // (byte-identical for existing consumers).\n if (headerLayout === 'top') {\n return (\n <div className={cx('appshell', `appshell--${theme}`, 'appshell--header-top', collapsed && 'is-collapsed', className)}>\n <header className=\"appshell__header\" role=\"banner\">\n <div className=\"appshell__header-left\">{header?.left}</div>\n <div className=\"appshell__header-center\">{header?.center}</div>\n <div className=\"appshell__header-right\">{header?.right}</div>\n </header>\n <div className=\"appshell__body\">\n <aside className=\"appshell__sidebar\" aria-label={t['appshell.mainNav']}>\n <nav className=\"appshell__nav\">\n {sections.map((s, i) => (\n <div key={s.id ?? i} className=\"appshell__navsection\">\n {s.label && <div className=\"appshell__navlabel-section\">{s.label}</div>}\n <ul>{s.items.map((it) => (\n <NavItemNode key={it.id} item={it} depth={0} linkAs={linkAs} onCloseMobile={closeMobile} />\n ))}</ul>\n </div>\n ))}\n </nav>\n {footer != null && <div className=\"appshell__sidebar-foot\">{footer}</div>}\n </aside>\n <main className=\"appshell__content\" role=\"main\">{children}</main>\n </div>\n </div>\n );\n }\n\n return (\n <div className={cx('appshell', `appshell--${theme}`, collapsed && 'is-collapsed', mobileOpen && 'is-mobile-open', className)}>\n <aside className=\"appshell__sidebar\" aria-label={t['appshell.mainNav']}>\n <div className=\"appshell__brand\">\n {collapsed ? (brandCollapsed ?? brand) : brand}\n </div>\n <nav className=\"appshell__nav\">\n {sections.map((s, i) => (\n <div key={s.id ?? i} className=\"appshell__navsection\">\n {s.label && <div className=\"appshell__navlabel-section\">{s.label}</div>}\n <ul>{s.items.map((it) => (\n <NavItemNode key={it.id} item={it} depth={0} linkAs={linkAs} onCloseMobile={closeMobile} />\n ))}</ul>\n </div>\n ))}\n </nav>\n <div className=\"appshell__sidebar-foot\">\n {footer}\n <button\n type=\"button\"\n className=\"appshell__collapse\"\n onClick={() => setCollapsed(!collapsed)}\n aria-expanded={!collapsed}\n aria-label={collapsed ? t['appshell.expandMenu'] : t['appshell.collapseMenu']}\n title={collapsed ? t['appshell.expand'] : t['appshell.collapse']}\n >\n {collapsed ? <ChevronRight size={14} /> : <ChevronLeft size={14} />}\n </button>\n </div>\n </aside>\n\n <div className=\"appshell__main\">\n <header className=\"appshell__topbar\">\n <button\n type=\"button\"\n className=\"appshell__hamburger\"\n aria-label={t['appshell.openMenu']}\n aria-expanded={mobileOpen}\n onClick={() => setMobileOpen((o) => !o)}\n ><MenuIcon size={20} /></button>\n <div className=\"appshell__topbar-content\">{topbar}</div>\n {user && <div className=\"appshell__topbar-user\">{user}</div>}\n </header>\n <main className=\"appshell__content\" role=\"main\">{children}</main>\n </div>\n\n {mobileOpen && (\n <div className=\"appshell__scrim\" onClick={() => setMobileOpen(false)} aria-hidden=\"true\" />\n )}\n </div>\n );\n}\n\n// ---------- PageHeader --------------------------------------------------\nexport interface PageHeaderProps {\n title: React.ReactNode;\n description?: React.ReactNode;\n breadcrumbs?: Array<{ label: React.ReactNode; href?: string }>;\n actions?: React.ReactNode;\n meta?: React.ReactNode;\n className?: string;\n}\n\nexport function PageHeader({ title, description, breadcrumbs, actions, meta, className }: PageHeaderProps) {\n const t = useLocale();\n return (\n <div className={cx('page-header', className)}>\n {breadcrumbs && breadcrumbs.length > 0 && (\n <nav className=\"page-header__crumbs\" aria-label={t['appshell.breadcrumb']}>\n <ol>\n {breadcrumbs.map((c, i) => (\n <li key={i}>\n {c.href ? <a href={c.href}>{c.label}</a> : <span aria-current=\"page\">{c.label}</span>}\n {i < breadcrumbs.length - 1 && <span className=\"page-header__crumb-sep\" aria-hidden=\"true\">/</span>}\n </li>\n ))}\n </ol>\n </nav>\n )}\n <div className=\"page-header__row\">\n <div className=\"page-header__title-wrap\">\n <h1 className=\"page-header__title\">{title}</h1>\n {description && <p className=\"page-header__desc\">{description}</p>}\n </div>\n {actions && <div className=\"page-header__actions\">{actions}</div>}\n </div>\n {meta && <div className=\"page-header__meta\">{meta}</div>}\n </div>\n );\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/AppShell.tsx"],"names":["NavItemNode"],"mappings":"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","file":"chunk-Y6AYAE4O.mjs","sourcesContent":["'use client';\nimport * as React from 'react';\nimport { cx } from '../utils/cx';\nimport { ChevronLeft, ChevronRight, MenuIcon } from './Icons';\nimport { useLocale } from '../locale/LocaleProvider';\n\n// ---------- AppShell (Sidebar + Topbar + Content) -----------------------\n// Designed to drop into a Next.js app/layout.tsx as a Client Component shell.\n\nexport interface NavItem {\n id: string;\n label: React.ReactNode;\n icon?: React.ReactNode;\n href?: string;\n active?: boolean;\n badge?: React.ReactNode;\n onSelect?: () => void;\n children?: NavItem[];\n}\n\nexport interface NavSection {\n id?: string;\n label?: React.ReactNode;\n items: NavItem[];\n}\n\nexport type AppShellTheme = 'default' | 'brand';\n\nexport type AppShellHeaderLayout = 'side' | 'top';\n\nexport interface AppShellHeader {\n /** Left slot — typically a menu/hamburger trigger or back action. */\n left?: React.ReactNode;\n /** Center slot — typically the brand (Logo). Lands at the true viewport centre. */\n center?: React.ReactNode;\n /** Right slot — notifications, user avatar, utilities. */\n right?: React.ReactNode;\n}\n\nexport interface AppShellProps {\n brand?: React.ReactNode;\n brandCollapsed?: React.ReactNode;\n sections: NavSection[];\n topbar?: React.ReactNode;\n footer?: React.ReactNode;\n user?: React.ReactNode;\n defaultCollapsed?: boolean;\n collapsed?: boolean;\n onCollapsedChange?: (c: boolean) => void;\n children: React.ReactNode;\n className?: string;\n /**\n * Sidebar color theme:\n * - `default` (light): claro, mejor para apps data-heavy de uso prolongado.\n * - `brand`: sidebar azul de marca con texto blanco. Mayor brand recall.\n */\n theme?: AppShellTheme;\n /** Render-prop for navigation links so the host app can use Next.js Link, etc. */\n linkAs?: (item: NavItem, content: React.ReactNode, className: string) => React.ReactNode;\n /**\n * Where the chrome lives. Default `'side'` is the legacy layout. `'top'`\n * renders a full-width header above the body with three slots\n * (`header.left/center/right`); the centre slot lands at the **true\n * viewport centre** (1fr·auto·1fr column grid). The sidebar has no brand\n * block (brand goes in `header.center`) and `collapsed` hides the\n * sidebar entirely — no 72px rail. The topbar is **invariant** to the\n * collapse (only the sidebar changes width). `theme=\"brand\"` tints both\n * header and sidebar with the same brand colour (single knob). `brand`,\n * `brandCollapsed` and `topbar` are ignored when `'top'`.\n */\n headerLayout?: AppShellHeaderLayout;\n /** Slots for the top-layout header (only used when `headerLayout=\"top\"`). */\n header?: AppShellHeader;\n}\n\n// Recursive nav item, memoized so a single item's parent re-render doesn't\n// churn through every other item in the tree. Stability of `linkAs` and\n// `onCloseMobile` is the parent's responsibility (we stabilize\n// `onCloseMobile` via useCallback below; consumers should memoize `linkAs`\n// if they care about avoiding renders, but for typical Next.js Link usage\n// the inline arrow is rarely a hot path).\ninterface NavItemNodeProps {\n item: NavItem;\n depth: number;\n linkAs?: AppShellProps['linkAs'];\n onCloseMobile: () => void;\n}\n\nconst NavItemNode = React.memo(function NavItemNode({\n item, depth, linkAs, onCloseMobile,\n}: NavItemNodeProps) {\n const klass = cx('appshell__navitem', item.active && 'is-active', `appshell__navitem--depth-${depth}`);\n const inner = (\n <>\n {item.icon && <span className=\"appshell__navicon\" aria-hidden=\"true\">{item.icon}</span>}\n <span className=\"appshell__navlabel\">{item.label}</span>\n {item.badge && <span className=\"appshell__navbadge\">{item.badge}</span>}\n </>\n );\n const node = item.href && linkAs\n ? linkAs(item, inner, klass)\n : (\n <a\n href={item.href ?? '#'}\n className={klass}\n aria-current={item.active ? 'page' : undefined}\n onClick={(e) => {\n if (!item.href) e.preventDefault();\n item.onSelect?.();\n onCloseMobile();\n }}\n >\n {inner}\n </a>\n );\n return (\n <li>\n {node}\n {item.children && item.children.length > 0 && (\n <ul className=\"appshell__navchildren\">\n {item.children.map((c) => (\n <NavItemNode key={c.id} item={c} depth={depth + 1} linkAs={linkAs} onCloseMobile={onCloseMobile} />\n ))}\n </ul>\n )}\n </li>\n );\n});\n\nexport function AppShell({\n brand, brandCollapsed, sections, topbar, footer, user,\n defaultCollapsed = false, collapsed: ctrlCollapsed, onCollapsedChange,\n children, className, theme = 'default', linkAs,\n headerLayout = 'side', header,\n}: AppShellProps) {\n const [internalCollapsed, setInternalCollapsed] = React.useState(defaultCollapsed);\n const [mobileOpen, setMobileOpen] = React.useState(false);\n const t = useLocale();\n const collapsed = ctrlCollapsed ?? internalCollapsed;\n const setCollapsed = (v: boolean) => {\n if (ctrlCollapsed === undefined) setInternalCollapsed(v);\n onCollapsedChange?.(v);\n };\n const closeMobile = React.useCallback(() => setMobileOpen(false), []);\n\n // Top-header variant: full-width header above the body. Topbar is\n // invariant to `collapsed` (only the inner body's columns animate); brand\n // lives in `header.center` at the true viewport centre. Default\n // `headerLayout=\"side\"` falls through to the legacy JSX below\n // (byte-identical for existing consumers).\n if (headerLayout === 'top') {\n return (\n <div className={cx('appshell', `appshell--${theme}`, 'appshell--header-top', collapsed && 'is-collapsed', className)}>\n <header className=\"appshell__header\" role=\"banner\">\n <div className=\"appshell__header-left\">{header?.left}</div>\n <div className=\"appshell__header-center\">{header?.center}</div>\n <div className=\"appshell__header-right\">{header?.right}</div>\n </header>\n <div className=\"appshell__body\">\n <aside className=\"appshell__sidebar\" aria-label={t['appshell.mainNav']}>\n <nav className=\"appshell__nav\">\n {sections.map((s, i) => (\n <div key={s.id ?? i} className=\"appshell__navsection\">\n {s.label && <div className=\"appshell__navlabel-section\">{s.label}</div>}\n <ul>{s.items.map((it) => (\n <NavItemNode key={it.id} item={it} depth={0} linkAs={linkAs} onCloseMobile={closeMobile} />\n ))}</ul>\n </div>\n ))}\n </nav>\n {footer != null && <div className=\"appshell__sidebar-foot\">{footer}</div>}\n </aside>\n <main className=\"appshell__content\" role=\"main\">{children}</main>\n </div>\n </div>\n );\n }\n\n return (\n <div className={cx('appshell', `appshell--${theme}`, collapsed && 'is-collapsed', mobileOpen && 'is-mobile-open', className)}>\n <aside className=\"appshell__sidebar\" aria-label={t['appshell.mainNav']}>\n <div className=\"appshell__brand\">\n {collapsed ? (brandCollapsed ?? brand) : brand}\n </div>\n <nav className=\"appshell__nav\">\n {sections.map((s, i) => (\n <div key={s.id ?? i} className=\"appshell__navsection\">\n {s.label && <div className=\"appshell__navlabel-section\">{s.label}</div>}\n <ul>{s.items.map((it) => (\n <NavItemNode key={it.id} item={it} depth={0} linkAs={linkAs} onCloseMobile={closeMobile} />\n ))}</ul>\n </div>\n ))}\n </nav>\n <div className=\"appshell__sidebar-foot\">\n {footer}\n <button\n type=\"button\"\n className=\"appshell__collapse\"\n onClick={() => setCollapsed(!collapsed)}\n aria-expanded={!collapsed}\n aria-label={collapsed ? t['appshell.expandMenu'] : t['appshell.collapseMenu']}\n title={collapsed ? t['appshell.expand'] : t['appshell.collapse']}\n >\n {collapsed ? <ChevronRight size={14} /> : <ChevronLeft size={14} />}\n </button>\n </div>\n </aside>\n\n <div className=\"appshell__main\">\n <header className=\"appshell__topbar\">\n <button\n type=\"button\"\n className=\"appshell__hamburger\"\n aria-label={t['appshell.openMenu']}\n aria-expanded={mobileOpen}\n onClick={() => setMobileOpen((o) => !o)}\n ><MenuIcon size={20} /></button>\n <div className=\"appshell__topbar-content\">{topbar}</div>\n {user && <div className=\"appshell__topbar-user\">{user}</div>}\n </header>\n <main className=\"appshell__content\" role=\"main\">{children}</main>\n </div>\n\n {mobileOpen && (\n <div className=\"appshell__scrim\" onClick={() => setMobileOpen(false)} aria-hidden=\"true\" />\n )}\n </div>\n );\n}\n\n// ---------- PageHeader --------------------------------------------------\nexport interface PageHeaderProps {\n title: React.ReactNode;\n description?: React.ReactNode;\n breadcrumbs?: Array<{ label: React.ReactNode; href?: string }>;\n actions?: React.ReactNode;\n meta?: React.ReactNode;\n className?: string;\n}\n\nexport function PageHeader({ title, description, breadcrumbs, actions, meta, className }: PageHeaderProps) {\n const t = useLocale();\n return (\n <div className={cx('page-header', className)}>\n {breadcrumbs && breadcrumbs.length > 0 && (\n <nav className=\"page-header__crumbs\" aria-label={t['appshell.breadcrumb']}>\n <ol>\n {breadcrumbs.map((c, i) => (\n <li key={i}>\n {c.href ? <a href={c.href}>{c.label}</a> : <span aria-current=\"page\">{c.label}</span>}\n {i < breadcrumbs.length - 1 && <span className=\"page-header__crumb-sep\" aria-hidden=\"true\">/</span>}\n </li>\n ))}\n </ol>\n </nav>\n )}\n <div className=\"page-header__row\">\n <div className=\"page-header__title-wrap\">\n <h1 className=\"page-header__title\">{title}</h1>\n {description && <p className=\"page-header__desc\">{description}</p>}\n </div>\n {actions && <div className=\"page-header__actions\">{actions}</div>}\n </div>\n {meta && <div className=\"page-header__meta\">{meta}</div>}\n </div>\n );\n}\n"]}