mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-28 22:28:51 +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
|
|
@ -221,7 +221,7 @@ export default function Nav({ navVisible, setNavVisible }) {
|
|||
</div>
|
||||
</div>
|
||||
{!navVisible && (
|
||||
<div className="absolute left-2 top-2 z-10 hidden md:inline-block">
|
||||
<div className="absolute left-2 top-2 z-20 hidden md:inline-block">
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export default function NavLinks() {
|
|||
)}
|
||||
<Menu.Button
|
||||
className={cn(
|
||||
'group-ui-open:bg-gray-800 flex w-full items-center gap-2.5 rounded-md px-3 py-3 text-sm transition-colors duration-200 hover:bg-gray-800',
|
||||
'group-ui-open:bg-gray-800 rounded-sd flex w-full items-center gap-2.5 px-3 py-2 text-sm transition-colors duration-200 hover:bg-gray-800',
|
||||
open ? 'bg-gray-800' : '',
|
||||
)}
|
||||
data-testid="nav-user"
|
||||
|
|
@ -69,18 +69,24 @@ export default function NavLinks() {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grow overflow-hidden text-ellipsis whitespace-nowrap text-left text-white">
|
||||
<div
|
||||
className="grow overflow-hidden text-ellipsis whitespace-nowrap text-left font-bold text-white"
|
||||
style={{ marginTop: '-4px', marginLeft: '2px' }}
|
||||
>
|
||||
{user?.name || localize('com_nav_user')}
|
||||
</div>
|
||||
<DotsIcon />
|
||||
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<DotsIcon />
|
||||
</div>
|
||||
</Menu.Button>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100 transform"
|
||||
enter="transition ease-out duration-110 transform"
|
||||
enterFrom="translate-y-2 opacity-0"
|
||||
enterTo="translate-y-0 opacity-100"
|
||||
leave="transition ease-in duration-75 transform"
|
||||
leave="transition ease-in duration-100 transform"
|
||||
leaveFrom="translate-y-0 opacity-100"
|
||||
leaveTo="translate-y-2 opacity-0"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import * as Tabs from '@radix-ui/react-tabs';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '~/components/ui';
|
||||
import { CogIcon, DataIcon } from '~/components/svg';
|
||||
import { GearIcon, DataIcon } from '~/components/svg';
|
||||
import { useMediaQuery, useLocalize } from '~/hooks';
|
||||
import type { TDialogProps } from '~/common';
|
||||
import { General, Data } from './SettingsTabs';
|
||||
|
|
@ -12,7 +12,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className={cn('shadow-2xl dark:bg-gray-900 dark:text-white md:w-[680px] ')}>
|
||||
<DialogContent
|
||||
className={cn('shadow-2xl dark:bg-gray-900 dark:text-white md:h-[373px] md:w-[680px]')}
|
||||
style={{ borderRadius: '12px' }}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">
|
||||
{localize('com_nav_settings')}
|
||||
|
|
@ -21,7 +24,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
<div className="px-6">
|
||||
<Tabs.Root
|
||||
defaultValue="general"
|
||||
className="flex flex-col gap-6 md:flex-row"
|
||||
className="flex flex-col gap-10 md:flex-row"
|
||||
orientation="vertical"
|
||||
>
|
||||
<Tabs.List
|
||||
|
|
@ -29,21 +32,21 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
role="tablist"
|
||||
aria-orientation="vertical"
|
||||
className={cn(
|
||||
'-ml-[8px] flex min-w-[180px] flex-shrink-0 flex-col',
|
||||
'min-w-auto -ml-[8px] flex flex-shrink-0 flex-col',
|
||||
isSmallScreen ? 'flex-row rounded-lg bg-gray-100 p-1 dark:bg-gray-800/30' : '',
|
||||
)}
|
||||
style={{ outline: 'none' }}
|
||||
>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-gray-500 radix-state-active:bg-gray-800 radix-state-active:text-white',
|
||||
'group my-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-gray-500 radix-state-active:bg-gray-800 radix-state-active:text-white',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||
: '',
|
||||
)}
|
||||
value="general"
|
||||
>
|
||||
<CogIcon className="fill-gray-800" />
|
||||
<GearIcon />
|
||||
{localize('com_nav_setting_general')}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default function AutoScrollSwitch({
|
|||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_auto_scroll')}</div>
|
||||
<div> {localize('com_nav_auto_scroll')} </div>
|
||||
<Switch
|
||||
id="autoScroll"
|
||||
checked={autoScroll}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const DangerButton = (props: TDangerButtonProps, ref: ForwardedRef<HTMLButtonEle
|
|||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
{showText && <div>{localize(infoTextCode)}</div>}
|
||||
{showText && <div> {localize(infoTextCode)} </div>}
|
||||
<DialogButton
|
||||
id={id}
|
||||
ref={ref}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import type { TDangerButtonProps } from '~/common';
|
|||
import AutoScrollSwitch from './AutoScrollSwitch';
|
||||
import DangerButton from './DangerButton';
|
||||
import store from '~/store';
|
||||
import { Dropdown } from '~/components/ui';
|
||||
|
||||
export const ThemeSelector = ({
|
||||
theme,
|
||||
|
|
@ -24,18 +25,22 @@ export const ThemeSelector = ({
|
|||
}) => {
|
||||
const localize = useLocalize();
|
||||
|
||||
const themeOptions = [
|
||||
{ value: 'system', display: localize('com_nav_theme_system') },
|
||||
{ value: 'dark', display: localize('com_nav_theme_dark') },
|
||||
{ value: 'light', display: localize('com_nav_theme_light') },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_theme')}</div>
|
||||
<select
|
||||
className="w-24 rounded border border-black/10 bg-transparent px-3 py-2 text-sm dark:border-white/20 dark:bg-gray-900"
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
<div> {localize('com_nav_theme')} </div>
|
||||
<Dropdown
|
||||
value={theme}
|
||||
>
|
||||
<option value="system">{localize('com_nav_theme_system')}</option>
|
||||
<option value="dark">{localize('com_nav_theme_dark')}</option>
|
||||
<option value="light">{localize('com_nav_theme_light')}</option>
|
||||
</select>
|
||||
onChange={onChange}
|
||||
options={themeOptions}
|
||||
width={150}
|
||||
testId="theme-selector"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -76,32 +81,30 @@ export const LangSelector = ({
|
|||
}) => {
|
||||
const localize = useLocalize();
|
||||
|
||||
// Create an array of options for the Dropdown
|
||||
const languageOptions = [
|
||||
{ value: 'auto', display: localize('com_nav_lang_auto') },
|
||||
{ value: 'en-US', display: localize('com_nav_lang_english') },
|
||||
{ value: 'zh-CN', display: localize('com_nav_lang_chinese') },
|
||||
{ value: 'zh-TC', display: localize('com_nav_lang_traditionalchinese') },
|
||||
{ value: 'ar-EG', display: localize('com_nav_lang_arabic') },
|
||||
{ value: 'de-DE', display: localize('com_nav_lang_german') },
|
||||
{ value: 'es-ES', display: localize('com_nav_lang_spanish') },
|
||||
{ value: 'fr-FR', display: localize('com_nav_lang_french') },
|
||||
{ value: 'it-IT', display: localize('com_nav_lang_italian') },
|
||||
{ value: 'pl-PL', display: localize('com_nav_lang_polish') },
|
||||
{ value: 'pt-BR', display: localize('com_nav_lang_brazilian_portuguese') },
|
||||
{ value: 'ru-RU', display: localize('com_nav_lang_russian') },
|
||||
{ value: 'ja-JP', display: localize('com_nav_lang_japanese') },
|
||||
{ value: 'sv-SE', display: localize('com_nav_lang_swedish') },
|
||||
{ value: 'ko-KR', display: localize('com_nav_lang_korean') },
|
||||
{ value: 'vi-VN', display: localize('com_nav_lang_vietnamese') },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_language')}</div>
|
||||
<select
|
||||
className="w-24 rounded border border-black/10 bg-transparent px-3 py-2 text-sm dark:border-white/20 dark:bg-gray-900"
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
value={langcode}
|
||||
>
|
||||
<option value="auto">{localize('com_nav_lang_auto')}</option>
|
||||
<option value="ar-EG">{localize('com_nav_lang_arabic')}</option>
|
||||
<option value="en-US">{localize('com_nav_lang_english')}</option>
|
||||
<option value="zh-CN">{localize('com_nav_lang_chinese')}</option>
|
||||
<option value="zh-TC">{localize('com_nav_lang_traditionalchinese')}</option>
|
||||
<option value="de-DE">{localize('com_nav_lang_german')}</option>
|
||||
<option value="es-ES">{localize('com_nav_lang_spanish')}</option>
|
||||
<option value="fr-FR">{localize('com_nav_lang_french')}</option>
|
||||
<option value="it-IT">{localize('com_nav_lang_italian')}</option>
|
||||
<option value="pl-PL">{localize('com_nav_lang_polish')}</option>
|
||||
<option value="pt-BR">{localize('com_nav_lang_brazilian_portuguese')}</option>
|
||||
<option value="ru-RU">{localize('com_nav_lang_russian')}</option>
|
||||
<option value="ja-JP">{localize('com_nav_lang_japanese')}</option>
|
||||
<option value="sv-SE">{localize('com_nav_lang_swedish')}</option>
|
||||
<option value="ko-KR">{localize('com_nav_lang_korean')}</option>
|
||||
<option value="vi-VN">{localize('com_nav_lang_vietnamese')}</option>
|
||||
<option value="tr-TR">{localize('com_nav_lang_turkish')}</option>
|
||||
</select>
|
||||
<div> {localize('com_nav_language')} </div>
|
||||
<Dropdown value={langcode} onChange={onChange} options={languageOptions} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,25 +13,36 @@ describe('LangSelector', () => {
|
|||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
const { getByText, getByDisplayValue } = render(
|
||||
const { getByText } = render(
|
||||
<RecoilRoot>
|
||||
<LangSelector langcode="en-US" onChange={mockOnChange} />
|
||||
</RecoilRoot>,
|
||||
);
|
||||
|
||||
expect(getByText('Language')).toBeInTheDocument();
|
||||
expect(getByDisplayValue('English')).toBeInTheDocument();
|
||||
expect(getByText('English')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onChange when the select value changes', () => {
|
||||
const { getByDisplayValue } = render(
|
||||
it('calls onChange when the select value changes', async () => {
|
||||
const { getByText, getByTestId } = render(
|
||||
<RecoilRoot>
|
||||
<LangSelector langcode="en-US" onChange={mockOnChange} />
|
||||
</RecoilRoot>,
|
||||
);
|
||||
|
||||
fireEvent.change(getByDisplayValue('English'), { target: { value: 'it-IT' } });
|
||||
expect(getByText('English')).toBeInTheDocument();
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith('it-IT');
|
||||
// Find the dropdown button by data-testid
|
||||
const dropdownButton = getByTestId('dropdown-menu');
|
||||
|
||||
// Open the dropdown
|
||||
fireEvent.click(dropdownButton);
|
||||
|
||||
// Find the option by text and click it
|
||||
const darkOption = getByText('Italiano');
|
||||
fireEvent.click(darkOption);
|
||||
|
||||
// Ensure that the onChange is called with the expected value after a short delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,24 +13,37 @@ describe('ThemeSelector', () => {
|
|||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
const { getByText, getByDisplayValue } = render(
|
||||
const { getByText } = render(
|
||||
<RecoilRoot>
|
||||
<ThemeSelector theme="system" onChange={mockOnChange} />
|
||||
</RecoilRoot>,
|
||||
);
|
||||
|
||||
expect(getByText('Theme')).toBeInTheDocument();
|
||||
expect(getByDisplayValue('System')).toBeInTheDocument();
|
||||
expect(getByText('System')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onChange when the select value changes', () => {
|
||||
const { getByDisplayValue } = render(
|
||||
it('calls onChange when the select value changes', async () => {
|
||||
const { getByText, getByTestId } = render(
|
||||
<RecoilRoot>
|
||||
<ThemeSelector theme="system" onChange={mockOnChange} />
|
||||
</RecoilRoot>,
|
||||
);
|
||||
|
||||
fireEvent.change(getByDisplayValue('System'), { target: { value: 'dark' } });
|
||||
expect(getByText('Theme')).toBeInTheDocument();
|
||||
|
||||
// Find the dropdown button by data-testid
|
||||
const dropdownButton = getByTestId('theme-selector');
|
||||
|
||||
// Open the dropdown
|
||||
fireEvent.click(dropdownButton);
|
||||
|
||||
// Find the option by text and click it
|
||||
const darkOption = getByText('Dark');
|
||||
fireEvent.click(darkOption);
|
||||
|
||||
// Ensure that the onChange is called with the expected value after a short delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith('dark');
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue