feat(pilot): ChatTabStrip component — [Chat] [Script Builder ●]
Two-tab strip for the chat region. Parent controls mounting (strip only appears when the fix needs a script drafted). Indicator dot signals in-progress draft state. Tab switching via onChange callback; parent handles display:none toggling so tab contents preserve state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
79
frontend/src/components/pilot/ChatTabStrip.tsx
Normal file
79
frontend/src/components/pilot/ChatTabStrip.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* ChatTabStrip — two-tab strip at the top of the chat region:
|
||||
* [Chat] [Script Builder ●]
|
||||
*
|
||||
* Visibility is controlled by the parent (AssistantChatPage) — this
|
||||
* component renders whenever it's mounted. The parent decides whether
|
||||
* to mount it based on fix state.
|
||||
*
|
||||
* Tab switching uses onChange; the parent toggles display:none on the
|
||||
* tab contents so state is preserved across switches.
|
||||
*/
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export type ChatTab = 'chat' | 'script_builder'
|
||||
|
||||
export interface ChatTabStripProps {
|
||||
active: ChatTab
|
||||
onChange: (tab: ChatTab) => void
|
||||
/** When true, shows the amber indicator dot on the Script Builder tab. */
|
||||
scriptBuilderHasProgress?: boolean
|
||||
}
|
||||
|
||||
export function ChatTabStrip({
|
||||
active, onChange, scriptBuilderHasProgress,
|
||||
}: ChatTabStripProps) {
|
||||
return (
|
||||
<div
|
||||
role="tablist"
|
||||
className="flex gap-1 px-4 pt-2 border-b border-default bg-bg-sidebar"
|
||||
>
|
||||
<TabButton
|
||||
label="Chat"
|
||||
active={active === 'chat'}
|
||||
onClick={() => onChange('chat')}
|
||||
/>
|
||||
<TabButton
|
||||
label="Script Builder"
|
||||
active={active === 'script_builder'}
|
||||
onClick={() => onChange('script_builder')}
|
||||
indicator={scriptBuilderHasProgress}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TabButton({
|
||||
label, active, onClick, indicator,
|
||||
}: {
|
||||
label: string
|
||||
active: boolean
|
||||
onClick: () => void
|
||||
indicator?: boolean
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
role="tab"
|
||||
aria-selected={active}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'relative px-3 py-[7px] text-[12.5px] font-medium rounded-t-md transition-colors',
|
||||
'border-b-2 -mb-px',
|
||||
active
|
||||
? 'text-heading border-accent bg-bg-page'
|
||||
: 'text-muted-foreground border-transparent hover:text-primary hover:bg-white/[0.08]',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{indicator && (
|
||||
<span
|
||||
role="img"
|
||||
aria-label="unsaved progress"
|
||||
className="ml-1.5 inline-block w-1.5 h-1.5 rounded-full bg-warning align-middle"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChatTabStrip
|
||||
Reference in New Issue
Block a user