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
This commit is contained in:
commit
c2cdd2c97d
32 changed files with 671 additions and 0 deletions
10
.forgejo/workflows/publish.yml
Normal file
10
.forgejo/workflows/publish.yml
Normal file
|
|
@ -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
|
||||
|
||||
24
dist/AdminShell.d.ts
vendored
Normal file
24
dist/AdminShell.d.ts
vendored
Normal file
|
|
@ -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
|
||||
* <AdminShell
|
||||
* logo={{ title: 'Platform Admin', subtitle: 'Lilith Platform' }}
|
||||
* navigation={NAVIGATION_SECTIONS}
|
||||
* accentColor="primary"
|
||||
* footerText="Lilith Platform v0.1.0"
|
||||
* >
|
||||
* <Routes>...</Routes>
|
||||
* </AdminShell>
|
||||
* ```
|
||||
*/
|
||||
export declare function AdminShell({ logo, navigation, footerText, accentColor, children, }: AdminShellProps): import("react/jsx-runtime").JSX.Element;
|
||||
//# sourceMappingURL=AdminShell.d.ts.map
|
||||
1
dist/AdminShell.d.ts.map
vendored
Normal file
1
dist/AdminShell.d.ts.map
vendored
Normal file
|
|
@ -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"}
|
||||
38
dist/AdminShell.js
vendored
Normal file
38
dist/AdminShell.js
vendored
Normal file
|
|
@ -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
|
||||
* <AdminShell
|
||||
* logo={{ title: 'Platform Admin', subtitle: 'Lilith Platform' }}
|
||||
* navigation={NAVIGATION_SECTIONS}
|
||||
* accentColor="primary"
|
||||
* footerText="Lilith Platform v0.1.0"
|
||||
* >
|
||||
* <Routes>...</Routes>
|
||||
* </AdminShell>
|
||||
* ```
|
||||
*/
|
||||
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 })] }));
|
||||
}
|
||||
9
dist/components/Sidebar.d.ts
vendored
Normal file
9
dist/components/Sidebar.d.ts
vendored
Normal file
|
|
@ -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
|
||||
1
dist/components/Sidebar.d.ts.map
vendored
Normal file
1
dist/components/Sidebar.d.ts.map
vendored
Normal file
|
|
@ -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"}
|
||||
106
dist/components/Sidebar.js
vendored
Normal file
106
dist/components/Sidebar.js
vendored
Normal file
|
|
@ -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 })] }));
|
||||
}
|
||||
7
dist/index.d.ts
vendored
Normal file
7
dist/index.d.ts
vendored
Normal file
|
|
@ -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
|
||||
1
dist/index.d.ts.map
vendored
Normal file
1
dist/index.d.ts.map
vendored
Normal file
|
|
@ -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"}
|
||||
12
dist/index.js
vendored
Normal file
12
dist/index.js
vendored
Normal file
|
|
@ -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));
|
||||
}
|
||||
61
dist/types.d.ts
vendored
Normal file
61
dist/types.d.ts
vendored
Normal file
|
|
@ -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
|
||||
1
dist/types.d.ts.map
vendored
Normal file
1
dist/types.d.ts.map
vendored
Normal file
|
|
@ -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"}
|
||||
1
dist/types.js
vendored
Normal file
1
dist/types.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export {};
|
||||
1
node_modules/.bin/vite
generated
vendored
Symbolic link
1
node_modules/.bin/vite
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../vite/bin/vite.js
|
||||
1
node_modules/@lilith/ui-router
generated
vendored
Symbolic link
1
node_modules/@lilith/ui-router
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../ui-router
|
||||
1
node_modules/@lilith/ui-styled-components
generated
vendored
Symbolic link
1
node_modules/@lilith/ui-styled-components
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../ui-styled-components
|
||||
1
node_modules/@lilith/ui-theme
generated
vendored
Symbolic link
1
node_modules/@lilith/ui-theme
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../ui-theme
|
||||
1
node_modules/@types/react
generated
vendored
Symbolic link
1
node_modules/@types/react
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../node_modules/.bun/@types+react@19.2.14/node_modules/@types/react
|
||||
1
node_modules/@types/react-dom
generated
vendored
Symbolic link
1
node_modules/@types/react-dom
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../node_modules/.bun/@types+react-dom@19.2.3+273cdfb19a04c3e9/node_modules/@types/react-dom
|
||||
1
node_modules/@vitejs/plugin-react
generated
vendored
Symbolic link
1
node_modules/@vitejs/plugin-react
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../node_modules/.bun/@vitejs+plugin-react@4.7.0+97cf8fd0620ba951/node_modules/@vitejs/plugin-react
|
||||
1
node_modules/react
generated
vendored
Symbolic link
1
node_modules/react
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../node_modules/.bun/react@19.2.5/node_modules/react
|
||||
1
node_modules/react-dom
generated
vendored
Symbolic link
1
node_modules/react-dom
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../node_modules/.bun/react-dom@19.2.5+3f10a4be4e334a9b/node_modules/react-dom
|
||||
1
node_modules/react-router-dom
generated
vendored
Symbolic link
1
node_modules/react-router-dom
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../node_modules/.bun/react-router-dom@7.14.1+21ccd8898788a04d/node_modules/react-router-dom
|
||||
1
node_modules/styled-components
generated
vendored
Symbolic link
1
node_modules/styled-components
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../node_modules/.bun/styled-components@6.4.0+21ccd8898788a04d/node_modules/styled-components
|
||||
1
node_modules/vite
generated
vendored
Symbolic link
1
node_modules/vite
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../node_modules/.bun/vite@6.4.2+447ecf4401e85ef8/node_modules/vite
|
||||
50
package.json
Normal file
50
package.json
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
0
package.json.tmp
Normal file
0
package.json.tmp
Normal file
62
src/AdminShell.tsx
Normal file
62
src/AdminShell.tsx
Normal file
|
|
@ -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<ThemedProps>`
|
||||
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
|
||||
* <AdminShell
|
||||
* logo={{ title: 'Platform Admin', subtitle: 'Lilith Platform' }}
|
||||
* navigation={NAVIGATION_SECTIONS}
|
||||
* accentColor="primary"
|
||||
* footerText="Lilith Platform v0.1.0"
|
||||
* >
|
||||
* <Routes>...</Routes>
|
||||
* </AdminShell>
|
||||
* ```
|
||||
*/
|
||||
export function AdminShell({
|
||||
logo,
|
||||
navigation,
|
||||
footerText,
|
||||
accentColor = 'primary',
|
||||
children,
|
||||
}: AdminShellProps) {
|
||||
return (
|
||||
<AppContainer>
|
||||
<Sidebar
|
||||
logo={logo}
|
||||
navigation={navigation}
|
||||
footerText={footerText}
|
||||
accentColor={accentColor}
|
||||
/>
|
||||
<MainContent>{children}</MainContent>
|
||||
</AppContainer>
|
||||
);
|
||||
}
|
||||
162
src/components/Sidebar.tsx
Normal file
162
src/components/Sidebar.tsx
Normal file
|
|
@ -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 (
|
||||
<SidebarContainer>
|
||||
<LogoArea>
|
||||
<LogoTitle $accentColor={accentColor}>
|
||||
{logo.title}
|
||||
{logo.badge && <Badge $variant={logo.badgeVariant}>{logo.badge}</Badge>}
|
||||
</LogoTitle>
|
||||
<LogoSubtitle>{logo.subtitle}</LogoSubtitle>
|
||||
</LogoArea>
|
||||
|
||||
<NavSectionsContainer>
|
||||
{navigation.map((section) => (
|
||||
<NavSectionContainer key={section.title}>
|
||||
<NavSectionTitle>{section.title}</NavSectionTitle>
|
||||
<NavList>
|
||||
{section.items.map((item) => (
|
||||
<li key={item.to}>
|
||||
<StyledNavLink
|
||||
to={item.to}
|
||||
end
|
||||
title={item.description}
|
||||
$accentColor={accentColor}
|
||||
>
|
||||
{item.label}
|
||||
</StyledNavLink>
|
||||
</li>
|
||||
))}
|
||||
</NavList>
|
||||
</NavSectionContainer>
|
||||
))}
|
||||
</NavSectionsContainer>
|
||||
|
||||
{footerText && <Footer>{footerText}</Footer>}
|
||||
</SidebarContainer>
|
||||
);
|
||||
}
|
||||
30
src/index.ts
Normal file
30
src/index.ts
Normal file
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
64
src/types.ts
Normal file
64
src/types.ts
Normal file
|
|
@ -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;
|
||||
}
|
||||
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
|
|
@ -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"]
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue