Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/components/ui/input/InputRadioGroup.tsx
T

74 lines
2.8 KiB
TypeScript

import { ReactNode, useEffect, useState } from "react";
import clsx from "~/utils/shared/ClassesUtils";
import { RadioGroup } from "~/ui/components/RadioGroup";
// Subframe migration (strangler shim):
// Keeps the original InputRadioGroup API (options/value/setValue/...) but renders the Subframe
// design-system `RadioGroup` (horizontal layout). The public props are unchanged.
//
// value/setValue keep the original `string | number | undefined` type. Subframe/Radix
// RadioGroup speaks strings only, so values are stringified at the boundary and mapped back to
// their original typed value (via the options lookup) before calling setValue.
//
// controlled + uncontrolled: Subframe's Root is internally controlled. The legacy field was
// usually controlled via value/setValue, but setValue is optional, so the shim holds its own
// state seeded from `value`. When the parent controls `value` it stays in sync; when it
// doesn't, internal state keeps the selection from sticking.
interface Props {
name?: string;
title?: string;
options: { name: string | ReactNode; value: string | number | undefined; disabled?: boolean }[];
value?: string | number | undefined;
setValue?: React.Dispatch<React.SetStateAction<string | number | undefined>>;
className?: string;
required?: boolean;
disabled?: boolean;
}
const toKey = (value: string | number | undefined): string => (value === undefined ? "" : String(value));
export default function InputRadioGroup({ name, title, value, options, setValue, className, required, disabled }: Props) {
const [actualValue, setActualValue] = useState<string | number | undefined>(value);
useEffect(() => {
setActualValue(value);
}, [value]);
function onValueChange(nextKey: string) {
// Map the stringified key back to the original typed option value.
const matched = options.find((option) => toKey(option.value) === nextKey);
const next = matched ? matched.value : nextKey;
setActualValue(next);
if (setValue) {
setValue(next);
}
}
return (
<div className={clsx(className, "w-full grow")}>
<div className="flex items-center justify-between">
<label className="flex justify-between space-x-2 truncate text-xs font-medium">
{title}
{required && <span className="ml-1 text-red-600">*</span>}
</label>
</div>
<input type="hidden" name={name} value={toKey(actualValue)} hidden readOnly />
<RadioGroup
className="mt-1 w-full max-w-none"
horizontal={true}
disabled={disabled}
value={toKey(actualValue)}
onValueChange={onValueChange}
aria-label={title}
>
{options.map((option, idx) => (
<RadioGroup.Option key={idx} value={toKey(option.value)} disabled={option.disabled || disabled} label={option.name} className="flex-1" />
))}
</RadioGroup>
</div>
);
}