Add Launcher application template and icon system

This commit is contained in:
DCCONSTRUCTIONS
2026-07-10 11:56:27 +03:00
parent 4a9ad01ea6
commit 9261d3af36
30 changed files with 1466 additions and 363 deletions
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@nodedc/ui-react",
"version": "0.2.0",
"version": "0.3.0",
"type": "module",
"files": ["dist"],
"main": "./dist/index.js",
@@ -16,7 +16,8 @@
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@nodedc/ui-core": "0.2.0"
"@nodedc/ui-core": "0.3.0",
"lucide-react": "^0.468.0"
},
"peerDependencies": {
"react": ">=18",
@@ -26,6 +26,7 @@ export interface AdminNavigationPanelProps extends Omit<HTMLAttributes<HTMLEleme
activeId?: string;
footer?: ReactNode;
closeLabel?: string;
navigationLabel?: string;
onClose: () => void;
onItemChange: (id: string) => void;
}
@@ -38,13 +39,14 @@ export function AdminNavigationPanel({
activeId,
footer,
closeLabel = "Закрыть администрирование",
navigationLabel = "Разделы администрирования",
onClose,
onItemChange,
className,
...props
}: AdminNavigationPanelProps) {
return (
<aside className={cn("nodedc-admin-panel", className)} {...props}>
<aside className={cn("nodedc-admin-panel", className)} data-has-contexts={contexts.length > 0 ? "true" : undefined} {...props}>
<header className="nodedc-admin-panel__head">
<div>
<p>{eyebrow}</p>
@@ -55,7 +57,7 @@ export function AdminNavigationPanel({
</button>
</header>
<div className="nodedc-admin-panel__contexts">
{contexts.length > 0 ? <div className="nodedc-admin-panel__contexts">
{contexts.map((context) => (
<div className="nodedc-admin-panel__context-group" key={context.id}>
{context.groupLabel ? <span className="nodedc-admin-panel__group-label">{context.groupLabel}</span> : null}
@@ -78,9 +80,9 @@ export function AdminNavigationPanel({
</div>
</div>
))}
</div>
</div> : null}
<nav className="nodedc-admin-panel__nav" aria-label="Разделы администрирования">
<nav className="nodedc-admin-panel__nav" aria-label={navigationLabel}>
{items.map((item) => (
<button
key={item.id}
@@ -99,4 +101,3 @@ export function AdminNavigationPanel({
</aside>
);
}
+3 -1
View File
@@ -5,6 +5,7 @@ export interface AppHeaderProps extends HTMLAttributes<HTMLElement> {
brand: ReactNode;
brandHref?: string;
brandLabel?: string;
fixed?: boolean;
left?: ReactNode;
center?: ReactNode;
right?: ReactNode;
@@ -14,6 +15,7 @@ export function AppHeader({
brand,
brandHref,
brandLabel = "NODE.DC",
fixed = true,
left,
center,
right,
@@ -27,7 +29,7 @@ export function AppHeader({
);
return (
<header className={cn("nodedc-header-shell", className)} {...props}>
<header className={cn("nodedc-header-shell", className)} data-fixed={fixed ? "true" : undefined} {...props}>
<div className="nodedc-header">
<div className="nodedc-header__left">{brandNode}{left}</div>
<div className="nodedc-header__center">{center}</div>
@@ -0,0 +1,96 @@
import type { HTMLAttributes, ReactNode } from "react";
import { cn } from "./cn";
import { Icon } from "./Icon";
export interface ApplicationShellProps extends Omit<HTMLAttributes<HTMLDivElement>, "content"> {
header: ReactNode;
stage: ReactNode;
navigation?: ReactNode;
content?: ReactNode;
navigationOpen?: boolean;
contentOpen?: boolean;
contentExpanded?: boolean;
}
export function ApplicationShell({
header,
stage,
navigation,
content,
navigationOpen = false,
contentOpen = false,
contentExpanded = false,
className,
...props
}: ApplicationShellProps) {
return (
<div
className={cn("nodedc-app-shell", className)}
data-navigation-open={navigationOpen ? "true" : undefined}
data-content-open={contentOpen ? "true" : undefined}
data-content-expanded={contentOpen && contentExpanded ? "true" : undefined}
{...props}
>
{header}
<main className="nodedc-app-shell__main">
<div className="nodedc-app-shell__stage">{stage}</div>
{navigationOpen && navigation ? <div className="nodedc-app-shell__navigation">{navigation}</div> : null}
{contentOpen && content ? <div className="nodedc-app-shell__content">{content}</div> : null}
</main>
</div>
);
}
export interface ApplicationPanelProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
eyebrow?: ReactNode;
title: ReactNode;
description?: ReactNode;
expanded?: boolean;
expandLabel?: string;
closeLabel?: string;
onExpandedChange?: (expanded: boolean) => void;
onClose: () => void;
children: ReactNode;
}
export function ApplicationPanel({
eyebrow,
title,
description,
expanded = false,
expandLabel = "Развернуть окно",
closeLabel = "Закрыть окно",
onExpandedChange,
onClose,
children,
className,
...props
}: ApplicationPanelProps) {
return (
<section className={cn("nodedc-application-panel", className)} data-expanded={expanded ? "true" : undefined} {...props}>
<header className="nodedc-application-panel__head">
<div className="nodedc-application-panel__titles">
{eyebrow ? <span>{eyebrow}</span> : null}
<h1>{title}</h1>
{description ? <p>{description}</p> : null}
</div>
<div className="nodedc-application-panel__actions">
{onExpandedChange ? (
<button
type="button"
className="nodedc-application-panel__action"
aria-label={expanded ? "Свернуть окно" : expandLabel}
onClick={() => onExpandedChange(!expanded)}
>
<Icon name={expanded ? "minimize" : "expand"} />
</button>
) : null}
<button type="button" className="nodedc-application-panel__action" aria-label={closeLabel} onClick={onClose}>
<Icon name="close" />
</button>
</div>
</header>
<div className="nodedc-application-panel__body">{children}</div>
</section>
);
}
+114
View File
@@ -0,0 +1,114 @@
import {
Activity,
AlertTriangle,
Boxes,
Building2,
Check,
ChevronDown,
ChevronLeft,
ChevronRight,
Circle,
ClipboardList,
Copy,
Database,
Download,
ExternalLink,
FileText,
FolderOpen,
Globe2,
Image,
Inbox,
KeyRound,
LayoutDashboard,
ListChecks,
LockKeyhole,
MailPlus,
Maximize2,
Minimize2,
Network,
PanelTop,
Pencil,
Plus,
RefreshCw,
Save,
Search,
Settings,
ShieldCheck,
SlidersHorizontal,
Trash2,
UploadCloud,
UserCircle,
UsersRound,
Video,
X,
type LucideIcon,
type LucideProps,
} from "lucide-react";
const icons = {
activity: Activity,
alert: AlertTriangle,
apps: Boxes,
building: Building2,
check: Check,
"chevron-down": ChevronDown,
"chevron-left": ChevronLeft,
"chevron-right": ChevronRight,
circle: Circle,
clipboard: ClipboardList,
close: X,
copy: Copy,
database: Database,
download: Download,
edit: Pencil,
expand: Maximize2,
external: ExternalLink,
file: FileText,
folder: FolderOpen,
globe: Globe2,
grid: LayoutDashboard,
image: Image,
inbox: Inbox,
key: KeyRound,
list: ListChecks,
lock: LockKeyhole,
mail: MailPlus,
minimize: Minimize2,
network: Network,
panel: PanelTop,
plus: Plus,
profile: UserCircle,
refresh: RefreshCw,
save: Save,
search: Search,
settings: Settings,
shield: ShieldCheck,
sliders: SlidersHorizontal,
trash: Trash2,
upload: UploadCloud,
users: UsersRound,
video: Video,
} satisfies Record<string, LucideIcon>;
export type IconName = keyof typeof icons;
export interface IconProps extends Omit<LucideProps, "ref"> {
name: IconName;
label?: string;
}
export function Icon({ name, label, size = 18, strokeWidth = 1.8, ...props }: IconProps) {
const IconComponent = icons[name];
return (
<IconComponent
size={size}
strokeWidth={strokeWidth}
aria-hidden={label ? undefined : "true"}
aria-label={label}
role={label ? "img" : undefined}
focusable="false"
{...props}
/>
);
}
+2
View File
@@ -1,5 +1,6 @@
export * from "./AppHeader";
export * from "./AdminNavigationPanel";
export * from "./ApplicationShell";
export * from "./Button";
export * from "./Checker";
export * from "./ConfirmationModal";
@@ -7,6 +8,7 @@ export * from "./Dropdown";
export * from "./Field";
export * from "./Glass";
export * from "./Inspector";
export * from "./Icon";
export * from "./RangeControl";
export * from "./SegmentedControl";
export * from "./Select";