Skip to content

Commit 14036b9

Browse files
committed
feat: add radio component and set input value
1 parent 265f217 commit 14036b9

File tree

5 files changed

+213
-40
lines changed

5 files changed

+213
-40
lines changed

site/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"dependencies": {
1313
"@radix-ui/react-dialog": "^1.1.6",
1414
"@radix-ui/react-label": "^2.1.2",
15+
"@radix-ui/react-radio-group": "^1.2.3",
1516
"@radix-ui/react-select": "^2.1.6",
1617
"@radix-ui/react-slider": "^1.2.3",
1718
"@radix-ui/react-switch": "^1.1.3",

site/pnpm-lock.yaml

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/DynamicForm.tsx

Lines changed: 80 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import { Input } from "./components/Input/Input";
1313
import { Switch } from "./components/Switch/Switch";
1414
import { useUsers } from './hooks/useUsers';
1515
import { useDirectories } from './hooks/useDirectories';
16-
import { useDebouncedFunction } from './hooks/debounce';
1716
import { CollapsibleSummary } from "./components/CollapsibleSummary/CollapsibleSummary";
1817
import { Slider } from "./components/ui/slider";
1918
import ReactJson from 'react-json-view';
19+
import { RadioGroup, RadioGroupItem } from "./components/ui/radio-group"
20+
import { Label } from "./components/Label/Label";
2021

2122
export function DynamicForm() {
2223
const serverAddress = "localhost:8100";
@@ -43,8 +44,6 @@ export function DynamicForm() {
4344

4445
const {
4546
directories,
46-
// testdata,
47-
// setTestdata,
4847
isLoading,
4948
fetchError
5049
} = useDirectories(serverAddress, urlTestdata);
@@ -134,6 +133,8 @@ export function DynamicForm() {
134133
// Track previous values to detect changes
135134
const [prevValues, setPrevValues] = useState<Record<string, string>>({});
136135

136+
const [debouncedTimer, setDebouncedTimer] = useState<NodeJS.Timeout | null>(null);
137+
137138
useEffect(() => {
138139
if (!response) return;
139140

@@ -144,20 +145,36 @@ export function DynamicForm() {
144145
key => watchedValues[key] !== prevValues[key]
145146
);
146147
if (hasChanged) {
147-
setCurrentId(prevId => {
148-
const newId = prevId + 1;
149-
const request: Request = {
150-
id: newId,
151-
inputs: watchedValues
152-
};
153-
console.log("request", request);
154-
sendMessage(request);
155-
return newId;
156-
});
157-
148+
if (debouncedTimer) {
149+
clearTimeout(debouncedTimer);
150+
}
151+
152+
const timer = setTimeout(() => {
153+
setCurrentId(prevId => {
154+
const newId = prevId + 1;
155+
const request: Request = {
156+
id: newId,
157+
inputs: watchedValues
158+
};
159+
console.log("request", request);
160+
sendMessage(request);
161+
return newId;
162+
});
163+
}, 250);
164+
165+
setDebouncedTimer(timer);
158166
setPrevValues({...watchedValues});
159167
}
160-
}, [watchedValues, response, sendMessage, prevValues]);
168+
}, [watchedValues, response, sendMessage, prevValues, debouncedTimer]);
169+
170+
// Clean up the timer when component unmounts
171+
useEffect(() => {
172+
return () => {
173+
if (debouncedTimer) {
174+
clearTimeout(debouncedTimer);
175+
}
176+
};
177+
}, [debouncedTimer]);
161178

162179
const renderParameter = (param: Parameter) => {
163180
const controlType = param.form_type;
@@ -177,7 +194,7 @@ export function DynamicForm() {
177194
render={({ field }) => (
178195
<Select
179196
onValueChange={field.onChange}
180-
defaultValue={param.value}
197+
defaultValue={param.default_value}
181198
>
182199
<SelectTrigger className="w-[300px]">
183200
<SelectValue placeholder={param.description} />
@@ -219,11 +236,18 @@ export function DynamicForm() {
219236
const values = selectedOptions.map(opt => opt.value).join(',');
220237
field.onChange(values);
221238
}}
222-
defaultOptions={param.options?.map(opt => ({
239+
options={param.options?.map(opt => ({
223240
value: opt?.value || '',
224241
label: opt?.name || '',
225242
disabled: false
226243
})) || []}
244+
// defaultOptions={param.default_value ?
245+
// param.default_value.replace(/[[\]"]/g, '').split(',').map(value => ({
246+
// value,
247+
// label: param.options?.find(opt => opt?.value === value)?.name || value,
248+
// disabled: false
249+
// }))
250+
// : []}
227251
emptyIndicator={<p className="text-sm">No results found</p>}
228252
/>
229253
</div>
@@ -248,7 +272,7 @@ export function DynamicForm() {
248272
control={methods.control}
249273
render={({ field }) => (
250274
<div className="w-[300px]">
251-
<Slider defaultValue={field.value ? [Number(field.value)] : [0]} max={param.validations[0].validation_max || undefined} min={param.validations[0].validation_min || undefined} step={1}
275+
<Slider defaultValue={param.default_value ? [Number(param.default_value)] : [0]} max={param.validations[0].validation_max || undefined} min={param.validations[0].validation_min || undefined} step={1}
252276
onValueChange={(value) => {
253277
console.log("value", value[0].toString());
254278
field.onChange(value[0].toString());
@@ -258,6 +282,38 @@ export function DynamicForm() {
258282
/>
259283
</div>
260284
)
285+
case "radio":
286+
return (
287+
<div key={param.name} className="flex flex-col gap-2 items-center">
288+
<div className="flex items-center justify-between gap-2">
289+
<label>
290+
{param.display_name || param.name}
291+
{param.icon && <img src={param.icon} alt="" style={{ marginLeft: 6 }} />}
292+
</label>
293+
<output className="text-sm font-medium tabular-nums">{param.value}</output>
294+
</div>
295+
{param.description && <div className="text-sm">{param.description}</div>}
296+
<Controller
297+
name={param.name}
298+
control={methods.control}
299+
render={({ field }) => (
300+
<div className="w-[300px]">
301+
<RadioGroup defaultValue={param.default_value} onValueChange={field.onChange}>
302+
{(param.options || []).map((option, idx) => {
303+
if (!option) return null;
304+
return (
305+
<div key={idx} className="flex items-center space-x-2">
306+
<RadioGroupItem value={option.value} id={option.value} />
307+
<Label htmlFor={option.value}>{option.name}</Label>
308+
</div>
309+
);
310+
})}
311+
</RadioGroup>
312+
</div>
313+
)}
314+
/>
315+
</div>
316+
)
261317
}
262318
}
263319

@@ -311,9 +367,13 @@ export function DynamicForm() {
311367
name={param.name}
312368
control={methods.control}
313369
render={({ field }) => (
314-
<DebouncedInput
315-
field={field}
370+
<Input
371+
onChange={(e) => {
372+
field.onChange(e);
373+
}}
374+
className="w-[300px]"
316375
type={mapParamTypeToInputType(param.type)}
376+
value={field.value}
317377
defaultValue={param.default_value}
318378
/>
319379
)}
@@ -336,26 +396,6 @@ export function DynamicForm() {
336396
);
337397
};
338398

339-
const DebouncedInput = ({ field, type, defaultValue }: {
340-
field: { onChange: (e: React.ChangeEvent<HTMLInputElement>) => void },
341-
type: string,
342-
defaultValue?: string
343-
}) => {
344-
const { debounced } = useDebouncedFunction(
345-
(e: React.ChangeEvent<HTMLInputElement>) => field.onChange(e),
346-
2000
347-
);
348-
349-
return (
350-
<Input
351-
onChange={debounced}
352-
className="w-[300px]"
353-
type={type}
354-
defaultValue={defaultValue}
355-
/>
356-
);
357-
};
358-
359399
if (isLoading && directories.length === 0) {
360400
return <div>Loading directories...</div>;
361401
}

site/src/components/Label/Label.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Copied from shadc/ui on 11/13/2024
3+
* @see {@link https://ui.shadcn.com/docs/components/label}
4+
*/
5+
import * as LabelPrimitive from "@radix-ui/react-label";
6+
import { type VariantProps, cva } from "class-variance-authority";
7+
import { forwardRef } from "react";
8+
9+
import { cn } from "../../utils/cn";
10+
11+
const labelVariants = cva(
12+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
13+
);
14+
15+
export const Label = forwardRef<
16+
React.ElementRef<typeof LabelPrimitive.Root>,
17+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
18+
VariantProps<typeof labelVariants>
19+
>(({ className, ...props }, ref) => (
20+
<LabelPrimitive.Root
21+
ref={ref}
22+
className={cn(labelVariants(), className)}
23+
{...props}
24+
/>
25+
));
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as React from "react"
2+
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
3+
import { CircleIcon } from "lucide-react"
4+
5+
import { cn } from "../../utils/cn"
6+
7+
function RadioGroup({
8+
className,
9+
...props
10+
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
11+
return (
12+
<RadioGroupPrimitive.Root
13+
data-slot="radio-group"
14+
className={cn("grid gap-3", className)}
15+
{...props}
16+
/>
17+
)
18+
}
19+
20+
function RadioGroupItem({
21+
className,
22+
...props
23+
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
24+
return (
25+
<RadioGroupPrimitive.Item
26+
data-slot="radio-group-item"
27+
className={cn(
28+
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
29+
className
30+
)}
31+
{...props}
32+
>
33+
<RadioGroupPrimitive.Indicator
34+
data-slot="radio-group-indicator"
35+
className="relative flex items-center justify-center"
36+
>
37+
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
38+
</RadioGroupPrimitive.Indicator>
39+
</RadioGroupPrimitive.Item>
40+
)
41+
}
42+
43+
export { RadioGroup, RadioGroupItem }

0 commit comments

Comments
 (0)