Add Launcher application template and icon system
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nodedc/tokens",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"type": "module",
|
||||
"files": ["tokens.json", "tokens.css", "themes.css"],
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nodedc/ui-core",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"type": "module",
|
||||
"files": ["dist", "styles.css"],
|
||||
"main": "./dist/index.js",
|
||||
@@ -17,6 +17,6 @@
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nodedc/tokens": "0.2.0"
|
||||
"@nodedc/tokens": "0.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,6 +532,14 @@ textarea.nodedc-field__control {
|
||||
padding: 1rem var(--nodedc-page-padding) 0.75rem;
|
||||
}
|
||||
|
||||
.nodedc-header-shell[data-fixed="true"] {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
margin-inline: 0;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.nodedc-header {
|
||||
display: grid;
|
||||
min-height: 4.25rem;
|
||||
@@ -603,6 +611,7 @@ textarea.nodedc-field__control {
|
||||
min-height: calc(var(--nodedc-header-pill-height) - 0.64rem);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.45rem;
|
||||
border: 0;
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
outline: 0;
|
||||
@@ -691,6 +700,189 @@ textarea.nodedc-field__control {
|
||||
color: var(--nodedc-glass-control-active-text);
|
||||
}
|
||||
|
||||
.nodedc-app-shell {
|
||||
--nodedc-app-header-height: 6rem;
|
||||
--nodedc-app-page-pad: 1.25rem;
|
||||
--nodedc-app-panel-gap: 1.25rem;
|
||||
--nodedc-app-nav-width: clamp(20.75rem, 19.5vw, 22rem);
|
||||
--nodedc-app-content-width: calc(
|
||||
100vw - (var(--nodedc-app-page-pad) * 2) - (var(--nodedc-app-panel-gap) * 2) - (var(--nodedc-app-nav-width) * 2)
|
||||
);
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--nodedc-canvas);
|
||||
}
|
||||
|
||||
.nodedc-app-shell__main {
|
||||
position: relative;
|
||||
height: calc(100vh - var(--nodedc-app-header-height));
|
||||
min-height: 0;
|
||||
margin-top: var(--nodedc-app-header-height);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nodedc-app-shell__stage {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
padding: 0 var(--nodedc-app-page-pad) var(--nodedc-app-page-pad);
|
||||
transition: padding 440ms cubic-bezier(0.22, 1, 0.36, 1), transform 440ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.nodedc-app-shell__stage > * {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.nodedc-app-shell__navigation,
|
||||
.nodedc-app-shell__content {
|
||||
position: absolute;
|
||||
z-index: var(--nodedc-layer-panel);
|
||||
top: 0;
|
||||
bottom: var(--nodedc-app-page-pad);
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.nodedc-app-shell__navigation {
|
||||
left: var(--nodedc-app-page-pad);
|
||||
width: var(--nodedc-app-nav-width);
|
||||
}
|
||||
|
||||
.nodedc-app-shell__content {
|
||||
left: calc(var(--nodedc-app-page-pad) + var(--nodedc-app-nav-width) + var(--nodedc-app-panel-gap));
|
||||
width: max(32rem, var(--nodedc-app-content-width));
|
||||
transition: width 500ms cubic-bezier(0.22, 1, 0.36, 1), right 500ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.nodedc-app-shell[data-navigation-open="true"] .nodedc-app-shell__stage {
|
||||
padding-left: calc(var(--nodedc-app-page-pad) + var(--nodedc-app-nav-width) + var(--nodedc-app-panel-gap));
|
||||
}
|
||||
|
||||
.nodedc-app-shell[data-content-open="true"]:not([data-content-expanded="true"]) .nodedc-app-shell__stage {
|
||||
padding-left: calc(
|
||||
var(--nodedc-app-page-pad) + var(--nodedc-app-nav-width) + var(--nodedc-app-panel-gap) +
|
||||
max(32rem, var(--nodedc-app-content-width)) + var(--nodedc-app-panel-gap)
|
||||
);
|
||||
}
|
||||
|
||||
.nodedc-app-shell[data-content-open="true"][data-content-expanded="true"] .nodedc-app-shell__content {
|
||||
right: var(--nodedc-app-page-pad);
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.nodedc-app-shell[data-content-open="true"][data-content-expanded="true"] .nodedc-app-shell__stage {
|
||||
pointer-events: none;
|
||||
transform: translateX(calc(100vw + var(--nodedc-app-page-pad)));
|
||||
}
|
||||
|
||||
.nodedc-application-panel {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
gap: 0.75rem;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
border-radius: var(--nodedc-radius-card);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.012)),
|
||||
rgba(10, 10, 13, 0.9);
|
||||
color: var(--nodedc-text-primary);
|
||||
padding: 1rem;
|
||||
box-shadow: 0 34px 110px rgba(0, 0, 0, 0.42);
|
||||
backdrop-filter: blur(28px);
|
||||
-webkit-backdrop-filter: blur(28px);
|
||||
animation: nodedc-application-panel-in 460ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
@keyframes nodedc-application-panel-in {
|
||||
from { opacity: 0; transform: translateX(-1.1rem); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
.nodedc-application-panel__head {
|
||||
display: flex;
|
||||
min-height: 3.55rem;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.nodedc-application-panel__titles {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 0.24rem;
|
||||
padding: 0.2rem 0.25rem;
|
||||
}
|
||||
|
||||
.nodedc-application-panel__titles span {
|
||||
color: var(--nodedc-text-muted);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.nodedc-application-panel__titles h1,
|
||||
.nodedc-application-panel__titles p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nodedc-application-panel__titles h1 {
|
||||
font-size: 1.15rem;
|
||||
font-weight: 780;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.nodedc-application-panel__titles p {
|
||||
max-width: 42rem;
|
||||
color: var(--nodedc-text-muted);
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.nodedc-application-panel__actions {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.nodedc-application-panel__action {
|
||||
display: grid;
|
||||
width: 2.92rem;
|
||||
height: 2.92rem;
|
||||
place-items: center;
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: var(--nodedc-text-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nodedc-application-panel__action:hover,
|
||||
.nodedc-application-panel__action:focus-visible {
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
background: var(--nodedc-glass-control-hover);
|
||||
color: var(--nodedc-text-primary);
|
||||
}
|
||||
|
||||
.nodedc-application-panel[data-expanded="true"] .nodedc-application-panel__action:first-child {
|
||||
border-color: transparent;
|
||||
background: var(--nodedc-glass-control-active);
|
||||
color: var(--nodedc-glass-control-active-text);
|
||||
}
|
||||
|
||||
.nodedc-application-panel__body {
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.15) transparent;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel {
|
||||
position: relative;
|
||||
display: grid;
|
||||
@@ -713,6 +905,10 @@ textarea.nodedc-field__control {
|
||||
animation: nodedc-admin-panel-in 420ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel:not([data-has-contexts="true"]) {
|
||||
grid-template-rows: auto minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
@keyframes nodedc-admin-panel-in {
|
||||
from { opacity: 0; transform: translateX(-1.6rem); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
@@ -915,6 +1111,7 @@ textarea.nodedc-field__control {
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__nav-item {
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
transition: background 160ms ease, color 160ms ease, opacity 160ms ease, transform 160ms ease;
|
||||
}
|
||||
@@ -933,6 +1130,15 @@ textarea.nodedc-field__control {
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.048), 0 10px 22px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__nav-item:focus-visible {
|
||||
outline: 0;
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08), 0 10px 22px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.nodedc-app-shell__navigation > .nodedc-admin-panel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__nav-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@@ -1514,9 +1720,30 @@ textarea.nodedc-field__control {
|
||||
font-size: var(--nodedc-font-size-lg);
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.nodedc-app-shell[data-content-open="true"] .nodedc-app-shell__content {
|
||||
right: var(--nodedc-app-page-pad);
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.nodedc-app-shell[data-content-open="true"] .nodedc-app-shell__stage {
|
||||
pointer-events: none;
|
||||
transform: translateX(calc(100vw + var(--nodedc-app-page-pad)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.nodedc-header-shell {
|
||||
width: 100%;
|
||||
padding: 0.85rem 1rem 0.65rem;
|
||||
background: var(--nodedc-canvas);
|
||||
}
|
||||
|
||||
.nodedc-header {
|
||||
min-height: 8.6rem;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-content: start;
|
||||
row-gap: 0.65rem;
|
||||
}
|
||||
|
||||
.nodedc-header__center {
|
||||
@@ -1530,6 +1757,54 @@ textarea.nodedc-field__control {
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.nodedc-app-shell {
|
||||
--nodedc-app-header-height: 10.25rem;
|
||||
--nodedc-app-page-pad: 0.7rem;
|
||||
--nodedc-app-panel-gap: 0.7rem;
|
||||
}
|
||||
|
||||
.nodedc-app-shell__stage {
|
||||
padding: 0 var(--nodedc-app-page-pad) var(--nodedc-app-page-pad);
|
||||
}
|
||||
|
||||
.nodedc-app-shell__navigation,
|
||||
.nodedc-app-shell__content {
|
||||
right: var(--nodedc-app-page-pad);
|
||||
bottom: var(--nodedc-app-page-pad);
|
||||
left: var(--nodedc-app-page-pad);
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.nodedc-app-shell[data-navigation-open="true"] .nodedc-app-shell__stage,
|
||||
.nodedc-app-shell[data-content-open="true"] .nodedc-app-shell__stage {
|
||||
padding-left: var(--nodedc-app-page-pad);
|
||||
pointer-events: none;
|
||||
transform: translateX(calc(100vw + var(--nodedc-app-page-pad)));
|
||||
}
|
||||
|
||||
.nodedc-app-shell[data-content-open="true"] .nodedc-app-shell__navigation {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.nodedc-application-panel,
|
||||
.nodedc-admin-panel {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.nodedc-application-panel {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.nodedc-application-panel__titles p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nodedc-application-panel__action:first-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nodedc-window[data-placement="end"] {
|
||||
width: calc(100vw - 1.75rem);
|
||||
max-height: calc(100vh - 1.75rem);
|
||||
@@ -1554,6 +1829,7 @@ textarea.nodedc-field__control {
|
||||
.nodedc-dropdown-surface,
|
||||
.nodedc-overlay,
|
||||
.nodedc-window,
|
||||
.nodedc-application-panel,
|
||||
.nodedc-inspector__section-content {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nodedc/ui-dom",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"type": "module",
|
||||
"files": ["dist"],
|
||||
"main": "./dist/index.js",
|
||||
@@ -16,6 +16,6 @@
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nodedc/ui-core": "0.2.0"
|
||||
"@nodedc/ui-core": "0.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user