Merge branch 'Features/Frontend/CollapsibleShell' into tmp/collapsible-shell-clean-merge

This commit is contained in:
MangoPig
2026-06-17 05:42:48 +01:00
15 changed files with 478 additions and 52 deletions

2
.gitignore vendored
View File

@@ -25,3 +25,5 @@ pnpm-debug.log*
# Go build output
tmp/
bin/
.cgcignore

View File

@@ -15,7 +15,6 @@ type Config struct {
LogLevel string
WebPort string
APIPort string
WorkerPort string
PostgresURL string
ValkeyURL string
ShutdownTimeout time.Duration
@@ -28,7 +27,6 @@ func Load() *Config {
LogLevel: getEnv("LOG_LEVEL", "debug"),
WebPort: getEnv("BACKEND_WEB_PORT", "8080"),
APIPort: getEnv("BACKEND_API_PORT", "8081"),
WorkerPort: getEnv("BACKEND_WORKER_PORT", "8082"),
PostgresURL: getEnv("DATABASE_URL", "postgres://moku:moku_dev_password@localhost:5432/moku?sslmode=disable"),
ValkeyURL: getEnv("VALKEY_URL", "redis://localhost:6379/0"),
ShutdownTimeout: getDurationEnv("BACKEND_SHUTDOWN_TIMEOUT", 10*time.Second),
@@ -43,8 +41,6 @@ func (c *Config) Address(serviceName string) string {
port = c.WebPort
case "api":
port = c.APIPort
case "worker":
port = c.WorkerPort
default:
port = c.WebPort
}

View File

@@ -6,7 +6,6 @@ LOG_LEVEL=debug
BACKEND_WEB_PORT=8080
BACKEND_API_PORT=8081
BACKEND_WORKER_PORT=8082
BACKEND_SHUTDOWN_TIMEOUT=10s
DATABASE_URL=postgres://moku:moku_dev_password@localhost:5432/moku?sslmode=disable

View File

@@ -23,6 +23,14 @@
background: var(--color-surface);
}
.bodyRailCollapsed {
--rail-width: 0rem;
}
.bodySidebarCollapsed {
--sidebar-width: 0rem;
}
.railColumn {
min-height: 0;
display: flex;
@@ -97,7 +105,7 @@
position: absolute;
bottom: var(--space-3);
left: calc(var(--space-1) + (var(--rail-width) * 0.1));
width: calc(var(--sidebar-width) + (var(--rail-width) * 0.9) - var(--space-2));
width: max(12rem, calc(var(--sidebar-width) + (var(--rail-width) * 0.9) - var(--space-2)));
right: auto;
z-index: calc(var(--z-modal) + 1);
pointer-events: none;
@@ -112,6 +120,14 @@
--rail-width: 5rem;
--sidebar-width: 17.25rem;
}
.bodyRailCollapsed {
--rail-width: 0rem;
}
.bodySidebarCollapsed {
--sidebar-width: 0rem;
}
}
@include respond-down(tablet) {
@@ -119,6 +135,58 @@
--rail-width: 4.5rem;
--sidebar-width: 13.25rem;
}
.bodyRailCollapsed {
--rail-width: 0rem;
}
.bodySidebarCollapsed {
--sidebar-width: 0rem;
}
}
.bodyRailCollapsed .railColumn {
overflow: hidden;
}
.bodySidebarCollapsed .railColumn {
--rail-dock-clearance: 0rem;
}
.bodySidebarCollapsed:not(.bodyRailCollapsed) .railColumn {
--rail-bottom-offset: var(--space-3);
}
.bodySidebarCollapsed .sidebarColumn {
--sidebar-dock-clearance: 0rem;
display: none;
overflow: hidden;
border-left-width: 0;
border-top-width: 0;
}
.bodySidebarCollapsed .workspaceRegion {
grid-template-columns: minmax(0, 1fr);
}
.bodySidebarCollapsed .workspaceMain {
border-left-color: transparent;
}
.bodySidebarCollapsed:not(.bodyRailCollapsed) .workspaceMain {
border-top-width: 1px;
border-top-color: var(--shell-frame-border);
border-left-color: var(--shell-frame-border);
border-top-left-radius: var(--shell-top-left-radius);
box-shadow: none;
}
.bodySidebarCollapsed:not(.bodyRailCollapsed) .workspaceRegion::before {
display: none;
}
.bodySidebarCollapsed .sidebarDock {
display: none;
}
@include respond-down(mobile) {

View File

@@ -11,6 +11,8 @@ import styles from "./AppShell.module.scss";
export const AppShell = (): JSX.Element => {
const [themeState, setThemeState] = createSignal<Theme>("light");
const [isRailCollapsed, setIsRailCollapsed] = createSignal(false);
const [isSidebarCollapsed, setIsSidebarCollapsed] = createSignal(false);
onMount((): void => {
setThemeState(getDocumentTheme());
@@ -27,20 +29,37 @@ export const AppShell = (): JSX.Element => {
<div class={styles.shell}>
<TopBar theme={themeState()} onToggleTheme={toggleTheme} />
<div class={styles.body}>
<div
classList={{
[styles.body]: true,
[styles.bodyRailCollapsed]: isRailCollapsed(),
[styles.bodySidebarCollapsed]: isSidebarCollapsed(),
}}
>
{/* Left server rail */}
<div class={styles.railColumn}>
<LeftRail />
<LeftRail collapsed={isRailCollapsed()} />
</div>
{/* Sidebar + main workspace frame */}
<div class={styles.workspaceRegion}>
<div class={styles.sidebarColumn}>
<WorkspaceSidebar />
<WorkspaceSidebar
collapsed={isSidebarCollapsed()}
railCollapsed={isRailCollapsed()}
onToggleRailCollapse={(): void => {
setIsRailCollapsed((collapsed) => !collapsed);
}}
/>
</div>
<div class={styles.workspaceMain}>
<WorkspaceHome />
<WorkspaceHome
sidebarCollapsed={isSidebarCollapsed()}
onToggleSidebarCollapse={(): void => {
setIsSidebarCollapsed((collapsed) => !collapsed);
}}
/>
</div>
</div>

View File

@@ -1,7 +1,6 @@
.rail {
--rail-workspace-size: var(--control-size-lg);
--rail-action-size: var(--control-size-md);
--rail-dock-clearance: 8rem;
position: relative;
z-index: 3;
flex: 1;
@@ -10,10 +9,19 @@
flex-direction: column;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-2) calc(var(--space-3) + var(--rail-dock-clearance));
padding: var(--space-3) var(--space-2) calc(var(--space-3) + var(--rail-dock-clearance, 8rem));
overflow: visible;
}
.railCollapsed {
--rail-workspace-size: calc(var(--control-size-md) + 0.1rem);
--rail-action-size: calc(var(--control-size-md) + 0.1rem);
justify-content: flex-start;
gap: 0;
padding-top: var(--space-4);
padding-inline: var(--space-1);
}
.topCluster,
.bottomCluster {
width: 100%;
@@ -25,12 +33,22 @@
.bottomCluster {
margin-top: auto;
margin-bottom: var(--rail-bottom-offset, 0rem);
}
.topCluster {
gap: var(--space-3);
}
.railCollapsed .topCluster {
gap: var(--space-3);
}
.railCollapsed .topCluster,
.railCollapsed .bottomCluster {
align-items: center;
}
.items {
width: 100%;
min-height: 0;

View File

@@ -1,6 +1,6 @@
// Path: Frontend/src/components/shell/LeftRail/LeftRail.tsx
import { For, type JSX } from "solid-js";
import { For, Show, type JSX } from "solid-js";
import { Plus } from "../../../lib/icons";
import { railItems, type RailItem } from "../data/shell.data";
import styles from "./LeftRail.module.scss";
@@ -42,29 +42,49 @@ const RailEntry = (props: RailEntryProps): JSX.Element => {
);
};
export const LeftRail = (): JSX.Element => {
type LeftRailProps = {
collapsed: boolean;
};
export const LeftRail = (props: LeftRailProps): JSX.Element => {
const personalItem = railItems.find((item) => item.kind === "personal");
const organizationItems = railItems.filter((item) => item.kind === "organization");
return (
<aside class={styles.rail} aria-label="Server rail">
<aside
classList={{
[styles.rail]: true,
[styles.railCollapsed]: props.collapsed,
}}
aria-label="Server rail"
>
<div class={styles.topCluster}>
{personalItem ? <RailEntry item={personalItem} label={personalItem.label} abbreviation="M" personal /> : null}
<Show when={!props.collapsed && personalItem}>
{(item): JSX.Element => (
<RailEntry item={item()} label={item().label} abbreviation={item().abbreviation} personal />
)}
</Show>
<div class={styles.sectionDivider} aria-hidden="true" />
<Show when={!props.collapsed}>
<div class={styles.sectionDivider} aria-hidden="true" />
</Show>
</div>
<div class={styles.items}>
<For each={organizationItems}>
{(item): JSX.Element => <RailEntry item={item} label={item.label} abbreviation={item.abbreviation} />}
</For>
</div>
<Show when={!props.collapsed}>
<div class={styles.items}>
<For each={organizationItems}>
{(item): JSX.Element => <RailEntry item={item} label={item.label} abbreviation={item.abbreviation} />}
</For>
</div>
</Show>
<div class={styles.bottomCluster}>
<button type="button" class={styles.addButton} aria-label="Create server" title="Create server">
<Plus size={16} strokeWidth={2} />
</button>
</div>
<Show when={!props.collapsed}>
<div class={styles.bottomCluster}>
<button type="button" class={styles.addButton} aria-label="Create server" title="Create server">
<Plus size={16} strokeWidth={2} />
</button>
</div>
</Show>
</aside>
);
};

View File

@@ -5,6 +5,10 @@
--project-drawer-bottom: calc(var(--sidebar-dock-clearance) + var(--project-drawer-gap));
}
.rootCompact {
justify-items: center;
}
.trigger {
width: 100%;
min-width: 0;
@@ -39,6 +43,29 @@
box-shadow: var(--shadow-soft);
}
.triggerCompact {
width: var(--control-size-xl);
min-height: var(--control-size-xl);
grid-template-columns: auto;
justify-items: center;
gap: 0.15rem;
padding: var(--space-2) 0;
border-radius: var(--radius-xl);
}
.triggerCompact .triggerLead {
width: var(--control-size-md);
height: var(--control-size-md);
}
.triggerCompact .triggerIcon {
transform: rotate(0deg);
}
.triggerCompact .triggerIconOpen {
transform: rotate(180deg);
}
.triggerLead {
width: var(--control-size-md);
height: var(--control-size-md);
@@ -99,6 +126,13 @@
pointer-events: auto;
}
.rootCompact .scrim,
.rootCompact .drawer {
left: 0;
right: auto;
width: min(18rem, calc(100vw - 6rem));
}
.drawer {
position: absolute;
inset: calc(var(--project-drawer-top) + var(--project-drawer-gap)) var(--space-4)

View File

@@ -6,6 +6,7 @@ import { activeProject, projectItems } from "../data/shell.data";
import styles from "./ProjectSelector.module.scss";
type ProjectSelectorProps = {
compact?: boolean;
isOpen: boolean;
onToggle: () => void;
onClose: () => void;
@@ -68,7 +69,10 @@ export const ProjectSelector = (props: ProjectSelectorProps): JSX.Element => {
return (
<div
class={styles.root}
classList={{
[styles.root]: true,
[styles.rootCompact]: !!props.compact,
}}
style={{
"--project-drawer-top": `${drawerTop()}px`,
}}
@@ -79,20 +83,23 @@ export const ProjectSelector = (props: ProjectSelectorProps): JSX.Element => {
ref={triggerRef}
classList={{
[styles.trigger]: true,
[styles.triggerCompact]: !!props.compact,
[styles.triggerOpen]: props.isOpen,
}}
aria-label="Open project drawer"
aria-label={`Open left workspace sidebar menu for ${selectedProject().name}`}
aria-expanded={props.isOpen}
title="Open project drawer"
title={selectedProject().name}
onClick={toggleOpen}
>
<span class={styles.triggerLead} aria-hidden="true">
<Folder size={18} strokeWidth={2} />
</span>
<span class={styles.triggerCopy}>
<span class={styles.eyebrow}>Projects</span>
<span class={styles.value}>{selectedProject().name}</span>
</span>
{!props.compact ? (
<span class={styles.triggerCopy}>
<span class={styles.eyebrow}>Projects</span>
<span class={styles.value}>{selectedProject().name}</span>
</span>
) : null}
<ChevronDown
classList={{
[styles.triggerIcon]: true,

View File

@@ -1,6 +1,5 @@
.sidebar {
--sidebar-nav-item-min-height: var(--control-size-lg);
--sidebar-dock-clearance: 8rem;
position: relative;
min-width: 0;
min-height: 0;
@@ -15,13 +14,60 @@
.header {
display: grid;
gap: var(--space-3);
}
.headerActions {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: var(--space-2);
justify-items: stretch;
}
.headerControls {
display: grid;
grid-template-columns: minmax(0, 1fr);
align-items: start;
}
.headerDrawerOpen {
z-index: 4;
}
.headerActionButton {
width: 100%;
min-height: var(--control-size-md);
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid color-mix(in srgb, var(--color-border-strong) 44%, transparent);
border-radius: var(--radius-pill);
background: color-mix(in srgb, var(--color-surface) 94%, transparent);
color: var(--color-text-muted);
box-shadow: var(--shadow-soft);
transition:
background 160ms var(--easing-standard),
color 160ms var(--easing-standard),
border-color 160ms var(--easing-standard),
transform 180ms var(--easing-standard);
}
.headerActionButton:hover,
.headerActionButton:focus-visible {
background: var(--color-surface-hover);
border-color: var(--color-border);
color: var(--color-text);
}
.headerActionButton:hover {
transform: translateY(-1px);
}
.headerCollapseButton {
background: color-mix(in srgb, var(--color-accent-soft) 58%, transparent);
color: var(--color-accent-strong);
}
.section {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
@@ -45,7 +91,7 @@
overflow-y: auto;
overscroll-behavior: contain;
padding-right: var(--space-1);
padding-bottom: calc(var(--space-4) + var(--sidebar-dock-clearance));
padding-bottom: calc(var(--space-4) + var(--sidebar-dock-clearance, 8rem));
margin-right: calc(var(--space-1) * -1);
}
@@ -97,6 +143,50 @@
color: var(--color-text-muted);
}
.sidebarCollapsed {
gap: var(--space-3);
padding: var(--space-3) var(--space-2);
}
.sidebarCollapsed .headerActions {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.sidebarCollapsed .headerControls {
justify-items: center;
}
.sidebarCollapsed .header {
justify-items: center;
}
.sidebarCollapsed .navScroller {
padding-right: 0;
margin-right: 0;
}
.sidebarCollapsed .navItem {
grid-template-columns: auto;
justify-content: center;
gap: 0;
min-height: calc(var(--control-size-lg) - var(--space-1));
padding: var(--space-2);
border-radius: var(--radius-md);
}
.sidebarCollapsed .label,
.sidebarCollapsed .itemMeta {
display: none;
}
.sidebarCollapsed .icon {
opacity: 1;
}
.sidebarCollapsed .section {
gap: var(--space-3);
}
@include respond-down(mobile) {
.sidebar {
display: none;

View File

@@ -1,30 +1,74 @@
// Path: Frontend/src/components/shell/WorkspaceSidebar/WorkspaceSidebar.tsx
import { For, Show, createSignal, type JSX } from "solid-js";
import { ChevronLeft, ChevronRight } from "../../../lib/icons";
import { ProjectSelector } from "../ProjectSelector/ProjectSelector";
import { serverSidebarItems } from "../data/shell.data";
import { serverSidebarItems, workspaceSidebarHeaderActions } from "../data/shell.data";
import styles from "./WorkspaceSidebar.module.scss";
export const WorkspaceSidebar = (): JSX.Element => {
type WorkspaceSidebarProps = {
collapsed: boolean;
railCollapsed: boolean;
onToggleRailCollapse: () => void;
};
export const WorkspaceSidebar = (props: WorkspaceSidebarProps): JSX.Element => {
const [isProjectDrawerOpen, setIsProjectDrawerOpen] = createSignal(false);
const railToggleLabel = (): string => (props.railCollapsed ? "Expand server rail" : "Collapse server rail");
return (
<aside class={styles.sidebar} aria-label="Server navigation">
<aside
classList={{
[styles.sidebar]: true,
[styles.sidebarCollapsed]: props.collapsed,
}}
aria-label="Left workspace sidebar"
>
<div
classList={{
[styles.header]: true,
[styles.headerDrawerOpen]: isProjectDrawerOpen(),
}}
>
<ProjectSelector
isOpen={isProjectDrawerOpen()}
onToggle={(): void => {
setIsProjectDrawerOpen(true);
}}
onClose={(): void => {
setIsProjectDrawerOpen(false);
}}
/>
<div class={styles.headerActions}>
<button
type="button"
classList={{
[styles.headerActionButton]: true,
[styles.headerCollapseButton]: true,
}}
aria-label={railToggleLabel()}
title={railToggleLabel()}
onClick={props.onToggleRailCollapse}
>
{props.railCollapsed ? <ChevronRight size={16} strokeWidth={2} /> : <ChevronLeft size={16} strokeWidth={2} />}
</button>
<For each={workspaceSidebarHeaderActions}>
{(action): JSX.Element => {
const Icon = action.icon;
return (
<button type="button" class={styles.headerActionButton} aria-label={action.label} title={action.label}>
<Icon size={16} strokeWidth={2} />
</button>
);
}}
</For>
</div>
<div class={styles.headerControls}>
<ProjectSelector
compact={props.collapsed}
isOpen={isProjectDrawerOpen()}
onToggle={(): void => {
setIsProjectDrawerOpen(true);
}}
onClose={(): void => {
setIsProjectDrawerOpen(false);
}}
/>
</div>
</div>
<div
@@ -33,7 +77,9 @@ export const WorkspaceSidebar = (): JSX.Element => {
[styles.sectionHidden]: isProjectDrawerOpen(),
}}
>
<span class={styles.sectionLabel}>Navigation</span>
<Show when={!props.collapsed}>
<span class={styles.sectionLabel}>Navigation</span>
</Show>
<div class={styles.navScroller}>
<ul class={styles.navList} role="list">
<For each={serverSidebarItems}>
@@ -48,6 +94,8 @@ export const WorkspaceSidebar = (): JSX.Element => {
[styles.navItem]: true,
[styles.navItemActive]: !!item.active,
}}
aria-label={item.label}
title={item.label}
>
<Icon class={styles.icon} size={18} strokeWidth={2} />
<span class={styles.label}>{item.label}</span>

View File

@@ -9,6 +9,7 @@ import {
Keyboard,
LayoutGrid,
LogOut,
Plus,
Repeat,
Search,
Settings,
@@ -81,6 +82,12 @@ export type SidebarItem = {
meta?: string;
};
export type SidebarHeaderAction = {
id: string;
label: string;
icon: ShellIcon;
};
export type TopBarAction = {
id: string;
label: string;
@@ -172,6 +179,12 @@ export const serverSidebarItems: readonly SidebarItem[] = [
{ id: "settings", label: "Settings", icon: Settings },
] as const;
export const workspaceSidebarHeaderActions: readonly SidebarHeaderAction[] = [
{ id: "workspace-settings", label: "Workspace settings", icon: Settings },
{ id: "search-workspace", label: "Search workspace", icon: Search },
{ id: "create-board", label: "Create board", icon: Plus },
] as const;
export const topBarActions: readonly TopBarAction[] = [
{ id: "search", label: "Search", icon: Search },
] as const;

View File

@@ -9,6 +9,71 @@
padding: var(--space-5) var(--space-6);
}
.workspaceTopBar {
width: 100%;
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: var(--space-2);
min-height: calc(var(--control-size-md) - var(--space-3));
padding: 0;
}
.workspaceTopBarStart,
.workspaceTopBarEnd {
min-width: calc(var(--control-size-md) - 0.5rem);
display: inline-flex;
align-items: center;
}
.workspaceTopBarEnd {
justify-content: flex-end;
}
.workspaceTopBarCenter {
min-width: 0;
display: flex;
justify-content: center;
}
.workspaceBreadcrumb {
@include text-caption;
min-width: 0;
color: var(--color-text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.workspaceCollapseButton {
display: inline-flex;
align-items: center;
justify-content: center;
width: calc(var(--control-size-md) - 0.5rem);
height: calc(var(--control-size-md) - 0.5rem);
border: 1px solid color-mix(in srgb, var(--color-border-strong) 44%, transparent);
border-radius: var(--radius-pill);
background: color-mix(in srgb, var(--color-surface) 94%, transparent);
color: var(--color-text-muted);
box-shadow: var(--shadow-soft);
transition:
background 160ms var(--easing-standard),
color 160ms var(--easing-standard),
border-color 160ms var(--easing-standard),
transform 180ms var(--easing-standard);
}
.workspaceCollapseButton:hover,
.workspaceCollapseButton:focus-visible {
background: var(--color-surface-hover);
border-color: var(--color-border);
color: var(--color-text);
}
.workspaceCollapseButton:hover {
transform: translateY(-1px);
}
.hero {
display: grid;
gap: var(--space-3);
@@ -76,4 +141,21 @@
gap: var(--space-4);
padding: var(--space-4);
}
.workspaceTopBar {
grid-template-columns: auto minmax(0, 1fr);
}
.workspaceTopBarEnd {
display: none;
}
.workspaceTopBarCenter {
justify-content: flex-start;
}
.workspaceCollapseButton {
width: calc(var(--control-size-md) - 0.5rem);
height: calc(var(--control-size-md) - 0.5rem);
}
}

View File

@@ -1,7 +1,8 @@
// Path: Frontend/src/components/workspace-home/WorkspaceHome/WorkspaceHome.tsx
import { For, type JSX } from "solid-js";
import { activeServer } from "../../shell/data/shell.data";
import { ChevronLeft, ChevronRight } from "../../../lib/icons";
import { activeProject, activeServer } from "../../shell/data/shell.data";
import styles from "./WorkspaceHome.module.scss";
type ShellCheckpointCard = {
@@ -28,9 +29,37 @@ const shellCheckpointCards: readonly ShellCheckpointCard[] = [
},
];
export const WorkspaceHome = (): JSX.Element => {
type WorkspaceHomeProps = {
sidebarCollapsed: boolean;
onToggleSidebarCollapse: () => void;
};
export const WorkspaceHome = (props: WorkspaceHomeProps): JSX.Element => {
const sidebarToggleLabel = (): string => (props.sidebarCollapsed ? "Expand left workspace sidebar" : "Collapse left workspace sidebar");
const breadcrumb = (): string => `${activeServer.name} / ${activeProject.name} / Home`;
return (
<main class={styles.viewport}>
<div class={styles.workspaceTopBar}>
<div class={styles.workspaceTopBarStart}>
<button
type="button"
class={styles.workspaceCollapseButton}
aria-label={sidebarToggleLabel()}
title={sidebarToggleLabel()}
onClick={props.onToggleSidebarCollapse}
>
{props.sidebarCollapsed ? <ChevronRight size={16} strokeWidth={2} /> : <ChevronLeft size={16} strokeWidth={2} />}
</button>
</div>
<div class={styles.workspaceTopBarCenter}>
<span class={styles.workspaceBreadcrumb}>{breadcrumb()}</span>
</div>
<div class={styles.workspaceTopBarEnd} aria-hidden="true" />
</div>
<section class={styles.hero}>
<span class={styles.eyebrow}>Server home</span>
<h1 class={styles.title}>{activeServer.name} is ready for its first real shell.</h1>

View File

@@ -3,6 +3,8 @@
export { default as Bell } from "lucide-solid/icons/bell";
export { default as CircleHelp } from "lucide-solid/icons/circle-help";
export { default as ChevronDown } from "lucide-solid/icons/chevron-down";
export { default as ChevronLeft } from "lucide-solid/icons/chevron-left";
export { default as ChevronRight } from "lucide-solid/icons/chevron-right";
export { default as Folder } from "lucide-solid/icons/folder";
export { default as Home } from "lucide-solid/icons/house";
export { default as Keyboard } from "lucide-solid/icons/keyboard";
@@ -12,7 +14,6 @@ export { default as Moon } from "lucide-solid/icons/moon";
export { default as Plus } from "lucide-solid/icons/plus";
export { default as Repeat } from "lucide-solid/icons/repeat";
export { default as Search } from "lucide-solid/icons/search";
export { default as Server } from "lucide-solid/icons/server";
export { default as Settings } from "lucide-solid/icons/settings";
export { default as Shield } from "lucide-solid/icons/shield";
export { default as Sun } from "lucide-solid/icons/sun";