Compare commits
1 Commits
Features/F
...
3041fa4d0f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3041fa4d0f |
@@ -37,12 +37,12 @@
|
|||||||
- [x] Foundation
|
- [x] Foundation
|
||||||
- [x] Typography
|
- [x] Typography
|
||||||
- [x] Icons
|
- [x] Icons
|
||||||
- [ ] App Shell
|
|
||||||
- [ ] Primitives
|
- [ ] Primitives
|
||||||
- [ ] Button
|
- [ ] Button
|
||||||
- [ ] IconButton
|
- [ ] IconButton
|
||||||
- [ ] Input
|
- [ ] Input
|
||||||
- [ ] Surface
|
- [ ] Surface
|
||||||
|
- [ ] App-Shell
|
||||||
- [ ] Nav-Bar
|
- [ ] Nav-Bar
|
||||||
- [ ] Workspace-Switching
|
- [ ] Workspace-Switching
|
||||||
- [ ] Workspace-Home
|
- [ ] Workspace-Home
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
// Path: Frontend/src/app.tsx
|
// Path: Frontend/src/app.tsx
|
||||||
|
|
||||||
import type { JSX } from "solid-js";
|
|
||||||
import { AppShell } from "./components/shell/AppShell/AppShell";
|
|
||||||
import "./styles/main.scss";
|
import "./styles/main.scss";
|
||||||
|
|
||||||
const App = (): JSX.Element => {
|
export default function App() {
|
||||||
return <AppShell />;
|
return <main />;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default App;
|
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
.shell {
|
|
||||||
height: 100dvh;
|
|
||||||
min-height: 100dvh;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: auto minmax(0, 1fr);
|
|
||||||
background: var(--color-canvas);
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.body {
|
|
||||||
--rail-width: 4.75rem;
|
|
||||||
--sidebar-width: 16.75rem;
|
|
||||||
--shell-top-left-radius: calc(var(--radius-xl) + var(--space-1));
|
|
||||||
--shell-frame-border: color-mix(in srgb, var(--color-border-strong) 44%, transparent);
|
|
||||||
--shell-divider-border: color-mix(in srgb, var(--color-border-strong) 34%, transparent);
|
|
||||||
--sidebar-panel-surface: color-mix(in srgb, var(--color-surface-muted) 92%, transparent);
|
|
||||||
--workspace-panel-surface: color-mix(in srgb, var(--color-canvas) 94%, var(--color-surface));
|
|
||||||
min-height: 0;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: var(--rail-width) minmax(0, 1fr);
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--color-surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
.railColumn {
|
|
||||||
min-height: 0;
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
background: var(--color-surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspaceRegion {
|
|
||||||
position: relative;
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 0;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
|
|
||||||
overflow: visible;
|
|
||||||
z-index: 1;
|
|
||||||
isolation: isolate;
|
|
||||||
border-top-left-radius: var(--shell-top-left-radius);
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspaceRegion::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background:
|
|
||||||
linear-gradient(
|
|
||||||
to right,
|
|
||||||
var(--sidebar-panel-surface) 0,
|
|
||||||
var(--sidebar-panel-surface) calc(var(--sidebar-width) - 0.5px),
|
|
||||||
var(--workspace-panel-surface) calc(var(--sidebar-width) - 0.5px),
|
|
||||||
var(--workspace-panel-surface) 100%
|
|
||||||
);
|
|
||||||
border: 1px solid var(--shell-frame-border);
|
|
||||||
border-top: 0;
|
|
||||||
border-top-left-radius: var(--shell-top-left-radius);
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
box-shadow: inset 0 1px 0 color-mix(in srgb, white 3%, transparent);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebarColumn {
|
|
||||||
position: relative;
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 0;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: minmax(0, 1fr);
|
|
||||||
overflow: visible;
|
|
||||||
z-index: 1;
|
|
||||||
background: var(--sidebar-panel-surface);
|
|
||||||
border-top: 1px solid var(--shell-frame-border);
|
|
||||||
border-left: 1px solid var(--shell-frame-border);
|
|
||||||
border-top-left-radius: var(--shell-top-left-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspaceMain {
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 0;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
z-index: 1;
|
|
||||||
border-top: 1px solid var(--shell-frame-border);
|
|
||||||
border-left: 1px solid var(--shell-divider-border);
|
|
||||||
background: var(--workspace-panel-surface);
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebarDock {
|
|
||||||
position: absolute;
|
|
||||||
right: var(--space-1);
|
|
||||||
bottom: var(--space-3);
|
|
||||||
left: calc(var(--space-1) - (var(--rail-width) * 0.9));
|
|
||||||
z-index: calc(var(--z-modal) + 1);
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include respond-up(mobile) {
|
|
||||||
.body {
|
|
||||||
--rail-width: 5rem;
|
|
||||||
--sidebar-width: 17.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include respond-down(tablet) {
|
|
||||||
.body {
|
|
||||||
--rail-width: 4.5rem;
|
|
||||||
--sidebar-width: 13.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include respond-down(mobile) {
|
|
||||||
.body {
|
|
||||||
grid-template-columns: 4.5rem minmax(0, 1fr);
|
|
||||||
--rail-width: 4.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.railColumn {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspaceRegion,
|
|
||||||
.sidebarDock {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Path: Frontend/src/components/shell/AppShell/AppShell.tsx
|
|
||||||
|
|
||||||
import { createSignal, onMount, type JSX } from "solid-js";
|
|
||||||
import { getDocumentTheme, setTheme, type Theme } from "../../../helpers/theme";
|
|
||||||
import { WorkspaceHome } from "../../workspace-home/WorkspaceHome/WorkspaceHome";
|
|
||||||
import { LeftRail } from "../LeftRail/LeftRail";
|
|
||||||
import { ProfileDock } from "../ProfileDock/ProfileDock";
|
|
||||||
import { TopBar } from "../TopBar/TopBar";
|
|
||||||
import { WorkspaceSidebar } from "../WorkspaceSidebar/WorkspaceSidebar";
|
|
||||||
import styles from "./AppShell.module.scss";
|
|
||||||
|
|
||||||
export const AppShell = (): JSX.Element => {
|
|
||||||
const [themeState, setThemeState] = createSignal<Theme>("light");
|
|
||||||
|
|
||||||
onMount((): void => {
|
|
||||||
setThemeState(getDocumentTheme());
|
|
||||||
});
|
|
||||||
|
|
||||||
const toggleTheme = (): void => {
|
|
||||||
const next: Theme = themeState() === "dark" ? "light" : "dark";
|
|
||||||
|
|
||||||
setTheme(next);
|
|
||||||
setThemeState(next);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class={styles.shell}>
|
|
||||||
<TopBar theme={themeState()} onToggleTheme={toggleTheme} />
|
|
||||||
<div class={styles.body}>
|
|
||||||
<div class={styles.railColumn}>
|
|
||||||
<LeftRail />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class={styles.workspaceRegion}>
|
|
||||||
<div class={styles.sidebarColumn}>
|
|
||||||
<WorkspaceSidebar />
|
|
||||||
|
|
||||||
<div class={styles.sidebarDock}>
|
|
||||||
<ProfileDock />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class={styles.workspaceMain}>
|
|
||||||
<WorkspaceHome />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
.rail {
|
|
||||||
--rail-workspace-size: var(--control-size-lg);
|
|
||||||
--rail-action-size: var(--control-size-md);
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-3);
|
|
||||||
padding: var(--space-3) var(--space-2);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topCluster,
|
|
||||||
.bottomCluster {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.items {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 0;
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-2);
|
|
||||||
overflow-y: auto;
|
|
||||||
overscroll-behavior: contain;
|
|
||||||
padding-block: var(--space-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
width: var(--rail-workspace-size);
|
|
||||||
height: var(--rail-workspace-size);
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
background: var(--color-accent);
|
|
||||||
color: var(--color-accent-contrast);
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspaceButton {
|
|
||||||
width: var(--rail-workspace-size);
|
|
||||||
height: var(--rail-workspace-size);
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
@include interactive-frame(var(--color-surface-muted), var(--color-border), var(--color-text-muted), var(--radius-lg));
|
|
||||||
@include text-label;
|
|
||||||
@include interactive-frame-hover();
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspaceButtonActive {
|
|
||||||
background: var(--color-accent);
|
|
||||||
border-color: transparent;
|
|
||||||
color: var(--color-accent-contrast);
|
|
||||||
box-shadow: var(--shadow-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.addButton {
|
|
||||||
width: var(--rail-action-size);
|
|
||||||
height: var(--rail-action-size);
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: 1px dashed var(--color-border-strong);
|
|
||||||
border-radius: var(--radius-pill);
|
|
||||||
background: transparent;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--color-surface-hover);
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// Path: Frontend/src/components/shell/LeftRail/LeftRail.tsx
|
|
||||||
|
|
||||||
import { For, type JSX } from "solid-js";
|
|
||||||
import { Plus } from "../../../lib/icons";
|
|
||||||
import { railItems } from "../data/shell.data";
|
|
||||||
import styles from "./LeftRail.module.scss";
|
|
||||||
|
|
||||||
export const LeftRail = (): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<aside class={styles.rail} aria-label="Workspace rail">
|
|
||||||
<div class={styles.topCluster}>
|
|
||||||
<div class={styles.logo} aria-hidden="true">
|
|
||||||
M
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class={styles.items}>
|
|
||||||
<For each={railItems}>
|
|
||||||
{(item): JSX.Element => (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
classList={{
|
|
||||||
[styles.workspaceButton]: true,
|
|
||||||
[styles.workspaceButtonActive]: !!item.active,
|
|
||||||
}}
|
|
||||||
title={item.label}
|
|
||||||
aria-label={item.label}
|
|
||||||
>
|
|
||||||
{item.abbreviation}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class={styles.bottomCluster}>
|
|
||||||
<button type="button" class={styles.addButton} aria-label="Create workspace" title="Create workspace">
|
|
||||||
<Plus size={16} strokeWidth={2} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
.panel {
|
|
||||||
--profile-dock-avatar-size: var(--control-size-md);
|
|
||||||
--profile-dock-action-min-height: var(--space-8);
|
|
||||||
--profile-dock-border: color-mix(in srgb, var(--color-border-strong) 75%, transparent);
|
|
||||||
--profile-dock-surface: color-mix(in srgb, var(--color-surface) 94%, transparent);
|
|
||||||
--profile-dock-status-ring: 0 0 0 3px color-mix(in srgb, var(--color-success) 18%, transparent);
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
width: 100%;
|
|
||||||
display: grid;
|
|
||||||
gap: var(--space-2);
|
|
||||||
padding: var(--space-3) var(--space-3) var(--space-2);
|
|
||||||
border: 1px solid var(--profile-dock-border);
|
|
||||||
border-radius: calc(var(--radius-xl) + var(--space-1));
|
|
||||||
background: var(--profile-dock-surface);
|
|
||||||
box-shadow:
|
|
||||||
0 20px 48px color-mix(in srgb, black 16%, transparent),
|
|
||||||
var(--shadow-strong);
|
|
||||||
backdrop-filter: blur(var(--blur-overlay));
|
|
||||||
}
|
|
||||||
|
|
||||||
.identity {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto minmax(0, 1fr);
|
|
||||||
gap: var(--space-3);
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: var(--profile-dock-avatar-size);
|
|
||||||
height: var(--profile-dock-avatar-size);
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--color-accent-soft);
|
|
||||||
color: var(--color-accent-strong);
|
|
||||||
@include text-label;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy {
|
|
||||||
min-width: 0;
|
|
||||||
display: grid;
|
|
||||||
gap: 0.15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
@include text-label;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
|
||||||
@include text-caption;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-2);
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.statusDot {
|
|
||||||
width: 0.5rem;
|
|
||||||
height: 0.5rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--color-success);
|
|
||||||
box-shadow: var(--profile-dock-status-ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: var(--space-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.action {
|
|
||||||
min-height: var(--profile-dock-action-min-height);
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: var(--space-1);
|
|
||||||
@include interactive-frame(var(--color-surface-muted));
|
|
||||||
@include interactive-frame-hover();
|
|
||||||
}
|
|
||||||
|
|
||||||
.actionLabel {
|
|
||||||
@include text-caption;
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
// Path: Frontend/src/components/shell/ProfileDock/ProfileDock.tsx
|
|
||||||
|
|
||||||
import type { JSX } from "solid-js";
|
|
||||||
import { Settings, User } from "../../../lib/icons";
|
|
||||||
import styles from "./ProfileDock.module.scss";
|
|
||||||
|
|
||||||
export const ProfileDock = (): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<section class={styles.panel} aria-label="Profile dock">
|
|
||||||
<div class={styles.identity}>
|
|
||||||
<div class={styles.avatar} aria-hidden="true">
|
|
||||||
R
|
|
||||||
</div>
|
|
||||||
<div class={styles.copy}>
|
|
||||||
<span class={styles.name}>Ronald</span>
|
|
||||||
<span class={styles.status}>
|
|
||||||
<span class={styles.statusDot} aria-hidden="true" />
|
|
||||||
Online in Moku
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class={styles.actions}>
|
|
||||||
<button type="button" class={styles.action}>
|
|
||||||
<User size={16} strokeWidth={2} />
|
|
||||||
<span class={styles.actionLabel}>Account</span>
|
|
||||||
</button>
|
|
||||||
<button type="button" class={styles.action}>
|
|
||||||
<Settings size={16} strokeWidth={2} />
|
|
||||||
<span class={styles.actionLabel}>Prefs</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
.topBar {
|
|
||||||
--topbar-control-size: var(--control-size-md);
|
|
||||||
min-height: 4rem;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-3);
|
|
||||||
padding: var(--space-3) var(--space-4) var(--space-3);
|
|
||||||
background: var(--color-surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
.identity {
|
|
||||||
min-width: 0;
|
|
||||||
display: grid;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eyebrow {
|
|
||||||
@include text-caption;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
@include text-title;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-2);
|
|
||||||
|
|
||||||
strong {
|
|
||||||
font: inherit;
|
|
||||||
font-weight: var(--font-weight-title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.context {
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.actionButton,
|
|
||||||
.themeButton {
|
|
||||||
height: var(--topbar-control-size);
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
@include interactive-frame();
|
|
||||||
}
|
|
||||||
|
|
||||||
.actionButton {
|
|
||||||
width: var(--topbar-control-size);
|
|
||||||
}
|
|
||||||
|
|
||||||
.themeButton {
|
|
||||||
width: auto;
|
|
||||||
padding-inline: var(--space-2);
|
|
||||||
gap: var(--space-1);
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.actionButton,
|
|
||||||
.themeButton {
|
|
||||||
@include interactive-frame-hover();
|
|
||||||
}
|
|
||||||
|
|
||||||
.themeLabel {
|
|
||||||
@include text-label;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include respond-down(mobile) {
|
|
||||||
.topBar {
|
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
|
||||||
padding: var(--space-2) var(--space-3) var(--space-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
// Path: Frontend/src/components/shell/TopBar/TopBar.tsx
|
|
||||||
|
|
||||||
import { For, type JSX } from "solid-js";
|
|
||||||
import type { Theme } from "../../../helpers/theme";
|
|
||||||
import { ChevronDown } from "../../../lib/icons";
|
|
||||||
import { topBarActions } from "../data/shell.data";
|
|
||||||
import styles from "./TopBar.module.scss";
|
|
||||||
|
|
||||||
type TopBarProps = {
|
|
||||||
theme: Theme;
|
|
||||||
onToggleTheme: VoidFunction;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TopBar = (props: TopBarProps): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<header class={styles.topBar}>
|
|
||||||
<div class={styles.identity}>
|
|
||||||
<span class={styles.eyebrow}>Moku Work</span>
|
|
||||||
<div class={styles.title}>
|
|
||||||
<strong>Workspace Shell</strong>
|
|
||||||
<span class={styles.context}>Moku / Product</span>
|
|
||||||
<ChevronDown size={16} strokeWidth={2} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class={styles.themeButton} type="button" onClick={props.onToggleTheme}>
|
|
||||||
<span class={styles.themeLabel}>{props.theme === "dark" ? "Dark" : "Light"}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class={styles.actions}>
|
|
||||||
<For each={topBarActions}>
|
|
||||||
{(item): JSX.Element => {
|
|
||||||
const Icon = item.icon;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button class={styles.actionButton} type="button" aria-label={item.label} title={item.label}>
|
|
||||||
<Icon size={18} strokeWidth={2} />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
.sidebar {
|
|
||||||
--sidebar-nav-item-min-height: var(--control-size-lg);
|
|
||||||
--sidebar-dock-clearance: 8rem;
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 0;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: auto auto minmax(0, 1fr);
|
|
||||||
gap: var(--space-4);
|
|
||||||
padding: var(--space-4);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: grid;
|
|
||||||
gap: 0.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eyebrow {
|
|
||||||
@include text-caption;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
@include text-title;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta {
|
|
||||||
@include text-caption;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
max-width: 28ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: auto minmax(0, 1fr);
|
|
||||||
gap: var(--space-2);
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navScroller {
|
|
||||||
min-height: 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
overscroll-behavior: contain;
|
|
||||||
padding-right: var(--space-1);
|
|
||||||
padding-bottom: calc(var(--space-4) + var(--sidebar-dock-clearance));
|
|
||||||
margin-right: calc(var(--space-1) * -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sectionLabel {
|
|
||||||
@include text-label;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navList {
|
|
||||||
list-style: none;
|
|
||||||
display: grid;
|
|
||||||
gap: var(--space-1);
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navItem {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-2);
|
|
||||||
min-height: var(--sidebar-nav-item-min-height);
|
|
||||||
padding: var(--space-2) var(--space-3);
|
|
||||||
@include interactive-frame(transparent, transparent, var(--color-text-muted), var(--radius-lg));
|
|
||||||
text-align: left;
|
|
||||||
@include interactive-frame-hover(var(--color-surface-hover), transparent, var(--color-text));
|
|
||||||
}
|
|
||||||
|
|
||||||
.navItemActive {
|
|
||||||
border-color: var(--color-border);
|
|
||||||
background: var(--color-surface);
|
|
||||||
color: var(--color-text);
|
|
||||||
box-shadow: var(--shadow-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
color: inherit;
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
@include text-label;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.itemMeta {
|
|
||||||
@include text-caption;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@include respond-down(mobile) {
|
|
||||||
.sidebar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
// Path: Frontend/src/components/shell/WorkspaceSidebar/WorkspaceSidebar.tsx
|
|
||||||
|
|
||||||
import { For, Show, type JSX } from "solid-js";
|
|
||||||
import { workspaceSidebarItems } from "../data/shell.data";
|
|
||||||
import styles from "./WorkspaceSidebar.module.scss";
|
|
||||||
|
|
||||||
export const WorkspaceSidebar = (): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<aside class={styles.sidebar} aria-label="Workspace navigation">
|
|
||||||
<div class={styles.header}>
|
|
||||||
<span class={styles.eyebrow}>Workspace</span>
|
|
||||||
<h2 class={styles.title}>Product Operations</h2>
|
|
||||||
<p class={styles.meta}>A barebone shell for Moku’s first real workspace layout.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class={styles.section}>
|
|
||||||
<span class={styles.sectionLabel}>Navigation</span>
|
|
||||||
<div class={styles.navScroller}>
|
|
||||||
<ul class={styles.navList} role="list">
|
|
||||||
<For each={workspaceSidebarItems}>
|
|
||||||
{(item): JSX.Element => {
|
|
||||||
const Icon = item.icon;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
classList={{
|
|
||||||
[styles.navItem]: true,
|
|
||||||
[styles.navItemActive]: !!item.active,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon class={styles.icon} size={18} strokeWidth={2} />
|
|
||||||
<span class={styles.label}>{item.label}</span>
|
|
||||||
<Show when={item.meta}>
|
|
||||||
<span class={styles.itemMeta}>{item.meta}</span>
|
|
||||||
</Show>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
// Path: Frontend/src/components/shell/data/shell.data.ts
|
|
||||||
|
|
||||||
import type { Component } from "solid-js";
|
|
||||||
import { Bell, Folder, Home, LayoutGrid, Plus, Search, Settings, User } from "../../../lib/icons";
|
|
||||||
|
|
||||||
type ShellIconProps = {
|
|
||||||
class?: string;
|
|
||||||
size?: number;
|
|
||||||
strokeWidth?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ShellIcon = Component<ShellIconProps>;
|
|
||||||
|
|
||||||
export type RailItem = {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
abbreviation: string;
|
|
||||||
active?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SidebarItem = {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
icon: ShellIcon;
|
|
||||||
active?: boolean;
|
|
||||||
meta?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TopBarAction = {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
icon: ShellIcon;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const railItems: readonly RailItem[] = [
|
|
||||||
{ id: "personal", label: "Personal", abbreviation: "P" },
|
|
||||||
{ id: "moku", label: "Moku", abbreviation: "M", active: true },
|
|
||||||
{ id: "labs", label: "Labs", abbreviation: "L" },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const workspaceSidebarItems: readonly SidebarItem[] = [
|
|
||||||
{ id: "home", label: "Home", icon: Home, active: true },
|
|
||||||
{ id: "boards", label: "Boards", icon: LayoutGrid, meta: "0" },
|
|
||||||
{ id: "docs", label: "Docs", icon: Folder, meta: "0" },
|
|
||||||
{ id: "settings", label: "Settings", icon: Settings },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const topBarActions: readonly TopBarAction[] = [
|
|
||||||
{ id: "search", label: "Search", icon: Search },
|
|
||||||
{ id: "create", label: "Create", icon: Plus },
|
|
||||||
{ id: "inbox", label: "Inbox", icon: Bell },
|
|
||||||
{ id: "profile", label: "Profile", icon: User },
|
|
||||||
] as const;
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
.viewport {
|
|
||||||
--workspace-content-max-width: var(--content-width-wide);
|
|
||||||
--workspace-card-min-height: calc(var(--space-12) * 3);
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 0;
|
|
||||||
display: grid;
|
|
||||||
align-content: start;
|
|
||||||
gap: var(--space-5);
|
|
||||||
padding: var(--space-5) var(--space-6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero {
|
|
||||||
display: grid;
|
|
||||||
gap: var(--space-3);
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--workspace-content-max-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eyebrow {
|
|
||||||
@include text-caption;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
@include text-display;
|
|
||||||
font-family: var(--font-family-display);
|
|
||||||
max-width: 12ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
max-width: 64ch;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--workspace-content-max-width);
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
||||||
gap: var(--space-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
display: grid;
|
|
||||||
gap: var(--space-2);
|
|
||||||
min-height: var(--workspace-card-min-height);
|
|
||||||
padding: var(--space-4);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-xl);
|
|
||||||
background: var(--color-surface);
|
|
||||||
box-shadow: var(--shadow-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cardTitle {
|
|
||||||
@include text-title;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cardCopy {
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cardMeta {
|
|
||||||
@include text-caption;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@include respond-down(tablet) {
|
|
||||||
.grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include respond-down(mobile) {
|
|
||||||
.viewport {
|
|
||||||
gap: var(--space-4);
|
|
||||||
padding: var(--space-4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
// Path: Frontend/src/components/workspace-home/WorkspaceHome/WorkspaceHome.tsx
|
|
||||||
|
|
||||||
import { For, type JSX } from "solid-js";
|
|
||||||
import styles from "./WorkspaceHome.module.scss";
|
|
||||||
|
|
||||||
type ShellCheckpointCard = {
|
|
||||||
title: string;
|
|
||||||
copy: string;
|
|
||||||
meta: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const shellCheckpointCards: readonly ShellCheckpointCard[] = [
|
|
||||||
{
|
|
||||||
title: "App shell",
|
|
||||||
copy: "Top bar, left rail, workspace sidebar, and content viewport are now split into modular components.",
|
|
||||||
meta: "Layout foundation",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Workspace context",
|
|
||||||
copy: "The shell already has clear places for org context, workspace switching, and future surface navigation.",
|
|
||||||
meta: "Navigation foundation",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Next build target",
|
|
||||||
copy: "You can now plug in workspace home content, auth state, and early primitives without redesigning the whole frame.",
|
|
||||||
meta: "Ready for v0.1.0 work",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const WorkspaceHome = (): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<main class={styles.viewport}>
|
|
||||||
<section class={styles.hero}>
|
|
||||||
<span class={styles.eyebrow}>Workspace home</span>
|
|
||||||
<h1 class={styles.title}>Moku is ready for its first real shell.</h1>
|
|
||||||
<p class={styles.description}>
|
|
||||||
This is the barebone app frame for v0.1.0 — enough structure to start building real frontend surfaces on top of a real backend core.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class={styles.grid} aria-label="Shell checkpoints">
|
|
||||||
<For each={shellCheckpointCards}>
|
|
||||||
{(card): JSX.Element => (
|
|
||||||
<article class={styles.card}>
|
|
||||||
<h2 class={styles.cardTitle}>{card.title}</h2>
|
|
||||||
<p class={styles.cardCopy}>{card.copy}</p>
|
|
||||||
<span class={styles.cardMeta}>{card.meta}</span>
|
|
||||||
</article>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -37,11 +37,6 @@
|
|||||||
--radius-xl: 1.25rem;
|
--radius-xl: 1.25rem;
|
||||||
--radius-pill: 999px;
|
--radius-pill: 999px;
|
||||||
|
|
||||||
--control-size-md: 2.25rem;
|
|
||||||
--control-size-lg: 2.5rem;
|
|
||||||
--content-width-wide: 72rem;
|
|
||||||
--blur-overlay: 18px;
|
|
||||||
|
|
||||||
--shadow-soft: 0 12px 32px hsl(220 30% 10% / 0.08);
|
--shadow-soft: 0 12px 32px hsl(220 30% 10% / 0.08);
|
||||||
--shadow-strong: 0 20px 48px hsl(220 30% 10% / 0.16);
|
--shadow-strong: 0 20px 48px hsl(220 30% 10% / 0.16);
|
||||||
|
|
||||||
|
|||||||
@@ -18,47 +18,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin respond-down($breakpoint) {
|
|
||||||
@if $breakpoint == mobile {
|
|
||||||
@media (max-width: calc($breakpoint-mobile - 0.01rem)) {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
} @else if $breakpoint == tablet {
|
|
||||||
@media (max-width: calc($breakpoint-tablet - 0.01rem)) {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
} @else if $breakpoint == desktop {
|
|
||||||
@media (max-width: calc($breakpoint-desktop - 0.01rem)) {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin interactive-frame(
|
|
||||||
$background: var(--color-surface),
|
|
||||||
$border: var(--color-border),
|
|
||||||
$color: var(--color-text-muted),
|
|
||||||
$radius: var(--radius-md)
|
|
||||||
) {
|
|
||||||
border: 1px solid $border;
|
|
||||||
border-radius: $radius;
|
|
||||||
background: $background;
|
|
||||||
color: $color;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin interactive-frame-hover(
|
|
||||||
$background: var(--color-surface-hover),
|
|
||||||
$border: var(--color-border-strong),
|
|
||||||
$color: var(--color-text)
|
|
||||||
) {
|
|
||||||
&:hover {
|
|
||||||
background: $background;
|
|
||||||
border-color: $border;
|
|
||||||
color: $color;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin text-caption {
|
@mixin text-caption {
|
||||||
font-size: var(--font-size-caption);
|
font-size: var(--font-size-caption);
|
||||||
font-weight: var(--font-weight-caption);
|
font-weight: var(--font-weight-caption);
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -1,4 +1,27 @@
|
|||||||
# Moku Work
|
# Moku Base
|
||||||
|
|
||||||
|
Empty scaffold for a Monday-style replacement app.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
- `Backend/` - blank backend placeholder
|
||||||
|
- `Commands/` - Just submodules and command entrypoints
|
||||||
|
- `Docker/` - local compose files
|
||||||
|
- `Env/` - local environment files
|
||||||
|
- `Frontend/` - SolidStart bare workspace shell
|
||||||
|
- `Proxy/` - blank proxy placeholder
|
||||||
|
|
||||||
|
## Local usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just --list --list-submodules
|
||||||
|
```
|
||||||
|
|
||||||
|
Main local flow:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just local dev
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user