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:
Josh 2026-01-12 01:59:56 +02:00
parent c662e41b97
commit bef5dd4437
108 changed files with 8650 additions and 441 deletions

View file

@ -1,74 +0,0 @@
import { useRef, useState, useLayoutEffect } from 'react'
import type { IconComponent } from '../shared/Icons'
export interface Tab<T extends string = string> {
value: T
label: string
Icon?: IconComponent
}
interface TabsProps<T extends string = string> {
value: T
onChange: (value: T) => void
tabs: Tab<T>[]
className?: string
}
export function Tabs<T extends string = string>({
value,
onChange,
tabs,
className = '',
}: TabsProps<T>) {
const containerRef = useRef<HTMLDivElement>(null)
const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 })
useLayoutEffect(() => {
const container = containerRef.current
if (!container) return
const activeIndex = tabs.findIndex(tab => tab.value === value)
const buttons = container.querySelectorAll('button')
const activeButton = buttons[activeIndex]
if (activeButton) {
setIndicatorStyle({
left: activeButton.offsetLeft,
width: activeButton.offsetWidth,
})
}
}, [value, tabs])
return (
<div
ref={containerRef}
className={`relative flex bg-border/50 border border-border ${className}`}
>
<div
className="absolute inset-y-0 bg-surface border-x border-border/50 transition-all duration-200 ease-out"
style={{
left: indicatorStyle.left,
width: indicatorStyle.width,
}}
/>
{tabs.map((tab) => {
const isActive = tab.value === value
return (
<button
key={tab.value}
type="button"
onClick={() => onChange(tab.value)}
className={`relative z-10 inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium tracking-wide transition-colors duration-150 ${
isActive
? 'text-text'
: 'text-muted hover:text-text/70'
}`}
>
{tab.Icon && <tab.Icon className="w-3.5 h-3.5" />}
<span>{tab.label}</span>
</button>
)
})}
</div>
)
}