mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-20 01:06:11 +01:00
🔀 fix: Rerender Edge Cases After Migration to Shared Package (#8713)
* fix: render issues in PromptForm by decoupling nested dependencies as a result of @librechat/client components * fix: MemoryViewer flicker by moving EditMemoryButton and DeleteMemoryButton outside of rendering * fix: CategorySelector to use DropdownPopup for improved mobile compatibility * chore: imports
This commit is contained in:
parent
8e6eef04ab
commit
a4ca4b7d9d
4 changed files with 401 additions and 268 deletions
|
|
@ -1,10 +1,13 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { Dropdown } from '@librechat/client';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import * as Ariakit from '@ariakit/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFormContext, Controller } from 'react-hook-form';
|
||||
import { DropdownPopup } from '@librechat/client';
|
||||
import { LocalStorageKeys } from 'librechat-data-provider';
|
||||
import { useFormContext, Controller } from 'react-hook-form';
|
||||
import type { MenuItemProps } from '@librechat/client';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCategories } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
interface CategorySelectorProps {
|
||||
currentCategory?: string;
|
||||
|
|
@ -20,10 +23,11 @@ const CategorySelector: React.FC<CategorySelectorProps> = ({
|
|||
const { t } = useTranslation();
|
||||
const formContext = useFormContext();
|
||||
const { categories, emptyCategory } = useCategories();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const control = formContext.control;
|
||||
const watch = formContext.watch;
|
||||
const setValue = formContext.setValue;
|
||||
const control = formContext?.control;
|
||||
const watch = formContext?.watch;
|
||||
const setValue = formContext?.setValue;
|
||||
|
||||
const watchedCategory = watch ? watch('category') : currentCategory;
|
||||
|
||||
|
|
@ -46,53 +50,71 @@ const CategorySelector: React.FC<CategorySelectorProps> = ({
|
|||
return categoryOption;
|
||||
}, [categoryOption, t]);
|
||||
|
||||
const menuItems: MenuItemProps[] = useMemo(() => {
|
||||
if (!categories) return [];
|
||||
|
||||
return categories.map((category) => ({
|
||||
id: category.value,
|
||||
label: category.label,
|
||||
icon: 'icon' in category ? category.icon : undefined,
|
||||
onClick: () => {
|
||||
const value = category.value || '';
|
||||
if (formContext && setValue) {
|
||||
setValue('category', value, { shouldDirty: false });
|
||||
}
|
||||
localStorage.setItem(LocalStorageKeys.LAST_PROMPT_CATEGORY, value);
|
||||
onValueChange?.(value);
|
||||
setIsOpen(false);
|
||||
},
|
||||
}));
|
||||
}, [categories, formContext, setValue, onValueChange]);
|
||||
|
||||
const trigger = (
|
||||
<Ariakit.MenuButton
|
||||
className={cn(
|
||||
'focus:ring-offset-ring-offset relative inline-flex items-center justify-between rounded-xl border border-input bg-background px-3 py-2 text-sm text-text-primary transition-all duration-200 ease-in-out hover:bg-accent hover:text-accent-foreground focus:ring-ring-primary',
|
||||
'w-fit gap-2',
|
||||
className,
|
||||
)}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
aria-label="Prompt's category selector"
|
||||
aria-labelledby="category-selector-label"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
{'icon' in displayCategory && displayCategory.icon != null && (
|
||||
<span>{displayCategory.icon as ReactNode}</span>
|
||||
)}
|
||||
<span>{displayCategory.value ? displayCategory.label : t('com_ui_category')}</span>
|
||||
</div>
|
||||
<Ariakit.MenuButtonArrow />
|
||||
</Ariakit.MenuButton>
|
||||
);
|
||||
|
||||
return formContext ? (
|
||||
<Controller
|
||||
name="category"
|
||||
control={control}
|
||||
render={() => (
|
||||
<Dropdown
|
||||
value={displayCategory.value ?? ''}
|
||||
label={displayCategory.value ? undefined : t('com_ui_category')}
|
||||
onChange={(value: string) => {
|
||||
setValue('category', value, { shouldDirty: false });
|
||||
localStorage.setItem(LocalStorageKeys.LAST_PROMPT_CATEGORY, value);
|
||||
onValueChange?.(value);
|
||||
}}
|
||||
aria-labelledby="category-selector-label"
|
||||
ariaLabel="Prompt's category selector"
|
||||
className={className}
|
||||
options={categories || []}
|
||||
renderValue={() => (
|
||||
<div className="flex items-center space-x-2">
|
||||
{'icon' in displayCategory && displayCategory.icon != null && (
|
||||
<span>{displayCategory.icon as ReactNode}</span>
|
||||
)}
|
||||
<span>{displayCategory.label}</span>
|
||||
</div>
|
||||
)}
|
||||
<DropdownPopup
|
||||
trigger={trigger}
|
||||
items={menuItems}
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
menuId="category-selector-menu"
|
||||
className="mt-2"
|
||||
portal={true}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Dropdown
|
||||
value={currentCategory ?? ''}
|
||||
onChange={(value: string) => {
|
||||
localStorage.setItem(LocalStorageKeys.LAST_PROMPT_CATEGORY, value);
|
||||
onValueChange?.(value);
|
||||
}}
|
||||
aria-labelledby="category-selector-label"
|
||||
ariaLabel="Prompt's category selector"
|
||||
className={className}
|
||||
options={categories || []}
|
||||
renderValue={() => (
|
||||
<div className="flex items-center space-x-2">
|
||||
{'icon' in displayCategory && displayCategory.icon != null && (
|
||||
<span>{displayCategory.icon as ReactNode}</span>
|
||||
)}
|
||||
<span>{displayCategory.label}</span>
|
||||
</div>
|
||||
)}
|
||||
<DropdownPopup
|
||||
trigger={trigger}
|
||||
items={menuItems}
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
menuId="category-selector-menu"
|
||||
className="mt-2"
|
||||
portal={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue