🎨 style: Prompt UI Refresh & A11Y Improvements (#5614)

* 🚀 feat: Add animated search input and improve filtering UI

* 🏄 refactor: Clean up category options and optimize event handlers in ChatGroupItem

* 🚀 refactor: 'Rename Prompt' option and enhance prompt filtering UI
Changed the useUpdatePromptGroup mutation in prompts.ts to replace the JSON.parse(JSON.stringify(...)) clones with structuredClone. This avoids errors when data contains non‑JSON values and improves data cloning reliability

* 🔧 refactor: Update Sharing Prompts UI; fix: Show info message only after updating switch status

* 🔧 refactor: Simplify condition checks and replace button with custom Button component in SharePrompt

* 🔧 refactor: Update DashGroupItem styles and improve accessibility with updated aria-label

* 🔧 refactor: Adjust layout styles in GroupSidePanel and enhance loading skeletons in List component

* 🔧 refactor: Improve layout and styling of AdvancedSwitch component; adjust DashBreadcrumb margin for better alignment

* 🔧 refactor: Add new surface colors for destructive actions and update localization strings for confirmation prompts

* 🔧 refactor: Update PromptForm and PromptName components for improved layout and styling; replace button with custom Button component

* 🔧 refactor: Enhance styling and layout of DashGroupItem, FilterPrompts, and Label components for improved user experience

* 🔧 refactor: Update DeleteBookmarkButton and Label components for improved layout and text handling

* 🔧 refactor: Simplify CategorySelector usage and update destructive surface colors for a11y

* 🔧 refactor: Update styling and layout of PromptName, SharePrompt, and DashGroupItem components; enhance Dropdown functionality with custom renderValue

* 🔧 refactor: Improve layout and styling of various components; update button sizes and localization strings for better accessibility and user experience

* 🔧 refactor: Add useCurrentPromptData hook and enhance RightPanel component; update CategorySelector for improved functionality and accessibility

* 🔧 refactor: Update input components and styling for Command and Description; enhance layout and accessibility in PromptVariables and PromptForm

* 🔧 refactor: Remove useCurrentPromptData hook and clean up related components; enhance PromptVersions layout

* 🔧 refactor: Enhance accessibility by adding aria-labels to buttons and inputs; improve localization for filter prompts

* 🔧 refactor: Enhance accessibility by adding aria-labels to various components; improve layout and styling in PromptForm and CategorySelector

* 🔧 refactor: Enhance accessibility by adding aria-labels to buttons and components; improve dialog roles and descriptions in SharePrompt and PromptForm

* 🔧 refactor: Improve accessibility by adding aria-labels and roles; enhance layout and styling in ChatGroupItem, ListCard, and ManagePrompts components

* 🔧 refactor: Update UI components for improved styling and accessibility; replace button elements with custom Button component and enhance layout in VariableForm, PromptDetails, and PromptVariables

* 🔧 refactor: Improve null checks for group and instanceProjectId in SharePrompt component; enhance readability and maintainability

* style: Enhance AnimatedSearchInput component with TypeScript types; improve conditional rendering for search states and accessibility

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Marco Beretta 2025-02-05 17:37:17 +01:00 committed by GitHub
parent a44f5b4b6e
commit 73fe0835cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 1269 additions and 1028 deletions

View file

@ -1,11 +1,140 @@
import React from 'react';
import { format } from 'date-fns';
import { Layers3 } from 'lucide-react';
import { Layers3, Crown, Zap } from 'lucide-react';
import type { TPrompt, TPromptGroup } from 'librechat-data-provider';
import { Tag, TooltipAnchor, Label } from '~/components/ui';
import { useLocalize } from '~/hooks';
import { Tag } from '~/components/ui';
import { cn } from '~/utils';
const CombinedStatusIcon = ({ description }: { description: string }) => (
<TooltipAnchor
description={description}
aria-label={description}
render={
<div className="flex items-center justify-center">
<Crown className="h-4 w-4 text-amber-500" />
</div>
}
></TooltipAnchor>
);
const VersionTags = ({ tags }: { tags: string[] }) => {
const localize = useLocalize();
const isLatestAndProduction = tags.includes('latest') && tags.includes('production');
if (isLatestAndProduction) {
return (
<span className="absolute bottom-3 right-3">
<CombinedStatusIcon description={localize('com_ui_latest_production_version')} />
</span>
);
}
return (
<span className="flex gap-1 text-sm">
{tags.map((tag, i) => (
<TooltipAnchor
description={
tag === 'production'
? localize('com_ui_currently_production')
: localize('com_ui_latest_version')
}
key={`${tag}-${i}`}
aria-label={
tag === 'production'
? localize('com_ui_currently_production')
: localize('com_ui_latest_version')
}
render={
<Tag
label={tag}
className={cn(
'w-24 justify-center border border-transparent',
tag === 'production'
? 'bg-green-100 text-green-500 dark:border-green-500 dark:bg-transparent dark:text-green-500'
: 'bg-blue-100 text-blue-500 dark:border-blue-500 dark:bg-transparent dark:text-blue-500',
)}
labelClassName="flex items-center m-0 justify-center gap-1"
LabelNode={(() => {
if (tag === 'production') {
return (
<div className="flex items-center">
<span className="slow-pulse size-2 rounded-full bg-green-400" />
</div>
);
}
if (tag === 'latest') {
return (
<div className="flex items-center">
<Zap className="size-4" />
</div>
);
}
return null;
})()}
/>
}
></TooltipAnchor>
))}
</span>
);
};
const VersionCard = ({
prompt,
index,
isSelected,
totalVersions,
onClick,
authorName,
tags,
}: {
prompt: TPrompt;
index: number;
isSelected: boolean;
totalVersions: number;
onClick: () => void;
authorName?: string;
tags: string[];
}) => {
const localize = useLocalize();
return (
<button
type="button"
className={cn(
'group relative w-full rounded-lg border border-border-light p-4 transition-all duration-300',
isSelected
? 'bg-surface-hover shadow-xl'
: 'bg-surface-primary shadow-sm hover:bg-surface-secondary',
)}
onClick={onClick}
aria-selected={isSelected}
role="tab"
aria-label={localize('com_ui_version_var', `${totalVersions - index}`)}
>
<div className="flex flex-col gap-2">
<div className="flex items-start justify-between lg:flex-col xl:flex-row">
<h3 className="font-bold text-text-primary">
{localize('com_ui_version_var', `${totalVersions - index}`)}
</h3>
<time className="text-xs text-text-secondary" dateTime={prompt.createdAt}>
{format(new Date(prompt.createdAt), 'yyyy-MM-dd HH:mm')}
</time>
</div>
<div className="flex items-center gap-1 lg:flex-col xl:flex-row">
{authorName && (
<Label className="text-left text-xs text-text-secondary">by {authorName}</Label>
)}
{tags.length > 0 && <VersionTags tags={tags} />}
</div>
</div>
</button>
);
};
const PromptVersions = ({
prompts,
group,
@ -14,19 +143,24 @@ const PromptVersions = ({
}: {
prompts: TPrompt[];
group?: TPromptGroup;
selectionIndex: React.SetStateAction<number>;
selectionIndex: number;
setSelectionIndex: React.Dispatch<React.SetStateAction<number>>;
}) => {
const localize = useLocalize();
return (
<>
<h2 className="mb-4 flex gap-2 text-base font-semibold dark:text-gray-200">
<Layers3 className="icon-lg text-green-500" />
{localize('com_ui_versions')}
</h2>
<ul className="flex flex-col gap-3">
<section className="my-6" aria-label="Prompt Versions">
<header className="mb-6">
<h2 className="flex items-center gap-2 text-base font-semibold text-text-primary">
<Layers3 className="h-5 w-5 text-green-500" />
{localize('com_ui_versions')}
</h2>
</header>
<div className="flex flex-col gap-3" role="tablist" aria-label="Version history">
{prompts.map((prompt: TPrompt, index: number) => {
const tags: string[] = [];
if (index === 0) {
tags.push('latest');
}
@ -36,53 +170,20 @@ const PromptVersions = ({
}
return (
<li
key={index}
className={cn(
'relative cursor-pointer rounded-lg border p-4 dark:border-gray-600 dark:bg-transparent',
index === selectionIndex ? 'bg-gray-100 dark:bg-gray-700' : 'bg-white',
)}
<VersionCard
key={prompt._id}
prompt={prompt}
index={index}
isSelected={index === selectionIndex}
totalVersions={prompts.length}
onClick={() => setSelectionIndex(index)}
>
<p className="font-bold dark:text-gray-200">
{localize('com_ui_version_var', `${prompts.length - index}`)}
</p>
<p className="absolute right-4 top-5 whitespace-nowrap text-xs text-gray-600 dark:text-gray-400">
{format(new Date(prompt.createdAt), 'yyyy-MM-dd HH:mm')}
</p>
{tags.length > 0 && (
<span className="flex flex-wrap gap-1 text-sm">
{tags.map((tag, i) => {
return (
<Tag
key={`${tag}-${i}`}
label={tag}
className={cn(
'w-fit border border-transparent bg-blue-100 text-blue-500 dark:border-blue-500 dark:bg-transparent dark:text-blue-500',
tag === 'production' &&
'bg-green-100 text-green-500 dark:border-green-500 dark:bg-transparent dark:text-green-500',
)}
labelClassName="flex m-0 justify-center gap-1"
LabelNode={
tag === 'production' ? (
<div className="flex items-center ">
<span className="slow-pulse h-[0.4rem] w-[0.4rem] rounded-full bg-green-400" />
</div>
) : null
}
/>
);
})}
</span>
)}
{group?.authorName && (
<p className="text-xs text-gray-600 dark:text-gray-400">by {group.authorName}</p>
)}
</li>
authorName={group?.authorName}
tags={tags}
/>
);
})}
</ul>
</>
</div>
</section>
);
};