Feat: Add responsive workspace shell

This commit is contained in:
MangoPig
2026-06-17 10:52:14 +01:00
parent 5d86a5124b
commit 85bf971547
24 changed files with 1153 additions and 255 deletions

View File

@@ -0,0 +1,112 @@
import { For, Show, type JSX } from "solid-js";
import { ChevronRight, X } from "../../../lib/icons";
import { activeProject, activeServer, workspaceStaticItems, workspaceTree, type SidebarItem, type WorkspaceTreeNode } from "../data/shell.data";
import styles from "./MobileWorkspaceBrowser.module.scss";
type MobileWorkspaceBrowserProps = {
open: boolean;
onClose: VoidFunction;
};
const TreeRow = (props: { node: WorkspaceTreeNode; depth?: number }): JSX.Element => {
const depth = props.depth ?? 0;
const Icon = props.node.icon;
const hasChildren = (props.node.children?.length ?? 0) > 0;
return (
<li class={styles.treeListItem}>
<button classList={{ [styles.treeRow]: true, [styles.treeRowActive]: props.node.active ?? false, [styles.treeRowBranch]: hasChildren }} type="button" style={{ "--tree-depth": `${depth}` }}>
<span class={styles.treeRowLead}>
<Icon size={16} strokeWidth={2} />
<span class={styles.treeLabel}>{props.node.label}</span>
</span>
<span class={styles.treeRowTrail}>
<Show when={props.node.meta}>
<span class={styles.treeMeta}>{props.node.meta}</span>
</Show>
<Show when={hasChildren}>
<ChevronRight size={14} strokeWidth={2} class={styles.treeChevron} />
</Show>
</span>
</button>
<Show when={hasChildren}>
<ul class={styles.treeListNested}>
<For each={props.node.children}>{(child): JSX.Element => <TreeRow node={child} depth={depth + 1} />}</For>
</ul>
</Show>
</li>
);
};
const StaticRow = (props: { item: SidebarItem }): JSX.Element => {
const Icon = props.item.icon;
return (
<li class={styles.treeListItem}>
<button classList={{ [styles.treeRow]: true, [styles.treeRowActive]: props.item.active ?? false }} type="button" style={{ "--tree-depth": "0" }}>
<span class={styles.treeRowLead}>
<Icon size={16} strokeWidth={2} />
<span class={styles.treeLabel}>{props.item.label}</span>
</span>
<span class={styles.treeRowTrail}>
<Show when={props.item.meta}>
<span class={styles.treeMeta}>{props.item.meta}</span>
</Show>
<ChevronRight size={14} strokeWidth={2} class={styles.treeChevron} />
</span>
</button>
</li>
);
};
export const MobileWorkspaceBrowser = (props: MobileWorkspaceBrowserProps): JSX.Element => {
const sectionNodes = workspaceTree.filter((node) => (node.children?.length ?? 0) > 0);
const looseNodes = workspaceTree.filter((node) => (node.children?.length ?? 0) === 0);
return (
<Show when={props.open}>
<div class={styles.browserLayer}>
<section class={styles.sheet} aria-label="Mobile workspace browser">
<header class={styles.sheetHeader}>
<div class={styles.brandBlock}>
<span class={styles.brandEyebrow}>Moku Work</span>
<strong class={styles.brandTitle}>{activeProject.name}</strong>
<span class={styles.brandContext}>{activeServer.name}</span>
</div>
<button class={styles.closeButton} type="button" aria-label="Close workspace browser" onClick={props.onClose}>
<X size={18} strokeWidth={2} />
</button>
</header>
<div class={styles.sheetBody}>
<section class={styles.sectionBlock}>
<span class={styles.sectionLabel}>Workspace</span>
<ul class={styles.treeList}>
<For each={workspaceStaticItems}>{(item): JSX.Element => <StaticRow item={item} />}</For>
</ul>
</section>
<section class={styles.sectionBlock}>
<span class={styles.sectionLabel}>Items</span>
<ul class={styles.treeList}>
<For each={sectionNodes}>{(node): JSX.Element => <TreeRow node={node} />}</For>
</ul>
</section>
<Show when={looseNodes.length > 0}>
<section class={styles.sectionBlock}>
<span class={styles.sectionLabel}>More</span>
<ul class={styles.treeList}>
<For each={looseNodes}>{(node): JSX.Element => <TreeRow node={node} />}</For>
</ul>
</section>
</Show>
</div>
</section>
</div>
</Show>
);
};