mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 02:10:15 +01:00
style: update graphics (#1138)
* style: update new icon and NavLinks scale * style: new username update * refactor(Dropdown); style: general settings * style(Dropdown); adjust theme * style: dropdown and settings text * fix(Dropdown) system theme not working * style: topbar sticky; fix: general's menu settings transparent with light theme * fix(SubmitButton) stop generate button * fix: user_provided dialog for new dropdown * fix: TS error 'display' * fix(EditPresetDialog): for new dropdown * style: added green send button * converted textchat in tsx * style(SubmitButton): tooltip * test: fixed ThemeSelector and LangSelector * removed transition-opacity * fix all tests * removed empty cn call * chore: Update General.tsx to add Arabic option --------- Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com>
This commit is contained in:
parent
8b28fdf240
commit
9ad47b6660
43 changed files with 442 additions and 318 deletions
|
|
@ -4,7 +4,7 @@ import { VariantProps, cva } from 'class-variance-authority';
|
|||
import { cn } from '../../utils';
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:hover:bg-slate-800 dark:hover:text-slate-100 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900 data-[state=open]:bg-slate-100 dark:data-[state=open]:bg-slate-800',
|
||||
'inline-flex items-center justify-center text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:hover:bg-slate-800 dark:hover:text-slate-100 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900 data-[state=open]:bg-slate-100 dark:data-[state=open]:bg-slate-800',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ const DialogClose = React.forwardRef<
|
|||
<DialogPrimitive.Close
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'mt-2 inline-flex h-10 items-center justify-center rounded-md border border-slate-200 bg-transparent px-4 py-2 text-sm font-semibold text-slate-900 transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-gray-900 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 sm:mt-0',
|
||||
'mt-2 inline-flex h-10 items-center justify-center rounded-lg border border-slate-200 bg-transparent px-4 py-2 text-sm font-semibold text-slate-900 transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-gray-900 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 sm:mt-0',
|
||||
className ?? '',
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -130,7 +130,7 @@ const DialogButton = React.forwardRef<
|
|||
ref={ref}
|
||||
variant="outline"
|
||||
className={cn(
|
||||
'mt-2 inline-flex h-10 items-center justify-center rounded-md border border-slate-200 bg-transparent px-4 py-2 text-sm font-semibold text-slate-900 transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-gray-900 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 sm:mt-0',
|
||||
'mt-2 inline-flex h-10 items-center justify-center rounded-lg border border-slate-200 bg-transparent px-4 py-2 text-sm font-semibold text-slate-900 transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-gray-900 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 sm:mt-0',
|
||||
className ?? '',
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
import React from 'react';
|
||||
import CheckMark from '../svg/CheckMark';
|
||||
import { Listbox } from '@headlessui/react';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
function Dropdown({
|
||||
value,
|
||||
label = '',
|
||||
onChange,
|
||||
options,
|
||||
className,
|
||||
containerClassName,
|
||||
optionsClassName = '',
|
||||
}) {
|
||||
const currentOption =
|
||||
options.find((element) => element === value || element?.value === value) ?? value;
|
||||
return (
|
||||
<div className={cn('flex items-center justify-center gap-2', containerClassName)}>
|
||||
<div className="relative w-full">
|
||||
<Listbox value={value} onChange={onChange}>
|
||||
<Listbox.Button
|
||||
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 focus:outline-none focus:ring-1 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
|
||||
className || '',
|
||||
)}
|
||||
>
|
||||
<span className="inline-flex w-full truncate">
|
||||
<span className="flex h-6 items-center gap-1 truncate text-sm text-black dark:text-white">
|
||||
{`${label}${currentOption?.display ?? value}`}
|
||||
</span>
|
||||
</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-4 w-4 text-gray-400"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
<Listbox.Options
|
||||
className={cn(
|
||||
'absolute z-50 mt-2 max-h-60 w-full overflow-auto rounded bg-white text-base text-xs ring-1 ring-black/10 focus:outline-none dark:bg-gray-800 dark:ring-white/20 dark:last:border-0 md:w-[100%] ',
|
||||
optionsClassName,
|
||||
)}
|
||||
>
|
||||
{options.map((item, i) => (
|
||||
<Listbox.Option
|
||||
key={i}
|
||||
value={item?.value ?? item}
|
||||
className="group relative flex h-[42px] cursor-pointer select-none items-center overflow-hidden border-b border-black/10 pl-3 pr-9 text-gray-900 last:border-0 hover:bg-gray-20 dark:border-white/20 dark:text-white dark:hover:bg-gray-700"
|
||||
>
|
||||
<span className="flex items-center gap-1.5 truncate">
|
||||
<span
|
||||
className={cn(
|
||||
'flex h-6 items-center gap-1 text-gray-800 dark:text-gray-100',
|
||||
value === (item?.value ?? item) ? 'font-semibold' : '',
|
||||
)}
|
||||
>
|
||||
{item?.display ?? item}
|
||||
</span>
|
||||
{value === (item?.value ?? item) && (
|
||||
<span className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-800 dark:text-gray-100">
|
||||
<CheckMark />
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Listbox>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Dropdown;
|
||||
121
client/src/components/ui/Dropdown.tsx
Normal file
121
client/src/components/ui/Dropdown.tsx
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import React, { FC, useContext, useState } from 'react';
|
||||
import { Listbox } from '@headlessui/react';
|
||||
import { cn } from '~/utils/';
|
||||
import { ThemeContext } from '~/hooks';
|
||||
|
||||
type OptionType = {
|
||||
value: string;
|
||||
display?: string;
|
||||
};
|
||||
|
||||
interface DropdownProps {
|
||||
value: string;
|
||||
label?: string;
|
||||
onChange: (value: string) => void;
|
||||
options: (string | OptionType)[];
|
||||
className?: string;
|
||||
width?: number;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
const Dropdown: FC<DropdownProps> = ({
|
||||
value: initialValue,
|
||||
label = '',
|
||||
onChange,
|
||||
options,
|
||||
className = '',
|
||||
width,
|
||||
testId = 'dropdown-menu',
|
||||
}) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const [selectedValue, setSelectedValue] = useState(initialValue);
|
||||
|
||||
const themeStyles = {
|
||||
light: 'bg-white text-gray-700 hover:bg-gray-200 border-gray-300',
|
||||
dark: 'bg-[#202123] text-white hover:bg-[#323236] border-gray-600',
|
||||
};
|
||||
|
||||
const isSystemDark =
|
||||
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const currentThemeStyle =
|
||||
theme === 'system'
|
||||
? isSystemDark
|
||||
? themeStyles.dark
|
||||
: themeStyles.light
|
||||
: themeStyles[theme] || themeStyles.light;
|
||||
|
||||
return (
|
||||
<div className={cn('relative', className)}>
|
||||
<Listbox
|
||||
value={selectedValue}
|
||||
onChange={(newValue) => {
|
||||
setSelectedValue(newValue);
|
||||
onChange(newValue);
|
||||
}}
|
||||
>
|
||||
<div className={cn('relative', className)}>
|
||||
<Listbox.Button
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
'relative inline-flex items-center justify-between rounded-md py-2 pl-3 pr-10',
|
||||
currentThemeStyle,
|
||||
'w-auto',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<span className="block truncate font-medium">
|
||||
{label}
|
||||
{options
|
||||
.map((o) => (typeof o === 'string' ? { value: o, display: o } : o))
|
||||
.find((o) => o.value === selectedValue)?.display || selectedValue}
|
||||
</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className="h-5 w-5 rotate-0 transform text-gray-400 transition-transform duration-300 ease-in-out"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
|
||||
<Listbox.Options
|
||||
className={cn(
|
||||
'max-h-90 absolute z-50 mt-1 overflow-auto rounded-md shadow-lg transition-opacity focus:outline-none',
|
||||
currentThemeStyle,
|
||||
className,
|
||||
)}
|
||||
style={{ width: width ? `${width}px` : 'auto' }}
|
||||
>
|
||||
{options.map((item, index) => (
|
||||
<Listbox.Option
|
||||
key={index}
|
||||
value={typeof item === 'string' ? item : item.value}
|
||||
className={cn(
|
||||
'relative cursor-pointer select-none py-1 pl-3 pr-6',
|
||||
currentThemeStyle,
|
||||
)}
|
||||
style={{ width: width ? `${width}px` : 'auto' }}
|
||||
data-theme={typeof item === 'string' ? item : (item as OptionType).value}
|
||||
>
|
||||
<span className="block truncate">
|
||||
{typeof item === 'string' ? item : (item as OptionType).display}
|
||||
</span>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</div>
|
||||
</Listbox>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dropdown;
|
||||
Loading…
Add table
Add a link
Reference in a new issue