Revert "feat(ui): support icon-only segmented controls"
This reverts commit 2e05fcf524.
This commit is contained in:
@@ -231,8 +231,7 @@ blurred backdrop.
|
||||
Empty lists hide their search field.
|
||||
- **`SegmentedControl`** — the choice control for small mutually-exclusive sets
|
||||
(color mode, tool-call display, usage period). Replaces radio piles and
|
||||
pill rows. `iconOnly` renders compact icon buttons with label tooltips and
|
||||
accessible names; use `codiconIcon()` for Codicon options.
|
||||
pill rows.
|
||||
- **`Switch`** (`size="xs"`) — bare, with `aria-label`. No bordered text wrapper.
|
||||
- **`FanMenu`** (`src/components/ui/fan-menu.tsx`) — one hub control that
|
||||
fans sibling toggles out on hover: `direction` `vertical` | `horizontal`
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { codiconIcon } from './codicon'
|
||||
import { SegmentedControl } from './segmented-control'
|
||||
|
||||
const options = [
|
||||
{ id: 'list', label: 'List view', icon: codiconIcon('list-unordered') },
|
||||
{ id: 'cards', label: 'Card view', icon: codiconIcon('extensions') }
|
||||
]
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
describe('SegmentedControl', () => {
|
||||
it('preserves visible labels by default', () => {
|
||||
render(<SegmentedControl onChange={vi.fn()} options={options} value="cards" />)
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Card view', pressed: true }).textContent).toBe('Card view')
|
||||
expect(screen.getByRole('button', { name: 'List view', pressed: false }).textContent).toBe('List view')
|
||||
})
|
||||
|
||||
it('keeps accessible labels and selection behavior for icon-only controls', () => {
|
||||
const onChange = vi.fn()
|
||||
render(<SegmentedControl iconOnly onChange={onChange} options={options} value="cards" />)
|
||||
const list = screen.getByRole('button', { name: 'List view', pressed: false })
|
||||
const cards = screen.getByRole('button', { name: 'Card view', pressed: true })
|
||||
|
||||
expect(list.textContent).toBe('')
|
||||
expect(cards.textContent).toBe('')
|
||||
expect(list.querySelector('.codicon-list-unordered')).not.toBeNull()
|
||||
expect(cards.querySelector('.codicon-extensions')).not.toBeNull()
|
||||
fireEvent.click(list)
|
||||
expect(onChange).toHaveBeenCalledExactlyOnceWith('list')
|
||||
})
|
||||
|
||||
it('retains a visible label when an icon-only option has no icon', () => {
|
||||
render(<SegmentedControl iconOnly onChange={vi.fn()} options={[{ id: 'list', label: 'List view' }]} value="list" />)
|
||||
|
||||
expect(screen.getByRole('button', { name: 'List view' }).textContent).toBe('List view')
|
||||
})
|
||||
|
||||
it('does not change a disabled icon-only control', () => {
|
||||
const onChange = vi.fn()
|
||||
render(<SegmentedControl disabled iconOnly onChange={onChange} options={options} value="cards" />)
|
||||
const list = screen.getByRole<HTMLButtonElement>('button', { name: 'List view' })
|
||||
|
||||
expect(list.disabled).toBe(true)
|
||||
fireEvent.click(list)
|
||||
expect(onChange).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Tip } from '@/components/ui/tooltip'
|
||||
import type { IconComponent } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
@@ -13,8 +12,6 @@ interface SegmentedControlProps<T extends string> {
|
||||
value: T
|
||||
onChange: (id: T) => void
|
||||
className?: string
|
||||
/** Icon buttons retain localized labels for tooltips and screen readers. */
|
||||
iconOnly?: boolean
|
||||
/** Dims the whole track and blocks selection (e.g. gated behind a prerequisite). */
|
||||
disabled?: boolean
|
||||
}
|
||||
@@ -27,7 +24,6 @@ interface SegmentedControlProps<T extends string> {
|
||||
export function SegmentedControl<T extends string>({
|
||||
className,
|
||||
disabled = false,
|
||||
iconOnly = false,
|
||||
onChange,
|
||||
options,
|
||||
value
|
||||
@@ -43,14 +39,12 @@ export function SegmentedControl<T extends string>({
|
||||
{options.map(({ id, label, icon: Icon }) => {
|
||||
const active = value === id
|
||||
|
||||
const button = (
|
||||
return (
|
||||
<button
|
||||
aria-label={iconOnly ? label : undefined}
|
||||
aria-pressed={active}
|
||||
className={cn(
|
||||
'flex items-center justify-center gap-1 rounded-[3px] px-2.5 py-0.5 text-[0.6875rem] font-medium transition-colors disabled:cursor-default',
|
||||
active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',
|
||||
iconOnly && Icon && 'size-6 p-0 text-sm'
|
||||
active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'
|
||||
)}
|
||||
disabled={disabled}
|
||||
key={id}
|
||||
@@ -58,11 +52,9 @@ export function SegmentedControl<T extends string>({
|
||||
type="button"
|
||||
>
|
||||
{Icon && <Icon className="size-3" />}
|
||||
{(!iconOnly || !Icon) && label}
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
|
||||
return iconOnly && Icon ? <Tip key={id} label={label}>{button}</Tip> : button
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user