mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
📻 feat: radio component (#8692)
* 📦 feat: Add Radio component * 📦 feat: Integrate localization for 'No options available' message in Radio component * 📦 feat: Bump version to 0.2.0 in package.json * 📦 feat: Update client package version to 0.2.0 in package-lock.json
This commit is contained in:
parent
9fddb0ff6a
commit
2ce6ac74f4
5 changed files with 104 additions and 3 deletions
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -51506,7 +51506,7 @@
|
||||||
},
|
},
|
||||||
"packages/client": {
|
"packages/client": {
|
||||||
"name": "@librechat/client",
|
"name": "@librechat/client",
|
||||||
"version": "0.1.9",
|
"version": "0.2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-alias": "^5.1.0",
|
"@rollup/plugin-alias": "^5.1.0",
|
||||||
"@rollup/plugin-commonjs": "^25.0.2",
|
"@rollup/plugin-commonjs": "^25.0.2",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@librechat/client",
|
"name": "@librechat/client",
|
||||||
"version": "0.1.9",
|
"version": "0.2.0",
|
||||||
"description": "React components for LibreChat",
|
"description": "React components for LibreChat",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/index.es.js",
|
"module": "dist/index.es.js",
|
||||||
|
|
|
||||||
99
packages/client/src/components/Radio.tsx
Normal file
99
packages/client/src/components/Radio.tsx
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
import React, { useState, useRef, useLayoutEffect, useCallback, memo } from 'react';
|
||||||
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
|
interface Option {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RadioProps {
|
||||||
|
options: Option[];
|
||||||
|
value?: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Radio = memo(function Radio({ options, value, onChange, disabled = false }: RadioProps) {
|
||||||
|
const localize = useLocalize();
|
||||||
|
const [currentValue, setCurrentValue] = useState<string>(value ?? '');
|
||||||
|
const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
||||||
|
const [backgroundStyle, setBackgroundStyle] = useState<React.CSSProperties>({});
|
||||||
|
|
||||||
|
const handleChange = (newValue: string) => {
|
||||||
|
setCurrentValue(newValue);
|
||||||
|
onChange?.(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateBackgroundStyle = useCallback(() => {
|
||||||
|
const selectedIndex = options.findIndex((opt) => opt.value === currentValue);
|
||||||
|
if (selectedIndex >= 0 && buttonRefs.current[selectedIndex]) {
|
||||||
|
const selectedButton = buttonRefs.current[selectedIndex];
|
||||||
|
const container = selectedButton?.parentElement;
|
||||||
|
if (selectedButton && container) {
|
||||||
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
const buttonRect = selectedButton.getBoundingClientRect();
|
||||||
|
const offsetLeft = buttonRect.left - containerRect.left - 4;
|
||||||
|
setBackgroundStyle({
|
||||||
|
width: `${buttonRect.width}px`,
|
||||||
|
transform: `translateX(${offsetLeft}px)`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [currentValue, options]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
updateBackgroundStyle();
|
||||||
|
}, [updateBackgroundStyle]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
setCurrentValue(value);
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
if (options.length === 0) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="relative inline-flex items-center rounded-lg bg-muted p-1 opacity-50"
|
||||||
|
role="radiogroup"
|
||||||
|
>
|
||||||
|
<span className="px-4 py-2 text-xs text-muted-foreground">
|
||||||
|
{localize('com_ui_no_options')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedIndex = options.findIndex((opt) => opt.value === currentValue);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative inline-flex items-center rounded-lg bg-muted p-1" role="radiogroup">
|
||||||
|
{selectedIndex >= 0 && (
|
||||||
|
<div
|
||||||
|
className="pointer-events-none absolute inset-y-1 rounded-md border border-border/50 bg-background shadow-sm transition-all duration-300 ease-out"
|
||||||
|
style={backgroundStyle}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{options.map((option, index) => (
|
||||||
|
<button
|
||||||
|
key={option.value}
|
||||||
|
ref={(el) => {
|
||||||
|
buttonRefs.current[index] = el;
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
role="radio"
|
||||||
|
aria-checked={currentValue === option.value}
|
||||||
|
onClick={() => handleChange(option.value)}
|
||||||
|
disabled={disabled}
|
||||||
|
className={`relative z-10 flex h-[34px] items-center justify-center rounded-md px-4 text-sm font-medium transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${
|
||||||
|
currentValue === option.value ? 'text-foreground' : 'text-foreground'
|
||||||
|
} ${disabled ? 'cursor-not-allowed opacity-50' : ''}`}
|
||||||
|
>
|
||||||
|
<span className="whitespace-nowrap">{option.label}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Radio;
|
||||||
|
|
@ -30,6 +30,7 @@ export * from './Progress';
|
||||||
export * from './InputOTP';
|
export * from './InputOTP';
|
||||||
export * from './MultiSearch';
|
export * from './MultiSearch';
|
||||||
export * from './Resizable';
|
export * from './Resizable';
|
||||||
|
export { default as Radio } from './Radio';
|
||||||
export { default as Badge } from './Badge';
|
export { default as Badge } from './Badge';
|
||||||
export { default as Combobox } from './Combobox';
|
export { default as Combobox } from './Combobox';
|
||||||
export { default as Dropdown } from './Dropdown';
|
export { default as Dropdown } from './Dropdown';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"com_ui_cancel": "Cancel"
|
"com_ui_cancel": "Cancel",
|
||||||
|
"com_ui_no_options": "No options available"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue