Skip to content

Commit 302c1ce

Browse files
committed
feat: resizable sidebar + improved branding
- Add drag-to-resize on sidebar left edge (280–520px range) - Redesign SidebarHeader with icon badge, refined typography, separator - Update canvas brand mark to yellow badge style - Clean up FooterBar status items
1 parent 1c53b50 commit 302c1ce

4 files changed

Lines changed: 87 additions & 29 deletions

File tree

src/components/FooterBar.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,24 @@ export function FooterBar({ onExport, onPresets }: Props) {
1717
if (!layer) return null
1818

1919
const items = [
20-
{ label: 'FMT', value: 'ASCII CANVAS' },
2120
{ label: 'STYLE', value: layer.artStyle.toUpperCase().replace('-', ' ') },
2221
{ label: 'FONT', value: layer.font.toUpperCase() },
23-
{ label: 'AR', value: aspectRatio === 'original' ? 'SOURCE' : aspectRatio },
24-
{ label: 'FX', value: layer.fxPreset === 'none' ? 'NONE' : layer.fxPreset.toUpperCase().replace('-', ' ') },
22+
{ label: 'AR', value: aspectRatio === 'original' ? 'SRC' : aspectRatio },
23+
{ label: 'FX', value: layer.fxPreset === 'none' ? '' : layer.fxPreset.toUpperCase().replace('-', ' ') },
2524
{ label: 'BG', value: backgroundColor },
26-
{ label: 'RES', value: 'DYNAMIC' },
2725
]
2826

2927
return (
30-
<div className="flex flex-col gap-1 border-t border-border px-3 py-2">
31-
<div className="flex flex-wrap gap-x-2 gap-y-0.5">
28+
<div className="flex flex-col gap-1.5 border-t border-border px-3 py-2.5">
29+
<div className="flex flex-wrap gap-x-3 gap-y-0.5">
3230
{items.map((item) => (
3331
<span key={item.label} className="text-[9px] font-mono whitespace-nowrap">
34-
<span className="text-muted-foreground/50">{item.label}</span>{' '}
35-
<span className="text-muted-foreground">{item.value}</span>
32+
<span className="text-muted-foreground/40">{item.label}</span>{' '}
33+
<span className="text-muted-foreground/80">{item.value}</span>
3634
</span>
3735
))}
3836
</div>
39-
<div className="flex items-center gap-1">
37+
<div className="flex items-center gap-0.5">
4038
<Button
4139
variant="ghost"
4240
size="xs"

src/components/LeftModeButtons.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function LeftModeButtons() {
1616

1717
return (
1818
<div className="absolute top-3 left-3 z-10 flex items-center gap-1.5">
19-
<span className="text-sm font-bold font-mono tracking-tighter text-accent/80 mr-1 select-none">G</span>
19+
<span className="flex items-center justify-center w-5 h-5 rounded bg-accent/90 text-accent-foreground font-mono font-black text-[11px] leading-none select-none mr-0.5">G</span>
2020
{PANELS.map((p) => (
2121
<Button
2222
key={p.value}

src/components/Sidebar.tsx

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react'
1+
import { useState, useCallback, useRef, useEffect } from 'react'
22
import { ScrollArea } from '@/components/ui/scroll-area'
33
import { SidebarHeader } from './SidebarHeader'
44
import { SourceUpload } from './SourceUpload'
@@ -8,12 +8,62 @@ import { FooterBar } from './FooterBar'
88
import { ExportPopover } from './ExportPopover'
99
import { SavePopover } from './SavePopover'
1010

11+
const MIN_WIDTH = 280
12+
const MAX_WIDTH = 520
13+
const DEFAULT_WIDTH = 320
14+
1115
export function Sidebar() {
1216
const [showExport, setShowExport] = useState(false)
1317
const [showPresets, setShowPresets] = useState(false)
18+
const [width, setWidth] = useState(DEFAULT_WIDTH)
19+
const isDragging = useRef(false)
20+
const startX = useRef(0)
21+
const startWidth = useRef(0)
22+
23+
const onPointerDown = useCallback((e: React.PointerEvent) => {
24+
e.preventDefault()
25+
isDragging.current = true
26+
startX.current = e.clientX
27+
startWidth.current = width
28+
document.body.style.cursor = 'col-resize'
29+
document.body.style.userSelect = 'none'
30+
}, [width])
31+
32+
useEffect(() => {
33+
const onPointerMove = (e: PointerEvent) => {
34+
if (!isDragging.current) return
35+
const delta = startX.current - e.clientX
36+
setWidth(Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, startWidth.current + delta)))
37+
}
38+
39+
const onPointerUp = () => {
40+
if (!isDragging.current) return
41+
isDragging.current = false
42+
document.body.style.cursor = ''
43+
document.body.style.userSelect = ''
44+
}
45+
46+
window.addEventListener('pointermove', onPointerMove)
47+
window.addEventListener('pointerup', onPointerUp)
48+
return () => {
49+
window.removeEventListener('pointermove', onPointerMove)
50+
window.removeEventListener('pointerup', onPointerUp)
51+
}
52+
}, [])
1453

1554
return (
16-
<aside className="relative flex w-[var(--sidebar-width)] min-w-[var(--sidebar-width)] flex-col border-l border-border bg-background z-[var(--z-sidebar)]">
55+
<aside
56+
className="relative flex flex-col border-l border-border bg-background z-[var(--z-sidebar)]"
57+
style={{ width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH }}
58+
>
59+
{/* Resize handle */}
60+
<div
61+
className="absolute inset-y-0 -left-1 w-2 cursor-col-resize z-20 group"
62+
onPointerDown={onPointerDown}
63+
>
64+
<div className="absolute inset-y-0 left-[3px] w-px bg-border group-hover:bg-accent/50 transition-colors" />
65+
</div>
66+
1767
<ScrollArea className="flex-1">
1868
<SidebarHeader />
1969
<SourceUpload />

src/components/SidebarHeader.tsx

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,42 @@
11
export function SidebarHeader() {
22
return (
3-
<div className="flex flex-col gap-1.5 px-4 pt-4 pb-2">
4-
<div className="flex items-baseline gap-2">
5-
<span className="text-xl font-bold tracking-tighter font-mono text-accent">
6-
GLYPH
7-
</span>
8-
<span className="text-[10px] text-muted-foreground leading-snug">
9-
ascii art generator
10-
</span>
3+
<div className="flex flex-col gap-3 px-4 pt-5 pb-3">
4+
{/* Brand */}
5+
<div className="flex items-center gap-2.5">
6+
<div className="flex items-center justify-center w-7 h-7 rounded-md bg-accent text-accent-foreground font-mono font-black text-sm leading-none select-none">
7+
G
8+
</div>
9+
<div className="flex flex-col">
10+
<span className="text-sm font-bold tracking-tight font-mono text-foreground leading-none">
11+
GLYPH
12+
</span>
13+
<span className="text-[9px] text-muted-foreground/60 tracking-widest uppercase mt-0.5">
14+
ASCII Art Engine
15+
</span>
16+
</div>
1117
</div>
12-
<div className="flex items-center gap-3">
13-
<span className="text-[10px] text-muted-foreground/50 font-mono">v1.0</span>
18+
19+
{/* Links */}
20+
<div className="flex items-center gap-2 border-t border-border/50 pt-2.5">
1421
<a
1522
href="https://github.com/Codeptor/glyph"
1623
target="_blank"
1724
rel="noopener noreferrer"
18-
className="text-[10px] uppercase tracking-widest text-muted-foreground hover:text-accent transition-colors cursor-crosshair"
25+
className="text-[9px] uppercase tracking-[0.15em] text-muted-foreground/70 hover:text-accent transition-colors cursor-crosshair"
1926
>
2027
GitHub
2128
</a>
22-
<button
23-
className="text-[10px] uppercase tracking-widest text-muted-foreground hover:text-foreground transition-colors cursor-crosshair bg-transparent border-none"
24-
onClick={() => {
25-
window.open('https://github.com/Codeptor/glyph/issues', '_blank')
26-
}}
29+
<span className="text-muted-foreground/20 text-[9px]">/</span>
30+
<a
31+
href="https://github.com/Codeptor/glyph/issues"
32+
target="_blank"
33+
rel="noopener noreferrer"
34+
className="text-[9px] uppercase tracking-[0.15em] text-muted-foreground/70 hover:text-foreground transition-colors cursor-crosshair"
2735
>
2836
Feedback
29-
</button>
37+
</a>
38+
<span className="text-muted-foreground/20 text-[9px]">/</span>
39+
<span className="text-[9px] text-muted-foreground/30 font-mono">v1.0</span>
3040
</div>
3141
</div>
3242
)

0 commit comments

Comments
 (0)