All files / web/src/components/studio SegmentedControl.tsx

0% Statements 0/89
0% Branches 0/1
0% Functions 0/1
0% Lines 0/89

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90                                                                                                                                                                                   
'use client'

// SegmentedControl — the studio's dark, cyan-active pill toggle (Gitea epic #5,
// full-bleed CP1a). Factored from the viewer's hand-rolled "My colors / Print
// preview" lens so the same primitive drives the fabrication-target chooser (the
// old fork), the preview lens, and — later — the start-policy toggle. Styled
// from theme.ts, which owns the one cyan gradient the studio is allowed to use
// (the local copy of it is gone).

import { STUDIO } from './theme'

export interface SegmentedOption<T extends string> {
  value: T
  label: string
  /** optional leading glyph (emoji/icon char) */
  icon?: string
}

export interface SegmentedControlProps<T extends string> {
  options: readonly SegmentedOption<T>[]
  value: T
  onChange: (v: T) => void
  /** required — the group needs an accessible name */
  ariaLabel: string
  /** data-element on the group wrapper */
  dataElement?: string
  /** data-action stamped on each segment button */
  dataAction?: string
  /** compact (preview lens) vs larger (target chooser) */
  size?: 'sm' | 'md'
}

export function SegmentedControl<T extends string>({
  options,
  value,
  onChange,
  ariaLabel,
  dataElement = 'segmented-control',
  dataAction = 'segmented-select',
  size = 'sm',
}: SegmentedControlProps<T>) {
  const pad = size === 'md' ? '10px' : '7px 8px'
  const fontSize = size === 'md' ? 12 : 11
  return (
    <div
      data-component="segmented-control"
      data-element={dataElement}
      role="group"
      aria-label={ariaLabel}
      style={{
        display: 'flex',
        borderRadius: STUDIO.radius.segmented,
        overflow: 'hidden',
        border: `1px solid ${STUDIO.color.borderStrong}`,
      }}
    >
      {options.map((opt) => {
        const active = opt.value === value
        return (
          <button
            key={opt.value}
            type="button"
            data-action={dataAction}
            data-value={opt.value}
            aria-pressed={active}
            onClick={() => onChange(opt.value)}
            style={{
              flex: 1,
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              gap: 6,
              padding: pad,
              border: 'none',
              background: active ? STUDIO.gradient.accent : 'transparent',
              color: active ? STUDIO.color.onAccent : STUDIO.color.text2,
              fontSize,
              fontWeight: 600,
              cursor: 'pointer',
            }}
          >
            {opt.icon && <span aria-hidden="true">{opt.icon}</span>}
            {opt.label}
          </button>
        )
      })}
    </div>
  )
}