mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-10 03:24:24 +01:00
* ✨ feat: Enhance Artifact Management with Version Control and UI Improvements ✨ feat: Improve mobile layout and responsiveness in Artifacts component ✨ feat: Refactor imports and remove unnecessary props in Artifact components ✨ feat: Enhance Artifacts and SidePanel components with improved mobile responsiveness and layout transitions feat: Enhance artifact panel animations and improve UI responsiveness - Updated Thinking component button styles for smoother transitions. - Implemented dynamic rendering for artifacts panel with animation effects. - Refactored localization keys for consistency across multiple languages. - Added new CSS animations for iOS-inspired smooth transitions. - Improved Tailwind CSS configuration to support enhanced animation effects. ✨ feat: Add fullWidth and icon support to Radio component for enhanced flexibility refactor: Remove unused PreviewProps import in ArtifactPreview component refactor: Improve button class handling and blur effect constants in Artifact components ✨ feat: Refactor Artifacts component structure and add mobile/desktop variants for improved UI chore: Bump @librechat/client version to 0.3.2 refactor: Update button styles and transition durations for improved UI responsiveness refactor: revert back localization key refactor: remove unused scaling and animation properties for cleaner CSS refactor: remove unused animation properties for cleaner configuration * ✨ refactor: Simplify className usage in ArtifactTabs, ArtifactsHeader, and SidePanelGroup components * refactor: Remove cycleArtifact function from useArtifacts hook * ✨ feat: Implement Chromium resize lag fix with performance optimizations and new ArtifactsPanel component * ✨ feat: Update Badge component for responsive design and improve tap scaling behavior * chore: Update react-resizable-panels dependency to version 3.0.6 * ✨ feat: Refactor Artifacts components for improved structure and performance; remove unused files and optimize styles * ✨ style: Update text color for improved visibility in Artifacts component * ✨ style: Remove text color class for improved Spinner styling in Artifacts component * refactor: Split EditorContext into MutationContext and CodeContext to optimize re-renders; update related components to use new hooks * refactor: Optimize debounced mutation handling in CodeEditor component using refs to maintain current values and reduce re-renders * fix: Correct endpoint for message artifacts by changing URL segment from 'artifacts' to 'artifact' * feat: Enhance useEditArtifact mutation with optimistic updates and rollback on error; improve type safety with context management * fix: proper switch to preview as soon as artifact becomes enclosed * refactor: Remove optimistic updates from useEditArtifact mutation to prevent errors; simplify onMutate logic * test: Add comprehensive unit tests for useArtifacts hook to validate artifact handling, tab switching, and state management * test: Enhance unit tests for useArtifacts hook to cover new conversation transitions and null message handling --------- Co-authored-by: Marco Beretta <81851188+berry-13@users.noreply.github.com>
113 lines
3.6 KiB
TypeScript
113 lines
3.6 KiB
TypeScript
import React, { useState, useRef, useLayoutEffect, useCallback, memo } from 'react';
|
|
import { useLocalize } from '~/hooks';
|
|
|
|
interface Option {
|
|
value: string;
|
|
label: string;
|
|
icon?: React.ReactNode;
|
|
}
|
|
|
|
interface RadioProps {
|
|
options: Option[];
|
|
value?: string;
|
|
onChange?: (value: string) => void;
|
|
disabled?: boolean;
|
|
className?: string;
|
|
fullWidth?: boolean;
|
|
}
|
|
|
|
const Radio = memo(function Radio({
|
|
options,
|
|
value,
|
|
onChange,
|
|
disabled = false,
|
|
className = '',
|
|
fullWidth = 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 ${fullWidth ? 'flex' : 'inline-flex'} items-center rounded-lg bg-muted p-1 ${className}`}
|
|
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 gap-2 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' : ''} ${fullWidth ? 'flex-1' : ''}`}
|
|
>
|
|
{option.icon && <span className="flex-shrink-0">{option.icon}</span>}
|
|
<span className="whitespace-nowrap">{option.label}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
});
|
|
|
|
export default Radio;
|