Skip to content

Commit 9f7c08d

Browse files
committed
Unify form labels: FieldLabel component above controls instead of floating labels
Replace MUI floating InputLabel with FieldLabel placed above inputs for consistent, readable forms. Affects NoteForm, TaskForm, SkillForm, and Tools playground. FieldLabel: uppercase, semibold, text.secondary color.
1 parent 4c4f142 commit 9f7c08d

7 files changed

Lines changed: 108 additions & 86 deletions

File tree

ui/src/app/theme.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,19 @@ export const lightTheme = createTheme({
186186
},
187187
typography,
188188
components: {
189+
MuiInputLabel: {
190+
styleOverrides: {
191+
root: {
192+
fontSize: '0.8125rem',
193+
textTransform: 'uppercase' as const,
194+
letterSpacing: '0.04em',
195+
fontWeight: 500,
196+
'&.MuiInputLabel-shrink': {
197+
transform: 'translate(14px, -9px) scale(0.85)',
198+
},
199+
},
200+
},
201+
},
189202
MuiDrawer: {
190203
styleOverrides: {
191204
paper: {

ui/src/features/note-crud/NoteForm.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState, useEffect } from 'react';
2-
import { Box, Button, TextField, CircularProgress, Typography } from '@mui/material';
3-
import { Section, FormGrid, FormField, Tags, MarkdownEditor } from '@/shared/ui/index.ts';
2+
import { Box, Button, TextField, CircularProgress } from '@mui/material';
3+
import { Section, FormGrid, FormField, FieldLabel, Tags, MarkdownEditor } from '@/shared/ui/index.ts';
44
import type { Note } from '@/entities/note/index.ts';
55

66
interface NoteFormProps {
@@ -43,18 +43,18 @@ export function NoteForm({ note, onSubmit, onCancel, submitLabel = 'Save' }: Not
4343
<Section title="Details">
4444
<FormGrid>
4545
<FormField fullWidth>
46+
<FieldLabel required>Title</FieldLabel>
4647
<TextField
4748
autoFocus
4849
fullWidth
49-
label="Title"
5050
value={title}
5151
onChange={e => { setTitle(e.target.value); setTitleError(false); }}
5252
error={titleError}
5353
helperText={titleError ? 'Title is required' : undefined}
5454
/>
5555
</FormField>
5656
<FormField fullWidth>
57-
<Typography variant="caption" color="text.secondary" sx={{ mb: 0.5 }}>Content</Typography>
57+
<FieldLabel>Content</FieldLabel>
5858
<MarkdownEditor value={content} onChange={setContent} height={300} />
5959
</FormField>
6060
<FormField fullWidth>

ui/src/features/skill-crud/SkillForm.tsx

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { useState, useEffect } from 'react';
22
import {
3-
Box, Button, TextField, FormControl, InputLabel, Select, MenuItem,
4-
CircularProgress, Typography, IconButton, Slider,
3+
Box, Button, TextField, Select, MenuItem, Typography,
4+
CircularProgress, IconButton, Slider,
55
} from '@mui/material';
66
import AddIcon from '@mui/icons-material/Add';
77
import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline';
8-
import { Section, FormGrid, FormField, Tags, MarkdownEditor } from '@/shared/ui/index.ts';
8+
import { Section, FormGrid, FormField, FieldLabel, Tags, MarkdownEditor } from '@/shared/ui/index.ts';
99
import type { Skill } from '@/entities/skill/index.ts';
1010

1111
interface SkillFormProps {
@@ -92,18 +92,18 @@ export function SkillForm({ skill, onSubmit, onCancel, submitLabel = 'Save' }: S
9292
<Section title="Details">
9393
<FormGrid>
9494
<FormField fullWidth>
95+
<FieldLabel required>Title</FieldLabel>
9596
<TextField
9697
autoFocus
9798
fullWidth
98-
label="Title"
9999
value={title}
100100
onChange={e => { setTitle(e.target.value); setTitleError(false); }}
101101
error={titleError}
102102
helperText={titleError ? 'Title is required' : undefined}
103103
/>
104104
</FormField>
105105
<FormField fullWidth>
106-
<Typography variant="caption" color="text.secondary" sx={{ mb: 0.5 }}>Description</Typography>
106+
<FieldLabel>Description</FieldLabel>
107107
<MarkdownEditor value={description} onChange={setDescription} height={200} />
108108
</FormField>
109109
</FormGrid>
@@ -139,7 +139,7 @@ export function SkillForm({ skill, onSubmit, onCancel, submitLabel = 'Save' }: S
139139
<Section title="Matching">
140140
<FormGrid>
141141
<FormField fullWidth>
142-
<Typography variant="caption" color="text.secondary" sx={{ mb: 0.5 }}>Triggers</Typography>
142+
<FieldLabel>Triggers</FieldLabel>
143143
<Tags
144144
tags={triggers}
145145
editable
@@ -148,7 +148,7 @@ export function SkillForm({ skill, onSubmit, onCancel, submitLabel = 'Save' }: S
148148
/>
149149
</FormField>
150150
<FormField fullWidth>
151-
<Typography variant="caption" color="text.secondary" sx={{ mb: 0.5 }}>Input Hints</Typography>
151+
<FieldLabel>Input Hints</FieldLabel>
152152
<Tags
153153
tags={inputHints}
154154
editable
@@ -157,7 +157,7 @@ export function SkillForm({ skill, onSubmit, onCancel, submitLabel = 'Save' }: S
157157
/>
158158
</FormField>
159159
<FormField fullWidth>
160-
<Typography variant="caption" color="text.secondary" sx={{ mb: 0.5 }}>File Patterns</Typography>
160+
<FieldLabel>File Patterns</FieldLabel>
161161
<Tags
162162
tags={filePatterns}
163163
editable
@@ -171,19 +171,15 @@ export function SkillForm({ skill, onSubmit, onCancel, submitLabel = 'Save' }: S
171171
<Section title="Properties">
172172
<FormGrid>
173173
<FormField>
174-
<FormControl fullWidth>
175-
<InputLabel>Source</InputLabel>
176-
<Select value={source} label="Source" onChange={e => setSource(e.target.value as 'user' | 'learned')}>
177-
<MenuItem value="user">User</MenuItem>
178-
<MenuItem value="learned">Learned</MenuItem>
179-
</Select>
180-
</FormControl>
174+
<FieldLabel>Source</FieldLabel>
175+
<Select fullWidth value={source} onChange={e => setSource(e.target.value as 'user' | 'learned')}>
176+
<MenuItem value="user">User</MenuItem>
177+
<MenuItem value="learned">Learned</MenuItem>
178+
</Select>
181179
</FormField>
182180
<FormField>
183181
<Box sx={{ px: 1 }}>
184-
<Typography variant="caption" color="text.secondary" gutterBottom>
185-
Confidence: {Math.round(confidence * 100)}%
186-
</Typography>
182+
<FieldLabel>Confidence: {Math.round(confidence * 100)}%</FieldLabel>
187183
<Slider
188184
value={confidence}
189185
onChange={(_e, v) => setConfidence(v as number)}

ui/src/features/task-crud/TaskForm.tsx

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { useState, useEffect } from 'react';
22
import {
3-
Box, Button, TextField, FormControl, InputLabel, Select, MenuItem,
4-
CircularProgress, Typography,
3+
Box, Button, TextField, Select, MenuItem,
4+
CircularProgress,
55
} from '@mui/material';
6-
import { Section, FormGrid, FormField, Tags, MarkdownEditor } from '@/shared/ui/index.ts';
6+
import { Section, FormGrid, FormField, FieldLabel, Tags, MarkdownEditor } from '@/shared/ui/index.ts';
77
import { COLUMNS, type Task, type TaskStatus, type TaskPriority } from '@/entities/task/index.ts';
88

99
interface TaskFormProps {
@@ -62,18 +62,18 @@ export function TaskForm({ task, onSubmit, onCancel, submitLabel = 'Save' }: Tas
6262
<Section title="Details">
6363
<FormGrid>
6464
<FormField fullWidth>
65+
<FieldLabel required>Title</FieldLabel>
6566
<TextField
6667
autoFocus
6768
fullWidth
68-
label="Title"
6969
value={title}
7070
onChange={e => { setTitle(e.target.value); setTitleError(false); }}
7171
error={titleError}
7272
helperText={titleError ? 'Title is required' : undefined}
7373
/>
7474
</FormField>
7575
<FormField fullWidth>
76-
<Typography variant="caption" color="text.secondary" sx={{ mb: 0.5 }}>Description</Typography>
76+
<FieldLabel>Description</FieldLabel>
7777
<MarkdownEditor value={description} onChange={setDescription} height={250} />
7878
</FormField>
7979
</FormGrid>
@@ -82,38 +82,33 @@ export function TaskForm({ task, onSubmit, onCancel, submitLabel = 'Save' }: Tas
8282
<Section title="Properties">
8383
<FormGrid>
8484
<FormField>
85-
<FormControl fullWidth>
86-
<InputLabel>Status</InputLabel>
87-
<Select value={status} label="Status" onChange={e => setStatus(e.target.value as TaskStatus)}>
88-
{COLUMNS.map(c => <MenuItem key={c.status} value={c.status}>{c.label}</MenuItem>)}
89-
</Select>
90-
</FormControl>
85+
<FieldLabel>Status</FieldLabel>
86+
<Select fullWidth value={status} onChange={e => setStatus(e.target.value as TaskStatus)}>
87+
{COLUMNS.map(c => <MenuItem key={c.status} value={c.status}>{c.label}</MenuItem>)}
88+
</Select>
9189
</FormField>
9290
<FormField>
93-
<FormControl fullWidth>
94-
<InputLabel>Priority</InputLabel>
95-
<Select value={priority} label="Priority" onChange={e => setPriority(e.target.value as TaskPriority)}>
96-
<MenuItem value="critical">Critical</MenuItem>
97-
<MenuItem value="high">High</MenuItem>
98-
<MenuItem value="medium">Medium</MenuItem>
99-
<MenuItem value="low">Low</MenuItem>
100-
</Select>
101-
</FormControl>
91+
<FieldLabel>Priority</FieldLabel>
92+
<Select fullWidth value={priority} onChange={e => setPriority(e.target.value as TaskPriority)}>
93+
<MenuItem value="critical">Critical</MenuItem>
94+
<MenuItem value="high">High</MenuItem>
95+
<MenuItem value="medium">Medium</MenuItem>
96+
<MenuItem value="low">Low</MenuItem>
97+
</Select>
10298
</FormField>
10399
<FormField>
100+
<FieldLabel>Due Date</FieldLabel>
104101
<TextField
105102
fullWidth
106-
label="Due Date"
107103
type="date"
108104
value={dueDate}
109105
onChange={e => setDueDate(e.target.value)}
110-
slotProps={{ inputLabel: { shrink: true } }}
111106
/>
112107
</FormField>
113108
<FormField>
109+
<FieldLabel>Estimate (hours)</FieldLabel>
114110
<TextField
115111
fullWidth
116-
label="Estimate (hours)"
117112
type="number"
118113
value={estimate}
119114
onChange={e => setEstimate(e.target.value)}

ui/src/pages/tools/[toolName].tsx

Lines changed: 31 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { useParams, useNavigate } from 'react-router-dom';
33
import {
44
Box, Typography, TextField, Button, Table, TableBody, TableCell,
55
TableContainer, TableHead, TableRow, useTheme, CircularProgress,
6-
Switch, FormControlLabel, Chip, Alert,
6+
Switch, Chip, Alert,
77
} from '@mui/material';
88
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
99
import BuildIcon from '@mui/icons-material/Build';
1010
import MenuBookIcon from '@mui/icons-material/MenuBook';
11-
import { PageTopBar, Section, StatusBadge, CopyButton, EmptyState } from '@/shared/ui/index.ts';
11+
import { PageTopBar, Section, StatusBadge, CopyButton, EmptyState, FieldLabel } from '@/shared/ui/index.ts';
1212
import { getTool, callTool, type ToolInfo, type ToolCallResult, type JsonSchemaProperty } from '@/entities/tool/index.ts';
1313
import { getArticlesForTool } from '@/content/help/index.ts';
1414

@@ -227,52 +227,42 @@ export default function ToolDetailPage() {
227227
Object.entries(properties).map(([name, prop]) => {
228228
if (prop.type === 'boolean') {
229229
return (
230-
<FormControlLabel
231-
key={name}
232-
control={
233-
<Switch
234-
checked={args[name] === 'true'}
235-
onChange={(e) => setArgs(prev => ({ ...prev, [name]: String(e.target.checked) }))}
236-
size="small"
237-
/>
238-
}
239-
label={
240-
<Typography variant="body2">
241-
<Box component="span" sx={{ fontFamily: 'monospace', fontWeight: 600, mr: 0.5 }}>{name}</Box>
242-
{!required.has(name) && (
243-
<Box component="span" sx={{ color: palette.custom.textMuted }}>(optional)</Box>
244-
)}
245-
</Typography>
246-
}
247-
/>
230+
<Box key={name}>
231+
<FieldLabel>{name}</FieldLabel>
232+
<Switch
233+
checked={args[name] === 'true'}
234+
onChange={(e) => setArgs(prev => ({ ...prev, [name]: String(e.target.checked) }))}
235+
size="small"
236+
/>
237+
</Box>
248238
);
249239
}
250240

251241
const isMultiline = prop.type === 'array' || prop.type === 'object' ||
252242
(name === 'content' || name === 'description');
253243

254244
return (
255-
<TextField
256-
key={name}
257-
label={`${name}${required.has(name) ? ' *' : ''}`}
258-
value={args[name] || ''}
259-
onChange={(e) => setArgs(prev => ({ ...prev, [name]: e.target.value }))}
260-
size="small"
261-
fullWidth
262-
multiline={isMultiline}
263-
minRows={isMultiline ? 2 : undefined}
264-
helperText={prop.description}
265-
placeholder={
266-
prop.enum ? prop.enum.join(' | ') :
267-
prop.type === 'array' ? '["item1", "item2"] or item1, item2' :
268-
prop.type === 'number' || prop.type === 'integer' ? '0' :
269-
undefined
270-
}
271-
slotProps={{
272-
inputLabel: { shrink: true },
273-
input: { sx: { fontFamily: 'monospace', fontSize: '0.875rem' } },
274-
}}
275-
/>
245+
<Box key={name}>
246+
<FieldLabel required={required.has(name)}>{name}</FieldLabel>
247+
<TextField
248+
value={args[name] || ''}
249+
onChange={(e) => setArgs(prev => ({ ...prev, [name]: e.target.value }))}
250+
size="small"
251+
fullWidth
252+
multiline={isMultiline}
253+
minRows={isMultiline ? 2 : undefined}
254+
helperText={prop.description}
255+
placeholder={
256+
prop.enum ? prop.enum.join(' | ') :
257+
prop.type === 'array' ? '["item1", "item2"] or item1, item2' :
258+
prop.type === 'number' || prop.type === 'integer' ? '0' :
259+
undefined
260+
}
261+
slotProps={{
262+
input: { sx: { fontFamily: 'monospace', fontSize: '0.875rem' } },
263+
}}
264+
/>
265+
</Box>
276266
);
277267
})
278268
)}

ui/src/shared/ui/FieldLabel.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Typography, type SxProps } from '@mui/material'
2+
import type { ReactNode } from 'react'
3+
4+
interface FieldLabelProps {
5+
children: ReactNode
6+
required?: boolean
7+
sx?: SxProps
8+
}
9+
10+
export function FieldLabel({ children, required, sx }: FieldLabelProps) {
11+
return (
12+
<Typography
13+
variant="caption"
14+
sx={{
15+
display: 'block',
16+
mb: 0.75,
17+
fontWeight: 600,
18+
textTransform: 'uppercase',
19+
letterSpacing: '0.05em',
20+
color: 'text.secondary',
21+
...sx as object,
22+
}}
23+
>
24+
{children}{required && ' *'}
25+
</Typography>
26+
)
27+
}

ui/src/shared/ui/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export { FilterBar } from './FilterBar'
88
export { PaginationBar } from './PaginationBar'
99
export { FieldRow } from './FieldRow'
1010
export { FormGrid, FormField } from './FormGrid'
11+
export { FieldLabel } from './FieldLabel'
1112
export { EmptyState } from './EmptyState'
1213
export { ConfirmDialog } from './ConfirmDialog'
1314
export { MarkdownRenderer } from './MarkdownRenderer'

0 commit comments

Comments
 (0)