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:
111
apps/web/src/app/create/worksheets/components/ConfigSidebar.tsx
Normal file
111
apps/web/src/app/create/worksheets/components/ConfigSidebar.tsx
Normal file
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user