mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
✨ style: Enhance Styling & Accessibility (#5956)
* ✨ feat: Enhance UI Components with Shadows and Accessibility Improvements * 🔧 fix: Correct Category Labels and Values in API Model & Adjust Button Class in Prompt List
This commit is contained in:
parent
fdb3cf3f58
commit
fe7013562b
9 changed files with 59 additions and 47 deletions
|
|
@ -3,40 +3,40 @@ const { logger } = require('~/config');
|
|||
|
||||
const options = [
|
||||
{
|
||||
label: 'idea',
|
||||
value: 'com_ui_idea',
|
||||
label: 'com_ui_idea',
|
||||
value: 'idea',
|
||||
},
|
||||
{
|
||||
label: 'travel',
|
||||
value: 'com_ui_travel',
|
||||
label: 'com_ui_travel',
|
||||
value: 'travel',
|
||||
},
|
||||
{
|
||||
label: 'teach_or_explain',
|
||||
value: 'com_ui_teach_or_explain',
|
||||
label: 'com_ui_teach_or_explain',
|
||||
value: 'teach_or_explain',
|
||||
},
|
||||
{
|
||||
label: 'write',
|
||||
value: 'com_ui_write',
|
||||
label: 'com_ui_write',
|
||||
value: 'write',
|
||||
},
|
||||
{
|
||||
label: 'shop',
|
||||
value: 'com_ui_shop',
|
||||
label: 'com_ui_shop',
|
||||
value: 'shop',
|
||||
},
|
||||
{
|
||||
label: 'code',
|
||||
value: 'com_ui_code',
|
||||
label: 'com_ui_code',
|
||||
value: 'code',
|
||||
},
|
||||
{
|
||||
label: 'misc',
|
||||
value: 'com_ui_misc',
|
||||
label: 'com_ui_misc',
|
||||
value: 'misc',
|
||||
},
|
||||
{
|
||||
label: 'roleplay',
|
||||
value: 'com_ui_roleplay',
|
||||
label: 'com_ui_roleplay',
|
||||
value: 'roleplay',
|
||||
},
|
||||
{
|
||||
label: 'finance',
|
||||
value: 'com_ui_finance',
|
||||
label: 'com_ui_finance',
|
||||
value: 'finance',
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ const Command = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-border-light">
|
||||
<div className="rounded-xl border border-border-light shadow-md">
|
||||
<h3 className="flex h-10 items-center gap-1 pl-4 text-sm text-text-secondary">
|
||||
<SquareSlash className="icon-sm" aria-hidden="true" />
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ const Description = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-border-light">
|
||||
<div className="rounded-xl border border-border-light shadow-md">
|
||||
<h3 className="flex h-10 items-center gap-1 pl-4 text-sm text-text-secondary">
|
||||
<Info className="icon-sm" aria-hidden="true" />
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export default function List({
|
|||
<div className="flex w-full justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full bg-transparent px-3"
|
||||
className={`w-full bg-transparent ${isChatRoute ? '' : 'mx-2'}`}
|
||||
onClick={() => navigate('/d/prompts/new')}
|
||||
>
|
||||
<Plus className="size-4" aria-hidden />
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ const PromptEditor: React.FC<Props> = ({ name, isEditing, setIsEditing }) => {
|
|||
<div
|
||||
role="button"
|
||||
className={cn(
|
||||
'w-full flex-1 overflow-auto rounded-b-xl border border-border-light p-2 transition-all duration-150 sm:p-4',
|
||||
'w-full flex-1 overflow-auto rounded-b-xl border border-border-light p-2 shadow-md transition-all duration-150 sm:p-4',
|
||||
{
|
||||
'cursor-pointer bg-surface-primary hover:bg-surface-secondary active:bg-surface-tertiary':
|
||||
!isEditing,
|
||||
|
|
@ -105,6 +105,7 @@ const PromptEditor: React.FC<Props> = ({ name, isEditing, setIsEditing }) => {
|
|||
isEditing ? (
|
||||
<TextareaAutosize
|
||||
{...field}
|
||||
autoFocus
|
||||
className="w-full resize-none overflow-y-auto rounded bg-transparent text-sm text-text-primary focus:outline-none sm:text-base"
|
||||
minRows={3}
|
||||
maxRows={14}
|
||||
|
|
|
|||
|
|
@ -237,7 +237,6 @@ const PromptForm = () => {
|
|||
payload: { name: groupName, category: value },
|
||||
})
|
||||
}
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="mt-2 flex flex-row items-center justify-center gap-x-2 lg:mt-0">
|
||||
{hasShareAccess && <SharePrompt group={group} disabled={isLoadingGroup} />}
|
||||
|
|
@ -349,7 +348,7 @@ const PromptForm = () => {
|
|||
{isLoadingPrompts ? (
|
||||
<Skeleton className="h-96" aria-live="polite" />
|
||||
) : (
|
||||
<div className="flex h-full flex-col gap-4">
|
||||
<div className="mb-2 flex h-full flex-col gap-4">
|
||||
<PromptEditor name="prompt" isEditing={isEditing} setIsEditing={setIsEditing} />
|
||||
<PromptVariables promptText={promptText} />
|
||||
<Description
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ const PromptVariables = ({
|
|||
}, [promptText]);
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-border-light bg-transparent p-4 shadow-md ">
|
||||
<div className="rounded-xl border border-border-light bg-transparent p-4 shadow-md">
|
||||
<h3 className="flex items-center gap-2 py-2 text-lg font-semibold text-text-primary">
|
||||
<Variable className="icon-sm" aria-hidden="true" />
|
||||
{localize('com_ui_variables')}
|
||||
|
|
@ -71,7 +71,7 @@ const PromptVariables = ({
|
|||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-text-text-primary text-sm font-medium">
|
||||
<span className="text-sm font-medium text-text-primary">
|
||||
{localize('com_ui_dropdown_variables')}
|
||||
</span>
|
||||
<span className="text-sm text-text-secondary">
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default function Parameters({
|
|||
return value ?? '';
|
||||
}, [providerOption]);
|
||||
const models = useMemo(
|
||||
() => (provider ? modelsData[provider] ?? [] : []),
|
||||
() => (provider ? (modelsData[provider] ?? []) : []),
|
||||
[modelsData, provider],
|
||||
);
|
||||
|
||||
|
|
@ -99,6 +99,7 @@ export default function Parameters({
|
|||
{/* Endpoint aka Provider for Agents */}
|
||||
<div className="mb-4">
|
||||
<label
|
||||
id="provider-label"
|
||||
className="text-token-text-primary model-panel-label mb-2 block font-medium"
|
||||
htmlFor="provider"
|
||||
>
|
||||
|
|
@ -111,6 +112,10 @@ export default function Parameters({
|
|||
render={({ field, fieldState: { error } }) => (
|
||||
<>
|
||||
<SelectDropDown
|
||||
id="provider"
|
||||
aria-labelledby="provider-label"
|
||||
aria-label={localize('com_ui_provider')}
|
||||
aria-required="true"
|
||||
emptyTitle={true}
|
||||
value={field.value ?? ''}
|
||||
title={localize('com_ui_provider')}
|
||||
|
|
@ -140,6 +145,7 @@ export default function Parameters({
|
|||
{/* Model */}
|
||||
<div className="model-panel-section mb-4">
|
||||
<label
|
||||
id="model-label"
|
||||
className={cn(
|
||||
'text-token-text-primary model-panel-label mb-2 block font-medium',
|
||||
!provider && 'text-gray-500 dark:text-gray-400',
|
||||
|
|
@ -155,6 +161,10 @@ export default function Parameters({
|
|||
render={({ field, fieldState: { error } }) => (
|
||||
<>
|
||||
<SelectDropDown
|
||||
id="model"
|
||||
aria-labelledby="model-label"
|
||||
aria-label={localize('com_ui_model')}
|
||||
aria-required="true"
|
||||
emptyTitle={true}
|
||||
placeholder={
|
||||
provider
|
||||
|
|
@ -188,7 +198,6 @@ export default function Parameters({
|
|||
{parameters && (
|
||||
<div className="h-auto max-w-full overflow-x-hidden p-2">
|
||||
<div className="grid grid-cols-4 gap-6">
|
||||
{' '}
|
||||
{/* This is the parent element containing all settings */}
|
||||
{/* Below is an example of an applied dynamic setting, each be contained by a div with the column span specified */}
|
||||
{parameters.map((setting) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import {
|
||||
Label,
|
||||
Listbox,
|
||||
|
|
@ -82,18 +82,14 @@ function SelectDropDown({
|
|||
}
|
||||
|
||||
let title = _title;
|
||||
|
||||
if (emptyTitle) {
|
||||
title = '';
|
||||
} else if (!(title ?? '')) {
|
||||
title = localize('com_ui_model');
|
||||
}
|
||||
|
||||
const values = availableValues ?? [];
|
||||
|
||||
// Detemine if we should to convert this component into a searchable select. If we have enough elements, a search
|
||||
// input will appear near the top of the menu, allowing correct filtering of different model menu items. This will
|
||||
// reset once the component is unmounted (as per a normal search)
|
||||
// Enable searchable select if enough items are provided.
|
||||
const [filteredValues, searchRender] = useMultiSearch<string[] | Option[]>({
|
||||
availableOptions: values,
|
||||
placeholder: searchPlaceholder,
|
||||
|
|
@ -103,26 +99,35 @@ function SelectDropDown({
|
|||
});
|
||||
const hasSearchRender = searchRender != null;
|
||||
const options = hasSearchRender ? filteredValues : values;
|
||||
|
||||
const renderIcon = showOptionIcon && value != null && (value as OptionWithIcon).icon != null;
|
||||
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center justify-center gap-2 ', containerClassName ?? '')}>
|
||||
<div className={cn('flex items-center justify-center gap-2', containerClassName ?? '')}>
|
||||
<div className={cn('relative w-full', subContainerClassName ?? '')}>
|
||||
<Listbox value={value} onChange={setValue} disabled={disabled}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<ListboxButton
|
||||
ref={buttonRef}
|
||||
data-testid="select-dropdown-button"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (!open && buttonRef.current) {
|
||||
buttonRef.current.click();
|
||||
}
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left disabled:bg-white dark:border-gray-600 dark:bg-gray-700 sm:text-sm',
|
||||
'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:bg-white dark:border-gray-600 dark:bg-gray-700 sm:text-sm',
|
||||
className ?? '',
|
||||
)}
|
||||
>
|
||||
{' '}
|
||||
{showLabel && (
|
||||
<Label
|
||||
className="block text-xs text-gray-700 dark:text-gray-500 "
|
||||
className="block text-xs text-gray-700 dark:text-gray-500"
|
||||
id="headlessui-listbox-label-:r1:"
|
||||
data-headlessui-state=""
|
||||
>
|
||||
|
|
@ -154,11 +159,9 @@ function SelectDropDown({
|
|||
if (!value) {
|
||||
return <span className="text-text-secondary">{placeholder}</span>;
|
||||
}
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
return value.label ?? '';
|
||||
}
|
||||
|
||||
return value;
|
||||
})()}
|
||||
</span>
|
||||
|
|
@ -171,7 +174,7 @@ function SelectDropDown({
|
|||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-4 w-4 text-gray-400"
|
||||
className="h-4 w-4 text-gray-400"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
@ -212,17 +215,17 @@ function SelectDropDown({
|
|||
if (!option) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const currentLabel =
|
||||
typeof option === 'string' ? option : option.label ?? option.value ?? '';
|
||||
const currentValue = typeof option === 'string' ? option : option.value ?? '';
|
||||
typeof option === 'string' ? option : (option.label ?? option.value ?? '');
|
||||
const currentValue = typeof option === 'string' ? option : (option.value ?? '');
|
||||
const currentIcon =
|
||||
typeof option === 'string' ? null : (option.icon as React.ReactNode) ?? null;
|
||||
typeof option === 'string'
|
||||
? null
|
||||
: ((option.icon as React.ReactNode) ?? null);
|
||||
let activeValue: string | number | null | Option = value;
|
||||
if (typeof activeValue !== 'string') {
|
||||
activeValue = activeValue?.value ?? '';
|
||||
}
|
||||
|
||||
return (
|
||||
<ListboxOption
|
||||
key={i}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue