feat: add dynamic operator icon to tab navigation
Make Operator tab icon reflect current operator setting: - Addition only: ➕ - Subtraction only: ➖ - Mixed (both): ± (larger and bold for visibility) Tab icon updates in real-time as user toggles checkboxes. Provides immediate visual feedback of operator selection. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c997c4a7ba
commit
b6ff995a8c
|
|
@ -0,0 +1,111 @@
|
|||
'use client'
|
||||
|
||||
import { css } from '@styled/css'
|
||||
import { useState } from 'react'
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import { StudentNameInput } from './config-panel/StudentNameInput'
|
||||
import { ContentTab } from './config-sidebar/ContentTab'
|
||||
import { DifficultyTab } from './config-sidebar/DifficultyTab'
|
||||
import { LayoutTab } from './config-sidebar/LayoutTab'
|
||||
import { ScaffoldingTab } from './config-sidebar/ScaffoldingTab'
|
||||
import { TabNavigation } from './config-sidebar/TabNavigation'
|
||||
import { useWorksheetConfig } from './WorksheetConfigContext'
|
||||
|
||||
interface ConfigSidebarProps {
|
||||
isSaving: boolean
|
||||
lastSaved: Date | null
|
||||
}
|
||||
|
||||
export function ConfigSidebar({ isSaving, lastSaved }: ConfigSidebarProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
const [activeTab, setActiveTab] = useState('operator')
|
||||
const { formState, onChange } = useWorksheetConfig()
|
||||
|
||||
return (
|
||||
<div
|
||||
data-component="config-sidebar"
|
||||
className={css({
|
||||
h: 'full',
|
||||
bg: isDark ? 'gray.800' : 'white',
|
||||
p: '4',
|
||||
overflow: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
})}
|
||||
>
|
||||
{/* Header */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
pb: '3',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: isDark ? 'gray.700' : 'gray.200',
|
||||
mb: '4',
|
||||
})}
|
||||
>
|
||||
<h2
|
||||
className={css({
|
||||
fontSize: 'lg',
|
||||
fontWeight: 'bold',
|
||||
color: isDark ? 'gray.100' : 'gray.900',
|
||||
})}
|
||||
>
|
||||
Worksheet Studio
|
||||
</h2>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
color: isSaving
|
||||
? isDark
|
||||
? 'gray.400'
|
||||
: 'gray.500'
|
||||
: isDark
|
||||
? 'green.400'
|
||||
: 'green.600',
|
||||
})}
|
||||
>
|
||||
{isSaving ? 'Saving...' : lastSaved ? '✓ Saved' : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Student Name - Global field above tabs */}
|
||||
<div className={css({ mb: '4' })}>
|
||||
<StudentNameInput
|
||||
value={formState.name}
|
||||
onChange={(name) => onChange({ name })}
|
||||
isDark={isDark}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Tab Navigation */}
|
||||
<div className={css({ mb: '4' })}>
|
||||
<TabNavigation
|
||||
activeTab={activeTab}
|
||||
onChange={setActiveTab}
|
||||
operator={formState.operator}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div
|
||||
className={css({
|
||||
flex: 1,
|
||||
bg: isDark ? 'gray.750' : 'gray.50',
|
||||
rounded: 'lg',
|
||||
p: '4',
|
||||
overflow: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
})}
|
||||
>
|
||||
{activeTab === 'operator' && <ContentTab />}
|
||||
{activeTab === 'layout' && <LayoutTab />}
|
||||
{activeTab === 'scaffolding' && <ScaffoldingTab />}
|
||||
{activeTab === 'difficulty' && <DifficultyTab />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
'use client'
|
||||
|
||||
import { css } from '@styled/css'
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
|
||||
export interface Tab {
|
||||
id: string
|
||||
label: string
|
||||
icon: string | ((operator?: 'addition' | 'subtraction' | 'mixed') => string)
|
||||
}
|
||||
|
||||
export const TABS: Tab[] = [
|
||||
{
|
||||
id: 'operator',
|
||||
label: 'Operator',
|
||||
icon: (operator) => {
|
||||
if (operator === 'mixed') return '±'
|
||||
if (operator === 'subtraction') return '➖'
|
||||
return '➕'
|
||||
},
|
||||
},
|
||||
{ id: 'layout', label: 'Layout', icon: '📐' },
|
||||
{ id: 'scaffolding', label: 'Scaffolding', icon: '🎨' },
|
||||
{ id: 'difficulty', label: 'Difficulty', icon: '📊' },
|
||||
]
|
||||
|
||||
interface TabNavigationProps {
|
||||
activeTab: string
|
||||
onChange: (tabId: string) => void
|
||||
operator?: 'addition' | 'subtraction' | 'mixed'
|
||||
}
|
||||
|
||||
export function TabNavigation({ activeTab, onChange, operator }: TabNavigationProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
|
||||
const getTabIcon = (tab: Tab) => {
|
||||
if (typeof tab.icon === 'function') {
|
||||
return tab.icon(operator)
|
||||
}
|
||||
return tab.icon
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-component="tab-navigation"
|
||||
className={css({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: '2',
|
||||
})}
|
||||
>
|
||||
{TABS.map((tab) => {
|
||||
const icon = getTabIcon(tab)
|
||||
const isPlusMinus = icon === '±'
|
||||
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
data-action={`select-tab-${tab.id}`}
|
||||
onClick={() => onChange(tab.id)}
|
||||
className={css({
|
||||
px: '3',
|
||||
py: '2',
|
||||
rounded: 'lg',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'medium',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s',
|
||||
bg:
|
||||
activeTab === tab.id
|
||||
? isDark
|
||||
? 'brand.900'
|
||||
: 'brand.50'
|
||||
: isDark
|
||||
? 'gray.700'
|
||||
: 'gray.100',
|
||||
color:
|
||||
activeTab === tab.id
|
||||
? isDark
|
||||
? 'brand.200'
|
||||
: 'brand.700'
|
||||
: isDark
|
||||
? 'gray.300'
|
||||
: 'gray.600',
|
||||
border: '2px solid',
|
||||
borderColor: activeTab === tab.id ? 'brand.500' : 'transparent',
|
||||
_hover: {
|
||||
borderColor: 'brand.400',
|
||||
},
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1.5',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
className={css({
|
||||
fontSize: isPlusMinus ? 'lg' : undefined,
|
||||
fontWeight: isPlusMinus ? 'bold' : undefined,
|
||||
})}
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
<span>{tab.label}</span>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue