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