From c2cdd2c97d391be88836e6da416246c5a67fa1a1 Mon Sep 17 00:00:00 2001 From: autocommit Date: Mon, 20 Apr 2026 01:10:56 -0700 Subject: [PATCH] chore: initial package split from monorepo Package: @lilith/admin-shell Split from: lilith/ui.git or lilith/build.git Publish workflow: calls lilith/workflows/.forgejo/workflows/publish-npm.yml@main --- .forgejo/workflows/publish.yml | 10 ++ dist/AdminShell.d.ts | 24 ++++ dist/AdminShell.d.ts.map | 1 + dist/AdminShell.js | 38 +++++ dist/components/Sidebar.d.ts | 9 ++ dist/components/Sidebar.d.ts.map | 1 + dist/components/Sidebar.js | 106 ++++++++++++++ dist/index.d.ts | 7 + dist/index.d.ts.map | 1 + dist/index.js | 12 ++ dist/types.d.ts | 61 ++++++++ dist/types.d.ts.map | 1 + dist/types.js | 1 + node_modules/.bin/vite | 1 + node_modules/@lilith/ui-router | 1 + node_modules/@lilith/ui-styled-components | 1 + node_modules/@lilith/ui-theme | 1 + node_modules/@types/react | 1 + node_modules/@types/react-dom | 1 + node_modules/@vitejs/plugin-react | 1 + node_modules/react | 1 + node_modules/react-dom | 1 + node_modules/react-router-dom | 1 + node_modules/styled-components | 1 + node_modules/vite | 1 + package.json | 50 +++++++ package.json.tmp | 0 src/AdminShell.tsx | 62 +++++++++ src/components/Sidebar.tsx | 162 ++++++++++++++++++++++ src/index.ts | 30 ++++ src/types.ts | 64 +++++++++ tsconfig.json | 19 +++ 32 files changed, 671 insertions(+) create mode 100644 .forgejo/workflows/publish.yml create mode 100644 dist/AdminShell.d.ts create mode 100644 dist/AdminShell.d.ts.map create mode 100644 dist/AdminShell.js create mode 100644 dist/components/Sidebar.d.ts create mode 100644 dist/components/Sidebar.d.ts.map create mode 100644 dist/components/Sidebar.js create mode 100644 dist/index.d.ts create mode 100644 dist/index.d.ts.map create mode 100644 dist/index.js create mode 100644 dist/types.d.ts create mode 100644 dist/types.d.ts.map create mode 100644 dist/types.js create mode 120000 node_modules/.bin/vite create mode 120000 node_modules/@lilith/ui-router create mode 120000 node_modules/@lilith/ui-styled-components create mode 120000 node_modules/@lilith/ui-theme create mode 120000 node_modules/@types/react create mode 120000 node_modules/@types/react-dom create mode 120000 node_modules/@vitejs/plugin-react create mode 120000 node_modules/react create mode 120000 node_modules/react-dom create mode 120000 node_modules/react-router-dom create mode 120000 node_modules/styled-components create mode 120000 node_modules/vite create mode 100644 package.json create mode 100644 package.json.tmp create mode 100644 src/AdminShell.tsx create mode 100644 src/components/Sidebar.tsx create mode 100644 src/index.ts create mode 100644 src/types.ts create mode 100644 tsconfig.json diff --git a/.forgejo/workflows/publish.yml b/.forgejo/workflows/publish.yml new file mode 100644 index 0000000..d9d5cdd --- /dev/null +++ b/.forgejo/workflows/publish.yml @@ -0,0 +1,10 @@ +name: Publish +on: + push: + branches: [main, master] + workflow_dispatch: +jobs: + publish: + uses: lilith/workflows/.forgejo/workflows/publish-npm.yml@main + secrets: inherit + diff --git a/dist/AdminShell.d.ts b/dist/AdminShell.d.ts new file mode 100644 index 0000000..9ab8d3c --- /dev/null +++ b/dist/AdminShell.d.ts @@ -0,0 +1,24 @@ +import type { AdminShellProps } from './types'; +/** + * AdminShell provides a consistent layout for admin dashboards. + * + * Features: + * - Responsive sidebar with navigation sections + * - Configurable logo with optional badge + * - Theme-aware styling with accent color support + * - Scrollable main content area + * + * @example + * ```tsx + * + * ... + * + * ``` + */ +export declare function AdminShell({ logo, navigation, footerText, accentColor, children, }: AdminShellProps): import("react/jsx-runtime").JSX.Element; +//# sourceMappingURL=AdminShell.d.ts.map \ No newline at end of file diff --git a/dist/AdminShell.d.ts.map b/dist/AdminShell.d.ts.map new file mode 100644 index 0000000..5779bc2 --- /dev/null +++ b/dist/AdminShell.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"AdminShell.d.ts","sourceRoot":"","sources":["../src/AdminShell.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAmB/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,UAAU,EACV,UAAU,EACV,WAAuB,EACvB,QAAQ,GACT,EAAE,eAAe,2CAYjB"} \ No newline at end of file diff --git a/dist/AdminShell.js b/dist/AdminShell.js new file mode 100644 index 0000000..b2d6d51 --- /dev/null +++ b/dist/AdminShell.js @@ -0,0 +1,38 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { styled } from '@lilith/ui-styled-components'; +import { Sidebar } from './components/Sidebar'; +const AppContainer = styled.div ` + height: 100vh; + display: flex; + overflow: hidden; +`; +const MainContent = styled.main ` + flex: 1; + padding: ${({ theme }) => theme.spacing.xl}; + overflow-y: auto; + min-width: 0; +`; +/** + * AdminShell provides a consistent layout for admin dashboards. + * + * Features: + * - Responsive sidebar with navigation sections + * - Configurable logo with optional badge + * - Theme-aware styling with accent color support + * - Scrollable main content area + * + * @example + * ```tsx + * + * ... + * + * ``` + */ +export function AdminShell({ logo, navigation, footerText, accentColor = 'primary', children, }) { + return (_jsxs(AppContainer, { children: [_jsx(Sidebar, { logo: logo, navigation: navigation, footerText: footerText, accentColor: accentColor }), _jsx(MainContent, { children: children })] })); +} diff --git a/dist/components/Sidebar.d.ts b/dist/components/Sidebar.d.ts new file mode 100644 index 0000000..bf74ce8 --- /dev/null +++ b/dist/components/Sidebar.d.ts @@ -0,0 +1,9 @@ +import type { NavSection, LogoConfig } from '../types'; +export interface SidebarProps { + logo: LogoConfig; + navigation: NavSection[]; + footerText?: string; + accentColor?: 'primary' | 'accent'; +} +export declare function Sidebar({ logo, navigation, footerText, accentColor }: SidebarProps): import("react/jsx-runtime").JSX.Element; +//# sourceMappingURL=Sidebar.d.ts.map \ No newline at end of file diff --git a/dist/components/Sidebar.d.ts.map b/dist/components/Sidebar.d.ts.map new file mode 100644 index 0000000..2a0f925 --- /dev/null +++ b/dist/components/Sidebar.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"Sidebar.d.ts","sourceRoot":"","sources":["../../src/components/Sidebar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAoHvD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CACpC;AAED,wBAAgB,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,WAAuB,EAAE,EAAE,YAAY,2CAoC9F"} \ No newline at end of file diff --git a/dist/components/Sidebar.js b/dist/components/Sidebar.js new file mode 100644 index 0000000..c9ad1b9 --- /dev/null +++ b/dist/components/Sidebar.js @@ -0,0 +1,106 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { NavLink } from '@lilith/ui-router'; +import { styled } from '@lilith/ui-styled-components'; +const SidebarContainer = styled.nav ` + width: 16rem; + background: ${({ theme }) => theme.colors.surface}; + border-right: 1px solid ${({ theme }) => theme.colors.border.default}; + padding: ${({ theme }) => theme.spacing.md}; + display: flex; + flex-direction: column; + overflow-y: auto; + flex-shrink: 0; +`; +const LogoArea = styled.div ` + margin-bottom: ${({ theme }) => theme.spacing.xl}; +`; +const LogoTitle = styled.h1 ` + font-size: ${({ theme }) => theme.typography.fontSize.xl}; + font-weight: ${({ theme }) => theme.typography.fontWeight.bold}; + color: ${({ theme, $accentColor }) => $accentColor === 'accent' ? theme.colors.accent.main : theme.colors.primary.main}; +`; +const LogoSubtitle = styled.p ` + font-size: ${({ theme }) => theme.typography.fontSize.sm}; + color: ${({ theme }) => theme.colors.text.muted}; +`; +const Badge = styled.span ` + display: inline-block; + padding: 2px 8px; + margin-left: 8px; + font-size: 10px; + font-weight: 600; + background: ${({ theme, $variant }) => { + switch ($variant) { + case 'warning': return `${theme.colors.warning.main}30`; + case 'info': return `${theme.colors.info.main}30`; + case 'success': return `${theme.colors.success.main}30`; + case 'error': return `${theme.colors.error.main}30`; + default: return `${theme.colors.warning.main}30`; + } +}}; + color: ${({ theme, $variant }) => { + switch ($variant) { + case 'warning': return theme.colors.warning.main; + case 'info': return theme.colors.info.main; + case 'success': return theme.colors.success.main; + case 'error': return theme.colors.error.main; + default: return theme.colors.warning.main; + } +}}; + border-radius: 4px; + text-transform: uppercase; +`; +const NavSectionsContainer = styled.div ` + flex: 1; + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing.lg}; +`; +const NavSectionContainer = styled.div ``; +const NavSectionTitle = styled.h2 ` + font-size: ${({ theme }) => theme.typography.fontSize.xs}; + font-weight: ${({ theme }) => theme.typography.fontWeight.semibold}; + color: ${({ theme }) => theme.colors.text.muted}; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: ${({ theme }) => theme.spacing.sm}; +`; +const NavList = styled.ul ` + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing.xs}; + list-style: none; + margin: 0; + padding: 0; +`; +const StyledNavLink = styled(NavLink) ` + display: block; + padding: ${({ theme }) => `${theme.spacing.sm} ${theme.spacing.md}`}; + border-radius: ${({ theme }) => theme.borderRadius.md}; + font-size: ${({ theme }) => theme.typography.fontSize.sm}; + transition: ${({ theme }) => theme.transitions.fast}; + text-decoration: none; + + &.active { + background: ${({ theme, $accentColor }) => $accentColor === 'accent' + ? `${theme.colors.accent.main}20` + : `${theme.colors.primary.main}20`}; + color: ${({ theme, $accentColor }) => $accentColor === 'accent' ? theme.colors.accent.main : theme.colors.primary.main}; + } + + &:not(.active) { + color: ${({ theme }) => theme.colors.text.muted}; + + &:hover { + background: ${({ theme }) => theme.colors.hover.surface}; + color: ${({ theme }) => theme.colors.text.primary}; + } + } +`; +const Footer = styled.div ` + font-size: ${({ theme }) => theme.typography.fontSize.xs}; + color: ${({ theme }) => theme.colors.text.muted}; +`; +export function Sidebar({ logo, navigation, footerText, accentColor = 'primary' }) { + return (_jsxs(SidebarContainer, { children: [_jsxs(LogoArea, { children: [_jsxs(LogoTitle, { "$accentColor": accentColor, children: [logo.title, logo.badge && _jsx(Badge, { "$variant": logo.badgeVariant, children: logo.badge })] }), _jsx(LogoSubtitle, { children: logo.subtitle })] }), _jsx(NavSectionsContainer, { children: navigation.map((section) => (_jsxs(NavSectionContainer, { children: [_jsx(NavSectionTitle, { children: section.title }), _jsx(NavList, { children: section.items.map((item) => (_jsx("li", { children: _jsx(StyledNavLink, { to: item.to, end: true, title: item.description, "$accentColor": accentColor, children: item.label }) }, item.to))) })] }, section.title))) }), footerText && _jsx(Footer, { children: footerText })] })); +} diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..e3a1a57 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,7 @@ +export { AdminShell } from './AdminShell'; +export { Sidebar } from './components/Sidebar'; +export type { AdminShellProps, NavItem, NavSection, LogoConfig, } from './types'; +export declare function getAllNavItems(sections: import('./types').NavSection[]): import('./types').NavItem[]; +export declare function findNavItemByPath(sections: import('./types').NavSection[], path: string): import('./types').NavItem | undefined; +export declare function findSectionByPath(sections: import('./types').NavSection[], path: string): import('./types').NavSection | undefined; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map new file mode 100644 index 0000000..81c4d75 --- /dev/null +++ b/dist/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,YAAY,EACV,eAAe,EACf,OAAO,EACP,UAAU,EACV,UAAU,GACX,MAAM,SAAS,CAAC;AAGjB,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,SAAS,EAAE,UAAU,EAAE,GAAG,OAAO,SAAS,EAAE,OAAO,EAAE,CAEpG;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,SAAS,EAAE,UAAU,EAAE,EACxC,IAAI,EAAE,MAAM,GACX,OAAO,SAAS,EAAE,OAAO,GAAG,SAAS,CAEvC;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,SAAS,EAAE,UAAU,EAAE,EACxC,IAAI,EAAE,MAAM,GACX,OAAO,SAAS,EAAE,UAAU,GAAG,SAAS,CAI1C"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..2a3bae2 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,12 @@ +export { AdminShell } from './AdminShell'; +export { Sidebar } from './components/Sidebar'; +// Navigation utilities +export function getAllNavItems(sections) { + return sections.flatMap((section) => section.items); +} +export function findNavItemByPath(sections, path) { + return getAllNavItems(sections).find((item) => item.to === path); +} +export function findSectionByPath(sections, path) { + return sections.find((section) => section.items.some((item) => item.to === path)); +} diff --git a/dist/types.d.ts b/dist/types.d.ts new file mode 100644 index 0000000..3589dd6 --- /dev/null +++ b/dist/types.d.ts @@ -0,0 +1,61 @@ +import type { ReactNode } from 'react'; +/** + * A single navigation item in the sidebar. + */ +export interface NavItem { + /** Route path */ + to: string; + /** Display label */ + label: string; + /** Optional description for tooltips/accessibility */ + description?: string; + /** Optional icon identifier (for future icon support) */ + icon?: string; +} +/** + * A section of navigation items in the sidebar. + */ +export interface NavSection { + /** Section title displayed in sidebar */ + title: string; + /** Navigation items in this section */ + items: NavItem[]; + /** Optional section-level metadata */ + meta?: { + /** Section description */ + description?: string; + /** Whether section should be collapsible (future feature) */ + collapsible?: boolean; + /** Default collapsed state (future feature) */ + defaultCollapsed?: boolean; + }; +} +/** + * Logo configuration for the admin shell. + */ +export interface LogoConfig { + /** Main title displayed in logo area */ + title: string; + /** Subtitle displayed below title */ + subtitle: string; + /** Optional badge text (e.g., "Dev Only") */ + badge?: string; + /** Badge variant for styling */ + badgeVariant?: 'warning' | 'info' | 'success' | 'error'; +} +/** + * Props for the AdminShell component. + */ +export interface AdminShellProps { + /** Logo configuration */ + logo: LogoConfig; + /** Navigation sections to render in sidebar */ + navigation: NavSection[]; + /** Optional footer text */ + footerText?: string; + /** Optional accent color for active states (defaults to theme primary) */ + accentColor?: 'primary' | 'accent'; + /** Main content to render */ + children: ReactNode; +} +//# sourceMappingURL=types.d.ts.map \ No newline at end of file diff --git a/dist/types.d.ts.map b/dist/types.d.ts.map new file mode 100644 index 0000000..b2d60f1 --- /dev/null +++ b/dist/types.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,iBAAiB;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,sCAAsC;IACtC,IAAI,CAAC,EAAE;QACL,0BAA0B;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,6DAA6D;QAC7D,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,+CAA+C;QAC/C,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,YAAY,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;CACzD;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,yBAAyB;IACzB,IAAI,EAAE,UAAU,CAAC;IACjB,+CAA+C;IAC/C,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IACnC,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAC;CACrB"} \ No newline at end of file diff --git a/dist/types.js b/dist/types.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/types.js @@ -0,0 +1 @@ +export {}; diff --git a/node_modules/.bin/vite b/node_modules/.bin/vite new file mode 120000 index 0000000..6d1e3be --- /dev/null +++ b/node_modules/.bin/vite @@ -0,0 +1 @@ +../vite/bin/vite.js \ No newline at end of file diff --git a/node_modules/@lilith/ui-router b/node_modules/@lilith/ui-router new file mode 120000 index 0000000..a9801d9 --- /dev/null +++ b/node_modules/@lilith/ui-router @@ -0,0 +1 @@ +../../../ui-router \ No newline at end of file diff --git a/node_modules/@lilith/ui-styled-components b/node_modules/@lilith/ui-styled-components new file mode 120000 index 0000000..83fa1a2 --- /dev/null +++ b/node_modules/@lilith/ui-styled-components @@ -0,0 +1 @@ +../../../ui-styled-components \ No newline at end of file diff --git a/node_modules/@lilith/ui-theme b/node_modules/@lilith/ui-theme new file mode 120000 index 0000000..8e536a2 --- /dev/null +++ b/node_modules/@lilith/ui-theme @@ -0,0 +1 @@ +../../../ui-theme \ No newline at end of file diff --git a/node_modules/@types/react b/node_modules/@types/react new file mode 120000 index 0000000..d29c301 --- /dev/null +++ b/node_modules/@types/react @@ -0,0 +1 @@ +../../../../node_modules/.bun/@types+react@19.2.14/node_modules/@types/react \ No newline at end of file diff --git a/node_modules/@types/react-dom b/node_modules/@types/react-dom new file mode 120000 index 0000000..f615321 --- /dev/null +++ b/node_modules/@types/react-dom @@ -0,0 +1 @@ +../../../../node_modules/.bun/@types+react-dom@19.2.3+273cdfb19a04c3e9/node_modules/@types/react-dom \ No newline at end of file diff --git a/node_modules/@vitejs/plugin-react b/node_modules/@vitejs/plugin-react new file mode 120000 index 0000000..07f7c95 --- /dev/null +++ b/node_modules/@vitejs/plugin-react @@ -0,0 +1 @@ +../../../../node_modules/.bun/@vitejs+plugin-react@4.7.0+97cf8fd0620ba951/node_modules/@vitejs/plugin-react \ No newline at end of file diff --git a/node_modules/react b/node_modules/react new file mode 120000 index 0000000..91a2b48 --- /dev/null +++ b/node_modules/react @@ -0,0 +1 @@ +../../../node_modules/.bun/react@19.2.5/node_modules/react \ No newline at end of file diff --git a/node_modules/react-dom b/node_modules/react-dom new file mode 120000 index 0000000..a0ee521 --- /dev/null +++ b/node_modules/react-dom @@ -0,0 +1 @@ +../../../node_modules/.bun/react-dom@19.2.5+3f10a4be4e334a9b/node_modules/react-dom \ No newline at end of file diff --git a/node_modules/react-router-dom b/node_modules/react-router-dom new file mode 120000 index 0000000..210c1b3 --- /dev/null +++ b/node_modules/react-router-dom @@ -0,0 +1 @@ +../../../node_modules/.bun/react-router-dom@7.14.1+21ccd8898788a04d/node_modules/react-router-dom \ No newline at end of file diff --git a/node_modules/styled-components b/node_modules/styled-components new file mode 120000 index 0000000..5ea5838 --- /dev/null +++ b/node_modules/styled-components @@ -0,0 +1 @@ +../../../node_modules/.bun/styled-components@6.4.0+21ccd8898788a04d/node_modules/styled-components \ No newline at end of file diff --git a/node_modules/vite b/node_modules/vite new file mode 120000 index 0000000..17998a8 --- /dev/null +++ b/node_modules/vite @@ -0,0 +1 @@ +../../../node_modules/.bun/vite@6.4.2+447ecf4401e85ef8/node_modules/vite \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..e135ec0 --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "@lilith/admin-shell", + "version": "1.0.3-dev.2", + "description": "Admin shell layout components for Lilith Platform admin dashboards", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "dev": "vite", + "build": "tsc --project tsconfig.json", + "typecheck": "tsc --noEmit", + "lint": "eslint src --fix", + "lint:check": "eslint src" + }, + "dependencies": { + "@lilith/ui-router": "^1.3.2", + "@lilith/ui-theme": "^1.4.0", + "@lilith/ui-styled-components": "^6.3.9" + }, + "devDependencies": { + "@types/react": "^19.2.8", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^4.7.0", + "vite": "^6.4.1" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "react-router-dom": "^6.0.0 || ^7.0.0", + "styled-components": "^6.0.0" + }, + "publishConfig": { + "registry": "http://forge.black.local/api/packages/lilith/npm/" + }, + "_": { + "registry": "forgejo", + "publish": true, + "build": true + } +} diff --git a/package.json.tmp b/package.json.tmp new file mode 100644 index 0000000..e69de29 diff --git a/src/AdminShell.tsx b/src/AdminShell.tsx new file mode 100644 index 0000000..2798ee5 --- /dev/null +++ b/src/AdminShell.tsx @@ -0,0 +1,62 @@ +import { styled } from '@lilith/ui-styled-components'; +import type { ThemeInterface } from '@lilith/ui-theme'; +import { Sidebar } from './components/Sidebar'; +import type { AdminShellProps } from './types'; + +interface ThemedProps { + theme: ThemeInterface; +} + +const AppContainer = styled.div` + height: 100vh; + display: flex; + overflow: hidden; +`; + +const MainContent = styled.main` + flex: 1; + padding: ${({ theme }) => theme.spacing.xl}; + overflow-y: auto; + min-width: 0; +`; + +/** + * AdminShell provides a consistent layout for admin dashboards. + * + * Features: + * - Responsive sidebar with navigation sections + * - Configurable logo with optional badge + * - Theme-aware styling with accent color support + * - Scrollable main content area + * + * @example + * ```tsx + * + * ... + * + * ``` + */ +export function AdminShell({ + logo, + navigation, + footerText, + accentColor = 'primary', + children, +}: AdminShellProps) { + return ( + + + {children} + + ); +} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx new file mode 100644 index 0000000..4b37035 --- /dev/null +++ b/src/components/Sidebar.tsx @@ -0,0 +1,162 @@ +import { NavLink } from '@lilith/ui-router'; +import { styled } from '@lilith/ui-styled-components'; +import type { NavSection, LogoConfig } from '../types'; + +const SidebarContainer = styled.nav` + width: 16rem; + background: ${({ theme }) => theme.colors.surface}; + border-right: 1px solid ${({ theme }) => theme.colors.border.default}; + padding: ${({ theme }) => theme.spacing.md}; + display: flex; + flex-direction: column; + overflow-y: auto; + flex-shrink: 0; +`; + +const LogoArea = styled.div` + margin-bottom: ${({ theme }) => theme.spacing.xl}; +`; + +const LogoTitle = styled.h1<{ $accentColor?: 'primary' | 'accent' }>` + font-size: ${({ theme }) => theme.typography.fontSize.xl}; + font-weight: ${({ theme }) => theme.typography.fontWeight.bold}; + color: ${({ theme, $accentColor }) => + $accentColor === 'accent' ? theme.colors.accent.main : theme.colors.primary.main}; +`; + +const LogoSubtitle = styled.p` + font-size: ${({ theme }) => theme.typography.fontSize.sm}; + color: ${({ theme }) => theme.colors.text.muted}; +`; + +const Badge = styled.span<{ $variant?: string }>` + display: inline-block; + padding: 2px 8px; + margin-left: 8px; + font-size: 10px; + font-weight: 600; + background: ${({ theme, $variant }) => { + switch ($variant) { + case 'warning': return `${theme.colors.warning.main}30`; + case 'info': return `${theme.colors.info.main}30`; + case 'success': return `${theme.colors.success.main}30`; + case 'error': return `${theme.colors.error.main}30`; + default: return `${theme.colors.warning.main}30`; + } + }}; + color: ${({ theme, $variant }) => { + switch ($variant) { + case 'warning': return theme.colors.warning.main; + case 'info': return theme.colors.info.main; + case 'success': return theme.colors.success.main; + case 'error': return theme.colors.error.main; + default: return theme.colors.warning.main; + } + }}; + border-radius: 4px; + text-transform: uppercase; +`; + +const NavSectionsContainer = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing.lg}; +`; + +const NavSectionContainer = styled.div``; + +const NavSectionTitle = styled.h2` + font-size: ${({ theme }) => theme.typography.fontSize.xs}; + font-weight: ${({ theme }) => theme.typography.fontWeight.semibold}; + color: ${({ theme }) => theme.colors.text.muted}; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: ${({ theme }) => theme.spacing.sm}; +`; + +const NavList = styled.ul` + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing.xs}; + list-style: none; + margin: 0; + padding: 0; +`; + +const StyledNavLink = styled(NavLink)<{ $accentColor?: 'primary' | 'accent' }>` + display: block; + padding: ${({ theme }) => `${theme.spacing.sm} ${theme.spacing.md}`}; + border-radius: ${({ theme }) => theme.borderRadius.md}; + font-size: ${({ theme }) => theme.typography.fontSize.sm}; + transition: ${({ theme }) => theme.transitions.fast}; + text-decoration: none; + + &.active { + background: ${({ theme, $accentColor }) => + $accentColor === 'accent' + ? `${theme.colors.accent.main}20` + : `${theme.colors.primary.main}20`}; + color: ${({ theme, $accentColor }) => + $accentColor === 'accent' ? theme.colors.accent.main : theme.colors.primary.main}; + } + + &:not(.active) { + color: ${({ theme }) => theme.colors.text.muted}; + + &:hover { + background: ${({ theme }) => theme.colors.hover.surface}; + color: ${({ theme }) => theme.colors.text.primary}; + } + } +`; + +const Footer = styled.div` + font-size: ${({ theme }) => theme.typography.fontSize.xs}; + color: ${({ theme }) => theme.colors.text.muted}; +`; + +export interface SidebarProps { + logo: LogoConfig; + navigation: NavSection[]; + footerText?: string; + accentColor?: 'primary' | 'accent'; +} + +export function Sidebar({ logo, navigation, footerText, accentColor = 'primary' }: SidebarProps) { + return ( + + + + {logo.title} + {logo.badge && {logo.badge}} + + {logo.subtitle} + + + + {navigation.map((section) => ( + + {section.title} + + {section.items.map((item) => ( +
  • + + {item.label} + +
  • + ))} +
    +
    + ))} +
    + + {footerText && } +
    + ); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..096f0b4 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,30 @@ +export { AdminShell } from './AdminShell'; +export { Sidebar } from './components/Sidebar'; + +export type { + AdminShellProps, + NavItem, + NavSection, + LogoConfig, +} from './types'; + +// Navigation utilities +export function getAllNavItems(sections: import('./types').NavSection[]): import('./types').NavItem[] { + return sections.flatMap((section) => section.items); +} + +export function findNavItemByPath( + sections: import('./types').NavSection[], + path: string +): import('./types').NavItem | undefined { + return getAllNavItems(sections).find((item) => item.to === path); +} + +export function findSectionByPath( + sections: import('./types').NavSection[], + path: string +): import('./types').NavSection | undefined { + return sections.find((section) => + section.items.some((item) => item.to === path) + ); +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..65da5bd --- /dev/null +++ b/src/types.ts @@ -0,0 +1,64 @@ +import type { ReactNode } from 'react'; + +/** + * A single navigation item in the sidebar. + */ +export interface NavItem { + /** Route path */ + to: string; + /** Display label */ + label: string; + /** Optional description for tooltips/accessibility */ + description?: string; + /** Optional icon identifier (for future icon support) */ + icon?: string; +} + +/** + * A section of navigation items in the sidebar. + */ +export interface NavSection { + /** Section title displayed in sidebar */ + title: string; + /** Navigation items in this section */ + items: NavItem[]; + /** Optional section-level metadata */ + meta?: { + /** Section description */ + description?: string; + /** Whether section should be collapsible (future feature) */ + collapsible?: boolean; + /** Default collapsed state (future feature) */ + defaultCollapsed?: boolean; + }; +} + +/** + * Logo configuration for the admin shell. + */ +export interface LogoConfig { + /** Main title displayed in logo area */ + title: string; + /** Subtitle displayed below title */ + subtitle: string; + /** Optional badge text (e.g., "Dev Only") */ + badge?: string; + /** Badge variant for styling */ + badgeVariant?: 'warning' | 'info' | 'success' | 'error'; +} + +/** + * Props for the AdminShell component. + */ +export interface AdminShellProps { + /** Logo configuration */ + logo: LogoConfig; + /** Navigation sections to render in sidebar */ + navigation: NavSection[]; + /** Optional footer text */ + footerText?: string; + /** Optional accent color for active states (defaults to theme primary) */ + accentColor?: 'primary' | 'accent'; + /** Main content to render */ + children: ReactNode; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2e2e7b8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "noEmit": false, + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}