Merge pull request #106 from findthelorax/fix/zone-styling

fix: zone styling
This commit is contained in:
Rémy
2026-05-14 15:58:45 +02:00
committed by GitHub
3 changed files with 86 additions and 4 deletions
@@ -0,0 +1,41 @@
/* Slider container: strip native chrome so custom track/thumb align cleanly */
.slider-thumb {
-webkit-appearance: none;
appearance: none;
background: transparent;
height: 13px; /* match thumb height so vertical centering is the input's box center */
}
.slider-thumb:focus {
outline: none;
}
/* Track */
.slider-accent::-webkit-slider-runnable-track {
height: 4px;
background: #00d4ff;
border-radius: 2px;
}
.slider-accent::-moz-range-track {
height: 4px;
background: #00d4ff;
border-radius: 2px;
}
/* Thumb — must offset on webkit so it centers on the 4px track */
.slider-thumb::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 13px;
height: 13px;
border-radius: 50%;
background: #00d4ff;
border: 2px solid #21262d;
margin-top: -4.5px; /* (13 - 4) / 2 */
}
.slider-thumb::-moz-range-thumb {
width: 13px;
height: 13px;
border-radius: 50%;
background: #00d4ff;
border: 2px solid #21262d;
}
@@ -7,6 +7,7 @@ import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import type { TextPosition } from '@/types'
import { hexToRgba, rgbaToHex8 } from '@/utils/colorUtils'
import styles from './GroupRectModal.module.css'
export type BorderStyle = 'solid' | 'dashed' | 'dotted' | 'double' | 'none'
@@ -88,6 +89,8 @@ const TEXT_POSITIONS: { value: TextPosition; label: string }[] = [
{ value: 'bottom-right', label: '↘' },
]
const getFontLabel = (value: string) => FONTS.find((f) => f.value === value)?.label ?? value
interface GroupRectModalProps {
open: boolean
onClose: () => void
@@ -138,8 +141,10 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
<div className="flex flex-col gap-1.5">
<Label className="text-xs text-muted-foreground">Font</Label>
<Select value={form.font} onValueChange={(v: string | null) => set('font', v ?? 'inter')}>
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 cursor-pointer ${modalStyles['modal-interactive']} ${modalStyles['modal-radius']}`}>
<SelectValue />
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 cursor-pointer ${modalStyles['modal-interactive']} ${modalStyles['modal-radius']}`} aria-label="Font selector">
<SelectValue>
{getFontLabel(form.font)}
</SelectValue>
</SelectTrigger>
<SelectContent className="bg-[#21262d] border-[#30363d]">
{FONTS.map((f) => (
@@ -164,6 +169,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
title={value}
onClick={() => set('text_position', value)}
className={`h-8 rounded text-base transition-colors cursor-pointer ${modalStyles['modal-interactive']}`}
aria-label={`Text position ${label}`}
style={{
background: isSelected ? '#00d4ff22' : '#21262d',
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
@@ -189,6 +195,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
type="button"
onClick={() => set('label_position', value)}
className={`flex items-center justify-center h-8 rounded text-xs transition-colors cursor-pointer ${modalStyles['modal-interactive']}`}
aria-label={`Label position ${label}`}
style={{
background: isSelected ? '#00d4ff22' : '#21262d',
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
@@ -228,7 +235,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
max={100}
value={alpha}
onChange={(e) => set(key, rgbaToHex8(hex6, Number(e.target.value)))}
className="w-full h-1 accent-[#00d4ff] cursor-pointer"
className={`w-full cursor-pointer mt-2 ${styles['slider-thumb']} ${styles['slider-accent']}`}
title={`Opacity: ${alpha}%`}
/>
<span className="text-[9px] text-muted-foreground/60">{label} {alpha}%</span>
@@ -250,6 +257,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
type="button"
onClick={() => set('text_size', value)}
className={`flex items-center justify-center h-8 rounded transition-colors cursor-pointer ${modalStyles['modal-interactive']}`}
aria-label={`Text size ${label}`}
style={{
background: isSelected ? '#00d4ff22' : '#21262d',
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
@@ -277,6 +285,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
title={label}
onClick={() => set('border_style', value)}
className={`flex flex-col items-center justify-center h-10 rounded text-xs gap-0.5 transition-colors cursor-pointer ${modalStyles['modal-interactive']}`}
aria-label={`Border style ${label}`}
style={{
background: isSelected ? '#00d4ff22' : '#21262d',
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
@@ -303,6 +312,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
type="button"
onClick={() => set('border_width', value)}
className={`flex items-center justify-center h-8 rounded text-xs transition-colors cursor-pointer ${modalStyles['modal-interactive']}`}
aria-label={`Border width ${label}`}
style={{
background: isSelected ? '#00d4ff22' : '#21262d',
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
@@ -320,7 +330,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
<div className="flex flex-col gap-1.5">
<Label className="text-xs text-muted-foreground">Z-Order (1 = furthest back)</Label>
<Select value={String(form.z_order)} onValueChange={(v: string | null) => set('z_order', v !== null ? Number(v) : 1)}>
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 cursor-pointer ${modalStyles['modal-interactive']}`}>
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 cursor-pointer ${modalStyles['modal-interactive']}`} aria-label="Z-order selector">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-[#21262d] border-[#30363d]">
@@ -37,6 +37,16 @@ describe('GroupRectModal', () => {
expect(submitted.z_order).toBe(1)
})
it('exposes aria-labels on grid buttons and select triggers', () => {
render(<GroupRectModal open onClose={vi.fn()} onSubmit={vi.fn()} />)
expect(screen.getByLabelText('Font selector')).toBeDefined()
expect(screen.getByLabelText('Z-order selector')).toBeDefined()
expect(screen.getByLabelText('Text position ↘')).toBeDefined()
expect(screen.getByLabelText('Label position Inside')).toBeDefined()
expect(screen.getByLabelText('Border style Solid')).toBeDefined()
expect(screen.getByLabelText('Border width 1px')).toBeDefined()
})
it('calls onClose when Cancel is clicked', () => {
const onClose = vi.fn()
render(<GroupRectModal open onClose={onClose} onSubmit={vi.fn()} />)
@@ -308,3 +318,24 @@ describe('GroupRectModal', () => {
expect(screen.getByText(/Background 5%/)).toBeInTheDocument()
})
})
describe('GroupRectModal font label rendering', () => {
it('renders the human font label in the Select trigger (default inter)', () => {
render(<GroupRectModal open onClose={vi.fn()} onSubmit={vi.fn()} />)
const trigger = screen.getByLabelText('Font selector')
expect(trigger.textContent).toContain('Inter (sans-serif)')
})
it('falls back to raw value when font is unknown', () => {
render(
<GroupRectModal
open
onClose={vi.fn()}
onSubmit={vi.fn()}
initial={{ font: 'comic-sans-9000' }}
/>
)
const trigger = screen.getByLabelText('Font selector')
expect(trigger.textContent).toContain('comic-sans-9000')
})
})