refactor: move studio to frontends workspace
- Move studio from root to frontends/studio/ - Add owner-tools frontend for live blog admin UI - Add shared ui component library - Set up npm workspaces for frontends - Add enhanced code block extension for editor Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c662e41b97
commit
bef5dd4437
108 changed files with 8650 additions and 441 deletions
76
frontends/studio/src/components/layout/Header.tsx
Normal file
76
frontends/studio/src/components/layout/Header.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { useState } from 'react'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { $router } from '../../stores/router'
|
||||
import { Icons, type IconComponent } from '../shared/Icons'
|
||||
|
||||
interface NavItem {
|
||||
route: string
|
||||
label: string
|
||||
Icon: IconComponent
|
||||
}
|
||||
|
||||
const navItems: NavItem[] = [
|
||||
{ route: 'posts', label: 'Posts', Icon: Icons.Posts },
|
||||
{ route: 'analytics', label: 'Analytics', Icon: Icons.Analytics },
|
||||
{ route: 'general', label: 'General', Icon: Icons.Settings },
|
||||
{ route: 'design', label: 'Design', Icon: Icons.Design },
|
||||
{ route: 'domain', label: 'Domain', Icon: Icons.Domain },
|
||||
{ route: 'engagement', label: 'Engagement', Icon: Icons.Engagement },
|
||||
{ route: 'monetization', label: 'Monetization', Icon: Icons.Monetization },
|
||||
{ route: 'api', label: 'API Keys', Icon: Icons.ApiKeys },
|
||||
{ route: 'data', label: 'Data', Icon: Icons.Data },
|
||||
{ route: 'billing', label: 'Billing', Icon: Icons.Billing },
|
||||
]
|
||||
|
||||
interface HeaderProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Header({ className = '' }: HeaderProps) {
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const page = useStore($router)
|
||||
const currentRoute = page?.route ?? 'posts'
|
||||
|
||||
return (
|
||||
<header className={`lg:hidden bg-surface border-b border-border sticky top-0 z-50 ${className}`}>
|
||||
<div className="h-14 flex items-center justify-between px-4">
|
||||
<a href="/" className="block">
|
||||
<div className="text-[15px] font-bold tracking-tight text-text">WriteKit</div>
|
||||
<div className="text-[11px] font-medium text-muted tracking-wide">Studio</div>
|
||||
</a>
|
||||
<button
|
||||
onClick={() => setMenuOpen(!menuOpen)}
|
||||
className="w-9 h-9 flex items-center justify-center hover:bg-border transition-colors"
|
||||
>
|
||||
{menuOpen ? <Icons.Close className="text-lg" /> : <Icons.Menu className="text-lg" />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{menuOpen && (
|
||||
<nav className="p-2 border-t border-border bg-surface">
|
||||
{navItems.map(item => (
|
||||
<a
|
||||
key={item.route}
|
||||
href={`/studio/${item.route}`}
|
||||
onClick={() => setMenuOpen(false)}
|
||||
className={currentRoute === item.route ? 'nav-item-active' : 'nav-item'}
|
||||
>
|
||||
<item.Icon className={`text-sm ${currentRoute === item.route ? 'text-accent opacity-100' : 'opacity-50'}`} />
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
))}
|
||||
<div className="border-t border-border mt-2 pt-2">
|
||||
<a
|
||||
href="/"
|
||||
target="_blank"
|
||||
className="nav-item"
|
||||
>
|
||||
<Icons.ExternalLink className="text-sm opacity-50" />
|
||||
<span>View Site</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
</header>
|
||||
)
|
||||
}
|
||||
102
frontends/studio/src/components/layout/Sidebar.tsx
Normal file
102
frontends/studio/src/components/layout/Sidebar.tsx
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import { useStore } from '@nanostores/react'
|
||||
import { $router } from '../../stores/router'
|
||||
import { Icons, type IconComponent } from '../shared/Icons'
|
||||
|
||||
interface NavItem {
|
||||
route: string
|
||||
label: string
|
||||
Icon: IconComponent
|
||||
}
|
||||
|
||||
interface NavSection {
|
||||
title: string
|
||||
items: NavItem[]
|
||||
}
|
||||
|
||||
const navigation: NavSection[] = [
|
||||
{
|
||||
title: '',
|
||||
items: [
|
||||
{ route: 'home', label: 'Home', Icon: Icons.Home },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Content',
|
||||
items: [
|
||||
{ route: 'posts', label: 'Posts', Icon: Icons.Posts },
|
||||
{ route: 'analytics', label: 'Analytics', Icon: Icons.Analytics },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Site',
|
||||
items: [
|
||||
{ route: 'general', label: 'General', Icon: Icons.Settings },
|
||||
{ route: 'design', label: 'Design', Icon: Icons.Design },
|
||||
{ route: 'domain', label: 'Domain', Icon: Icons.Domain },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Readers',
|
||||
items: [
|
||||
{ route: 'engagement', label: 'Engagement', Icon: Icons.Engagement },
|
||||
{ route: 'monetization', label: 'Monetization', Icon: Icons.Monetization },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Developer',
|
||||
items: [
|
||||
{ route: 'plugins', label: 'Plugins', Icon: Icons.Code },
|
||||
{ route: 'api', label: 'API Keys', Icon: Icons.ApiKeys },
|
||||
{ route: 'data', label: 'Data', Icon: Icons.Data },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Account',
|
||||
items: [
|
||||
{ route: 'billing', label: 'Billing', Icon: Icons.Billing },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export function Sidebar() {
|
||||
const page = useStore($router)
|
||||
const currentRoute = page?.route ?? 'posts'
|
||||
|
||||
return (
|
||||
<aside className="w-56 h-screen bg-bg border-r border-border flex flex-col">
|
||||
<div className="px-4 py-6">
|
||||
<a href="/" className="block group">
|
||||
<div className="text-[15px] font-bold tracking-tight text-text">
|
||||
WriteKit
|
||||
</div>
|
||||
<div className="text-[11px] font-medium text-muted tracking-wide">
|
||||
Studio
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 overflow-y-auto px-3">
|
||||
{navigation.map((section, idx) => (
|
||||
<div key={section.title || idx} className="mb-1">
|
||||
{section.title && <div className="nav-section">{section.title}</div>}
|
||||
<div className="space-y-0.5">
|
||||
{section.items.map(item => {
|
||||
const href = item.route === 'home' ? '/studio' : `/studio/${item.route}`
|
||||
return (
|
||||
<a
|
||||
key={item.route}
|
||||
href={href}
|
||||
className={currentRoute === item.route ? 'nav-item-active' : 'nav-item'}
|
||||
>
|
||||
<item.Icon className={`text-sm ${currentRoute === item.route ? 'text-accent opacity-100' : 'opacity-50'}`} />
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
2
frontends/studio/src/components/layout/index.ts
Normal file
2
frontends/studio/src/components/layout/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { Sidebar } from './Sidebar'
|
||||
export { Header } from './Header'
|
||||
Loading…
Add table
Add a link
Reference in a new issue