🪄 refactor: UI Polish and Admin Dialog Unification (#11108)

* refactor(OpenSidebar): removed useless classNames

* style(Header): update hover styles across various components for improved UI consistency

* style(Nav): update hover styles in AccountSettings and SearchBar for improved UI consistency

* style: update button classes for consistent hover effects and improved UI responsiveness

* style(Nav, OpenSidebar, Header, Convo): improve UI responsiveness and animation transitions

* style(PresetsMenu, NewChat): update icon sizes and improve component styling for better UI consistency

* style(Nav, Root): enhance sidebar mobile animations and responsiveness for better UI experience

* style(ExportAndShareMenu, BookmarkMenu): update icon sizes for improved UI consistency

* style: remove transition duration from button classes for improved UI responsiveness

* style(CustomMenu, ModelSelector): update background colors for improved UI consistency and responsiveness

* style(ExportAndShareMenu): update icon color for improved UI consistency

* style(TemporaryChat): refine button styles for improved UI consistency and responsiveness

* style(BookmarkNav): refactor to use DropdownPopup and remove BookmarkNavItems for improved UI consistency and functionality

* style(CustomMenu, EndpointItem): enhance UI elements for improved consistency and accessibility

* style(EndpointItem): adjust gap in icon container for improved layout consistency

* style(CustomMenu, EndpointItem): update focus ring color for improved UI consistency

* style(EndpointItem): update icon color for improved UI consistency in dark theme

* style: update focus styles for improved accessibility and consistency across components

* refactor(Nav): extract sidebar width to NAV_WIDTH constant

Centralize mobile (320px) and desktop (260px) sidebar widths in a single
exported constant to avoid magic numbers and ensure consistency.

* fix(BookmarkNav): memoize handlers used in useMemo

Wrap handleTagClick and handleClear in useCallback and add them to the
dropdownItems useMemo dependency array to prevent stale closures.

* feat: introduce FilterInput component and replace existing inputs with it across multiple components

* feat(DataTable): replace custom input with FilterInput component for improved filtering

* fix: Nested dialog overlay stacking issue

Fixes overlay appearing behind content when opening nested dialogs.
Introduced dynamic z-index calculation based on dialog depth using React context.

- First dialog: overlay z-50, content z-100
- Nested dialogs increment by 60: overlay z-110/content z-160, etc.

Preserves a11y escape key handling from #10975 and #10851.

Regression from #11008 (afb67fcf1) which increased content z-index
without adjusting overlay z-index for nested dialog scenarios.

* Refactor admin settings components to use a unified AdminSettingsDialog

- Removed redundant code from AdminSettings, MCPAdminSettings, and Memories AdminSettings components.
- Introduced AdminSettingsDialog component to handle permission management for different sections.
- Updated permission handling logic to use a consistent structure across components.
- Enhanced role selection and permission confirmation features in the new dialog.
- Improved UI consistency and maintainability by centralizing dialog functionality.

* refactor(Memory): memory management UI components and replace MemoryViewer with MemoryPanel

* refactor(Memory): enhance UI components for Memory dialogs and improve input styling

* refactor(Bookmarks): improve bookmark management UI with enhanced styling

* refactor(translations): remove redundant filter input and bookmark count entries

* refactor(Convo): integrate useShiftKey hook for enhanced keyboard interaction and improve UI responsiveness
This commit is contained in:
Marco Beretta 2025-12-28 17:01:25 +01:00 committed by GitHub
parent c21733930c
commit 5181356bef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 2115 additions and 2191 deletions

View file

@ -84,7 +84,7 @@ export default function ExportAndShareMenu({
className="inline-flex size-10 flex-shrink-0 items-center justify-center rounded-xl border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary"
>
<Share2
className="icon-md text-text-secondary"
className="icon-lg text-text-primary"
aria-hidden="true"
focusable="false"
/>

View file

@ -45,10 +45,10 @@ export default function Header() {
{!navVisible && (
<motion.div
className="flex items-center gap-2"
initial={{ width: 0, opacity: 0 }}
animate={{ width: 'auto', opacity: 1 }}
exit={{ width: 0, opacity: 0 }}
transition={{ duration: 0.2 }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
key="header-buttons"
>
<OpenSidebar setNavVisible={setNavVisible} className="max-md:hidden" />

View file

@ -22,7 +22,7 @@ const AttachFile = ({ disabled }: { disabled?: boolean | null }) => {
aria-label={localize('com_sidepanel_attach_files')}
disabled={isUploadDisabled}
className={cn(
'flex size-9 items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50',
'flex size-9 items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-opacity-50',
)}
onKeyDownCapture={(e) => {
if (!inputRef.current) {

View file

@ -234,7 +234,7 @@ const AttachFileMenu = ({
id="attach-file-menu-button"
aria-label="Attach File Options"
className={cn(
'flex size-9 items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50',
'flex size-9 items-center justify-center rounded-full p-1 hover:bg-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-opacity-50',
isPopoverActive && 'bg-surface-hover',
)}
>

View file

@ -1,5 +1,4 @@
import { useState } from 'react';
import { Search } from 'lucide-react';
import { useSetRecoilState } from 'recoil';
import {
flexRender,
@ -17,7 +16,6 @@ import type {
} from '@tanstack/react-table';
import { FileContext } from 'librechat-data-provider';
import {
Input,
Table,
Button,
Spinner,
@ -26,6 +24,7 @@ import {
TableCell,
TableHead,
TrashIcon,
FilterInput,
TableHeader,
useMediaQuery,
} from '@librechat/client';
@ -115,23 +114,13 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
)}
{!isSmallScreen && <span className="ml-2">{localize('com_ui_delete')}</span>}
</Button>
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 z-10 h-4 w-4 -translate-y-1/2 text-text-secondary" />
<Input
id="files-filter"
placeholder=" "
value={(table.getColumn('filename')?.getFilterValue() as string | undefined) ?? ''}
onChange={(event) => table.getColumn('filename')?.setFilterValue(event.target.value)}
className="peer w-full pl-10 text-sm focus-visible:ring-2 focus-visible:ring-ring"
aria-label={localize('com_files_filter_input')}
/>
<label
htmlFor="files-filter"
className="pointer-events-none absolute left-10 top-1/2 -translate-y-1/2 text-sm text-text-secondary transition-all duration-200 peer-focus:top-0 peer-focus:bg-background peer-focus:px-1 peer-focus:text-xs peer-[:not(:placeholder-shown)]:top-0 peer-[:not(:placeholder-shown)]:bg-background peer-[:not(:placeholder-shown)]:px-1 peer-[:not(:placeholder-shown)]:text-xs"
>
{localize('com_files_filter')}
</label>
</div>
<FilterInput
inputId="files-filter"
label={localize('com_files_filter')}
value={(table.getColumn('filename')?.getFilterValue() as string | undefined) ?? ''}
onChange={(event) => table.getColumn('filename')?.setFilterValue(event.target.value)}
containerClassName="flex-1"
/>
<div className="relative focus-within:z-[100]">
<ColumnVisibilityDropdown
table={table}

View file

@ -311,7 +311,7 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
id="tools-dropdown-button"
aria-label="Tools Options"
className={cn(
'flex size-9 items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50',
'flex size-9 items-center justify-center rounded-full p-1 hover:bg-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-opacity-50',
isPopoverActive && 'bg-surface-hover',
)}
>

View file

@ -147,9 +147,9 @@ const BookmarkMenu: FC = () => {
return <Spinner aria-label="Spinner" />;
}
if ((tags?.length ?? 0) > 0) {
return <BookmarkFilledIcon className="icon-sm" aria-label="Filled Bookmark" />;
return <BookmarkFilledIcon className="icon-lg" aria-label="Filled Bookmark" />;
}
return <BookmarkIcon className="icon-sm" aria-label="Bookmark" />;
return <BookmarkIcon className="icon-lg" aria-label="Bookmark" />;
};
return (

View file

@ -48,8 +48,8 @@ export const CustomMenu = React.forwardRef<HTMLDivElement, CustomMenuProps>(func
!parent &&
'flex h-10 w-full items-center justify-center gap-2 rounded-xl border border-border-light px-3 py-2 text-sm text-text-primary',
menuStore.useState('open')
? 'bg-surface-tertiary hover:bg-surface-tertiary'
: 'bg-surface-secondary hover:bg-surface-tertiary',
? 'bg-surface-active-alt hover:bg-surface-active-alt'
: 'bg-presentation hover:bg-surface-active-alt',
props.className,
)}
render={parent ? <CustomMenuItem render={trigger} /> : trigger}
@ -66,7 +66,7 @@ export const CustomMenu = React.forwardRef<HTMLDivElement, CustomMenuProps>(func
className={cn(
`${parent ? 'animate-popover-left ml-3' : 'animate-popover'} outline-none! z-50 flex max-h-[min(450px,var(--popover-available-height))] w-full`,
'w-[var(--menu-width,auto)] min-w-[300px] flex-col overflow-auto rounded-xl border border-border-light',
'bg-surface-secondary px-3 py-2 text-sm text-text-primary shadow-lg',
'bg-presentation px-3 py-2 text-sm text-text-primary shadow-lg',
'max-w-[calc(100vw-4rem)] sm:max-h-[calc(65vh)] sm:max-w-[400px]',
searchable && 'p-0',
)}
@ -80,13 +80,13 @@ export const CustomMenu = React.forwardRef<HTMLDivElement, CustomMenuProps>(func
autoSelect
render={combobox}
className={cn(
'peer mt-1 h-10 w-full rounded-lg border-none bg-transparent px-2 text-base',
'peer flex h-10 w-full items-center justify-center rounded-lg border-none bg-transparent px-2 text-base',
'sm:h-8 sm:text-sm',
'focus:outline-none focus:ring-0 focus-visible:ring-2 focus-visible:ring-white',
'focus:outline-none focus:ring-0 focus-visible:ring-2 focus-visible:ring-primary',
)}
/>
{comboboxLabel && (
<label className="pointer-events-none absolute left-2.5 top-2.5 text-sm text-text-secondary transition-all duration-200 peer-[:not(:placeholder-shown)]:-top-1.5 peer-[:not(:placeholder-shown)]:left-1.5 peer-[:not(:placeholder-shown)]:bg-surface-secondary peer-[:not(:placeholder-shown)]:text-xs">
<label className="pointer-events-none absolute left-2.5 top-2.5 text-sm text-text-secondary transition-all duration-200 peer-[:not(:placeholder-shown)]:-top-1.5 peer-[:not(:placeholder-shown)]:left-1.5 peer-[:not(:placeholder-shown)]:bg-presentation peer-[:not(:placeholder-shown)]:text-xs sm:top-1.5">
{comboboxLabel}
</label>
)}
@ -168,7 +168,7 @@ export const CustomMenuItem = React.forwardRef<HTMLDivElement, CustomMenuItemPro
blurOnHoverEnd: false,
...props,
className: cn(
'relative flex cursor-default items-center gap-2 rounded-lg p-2 outline-none! scroll-m-1 scroll-mt-[calc(var(--combobox-height,0px)+var(--label-height,4px))] aria-disabled:opacity-25 data-[active-item]:bg-black/[0.075] data-[active-item]:text-black dark:data-[active-item]:bg-white/10 dark:data-[active-item]:text-white sm:py-1 sm:text-sm min-w-0 w-full before:absolute before:left-0 before:top-1 before:bottom-1 before:w-0.5 before:bg-transparent before:rounded-full data-[active-item]:before:bg-black dark:data-[active-item]:before:bg-white',
'relative flex cursor-default items-center gap-2 rounded-lg px-2 py-1 outline-none! scroll-m-1 scroll-mt-[calc(var(--combobox-height,0px)+var(--label-height,4px))] aria-disabled:opacity-25 data-[active-item]:bg-black/[0.075] data-[active-item]:text-black dark:data-[active-item]:bg-white/10 dark:data-[active-item]:text-white sm:text-sm min-w-0 w-full before:absolute before:left-0 before:top-1 before:bottom-1 before:w-0.5 before:bg-transparent before:rounded-full data-[active-item]:before:bg-black dark:data-[active-item]:before:bg-white',
props.className,
),
};

View file

@ -65,7 +65,7 @@ function ModelSelectorContent() {
description={localize('com_ui_select_model')}
render={
<button
className="my-1 flex h-10 w-full max-w-[70vw] items-center justify-center gap-2 rounded-xl border border-border-light bg-surface-secondary px-3 py-2 text-sm text-text-primary hover:bg-surface-tertiary"
className="my-1 flex h-10 w-full max-w-[70vw] items-center justify-center gap-2 rounded-xl border border-border-light bg-presentation px-3 py-2 text-sm text-text-primary hover:bg-surface-active-alt"
aria-label={localize('com_ui_select_model')}
>
{selectedIcon && React.isValidElement(selectedIcon) && (

View file

@ -1,6 +1,6 @@
import { useMemo } from 'react';
import { SettingsIcon } from 'lucide-react';
import { Spinner } from '@librechat/client';
import { Spinner, TooltipAnchor } from '@librechat/client';
import { CheckCircle2, MousePointerClick, SettingsIcon } from 'lucide-react';
import { EModelEndpoint, isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
import type { TModelSpec } from 'librechat-data-provider';
import type { Endpoint } from '~/common';
@ -28,29 +28,53 @@ const SettingsButton = ({
}) => {
const localize = useLocalize();
const text = localize('com_endpoint_config_key');
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
if (!endpoint.value) {
return;
}
e.stopPropagation();
handleOpenKeyDialog(endpoint.value as EModelEndpoint, e);
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
e.stopPropagation();
if (endpoint.value) {
handleOpenKeyDialog(endpoint.value as EModelEndpoint, e as unknown as React.MouseEvent);
}
}
};
return (
<button
type="button"
id={`endpoint-${endpoint.value}-settings`}
onClick={(e) => {
if (!endpoint.value) {
return;
}
e.stopPropagation();
handleOpenKeyDialog(endpoint.value as EModelEndpoint, e);
}}
onClick={handleClick}
onKeyDown={handleKeyDown}
className={cn(
'flex items-center overflow-visible text-text-primary transition-all duration-300 ease-in-out',
'group/button rounded-md px-1 hover:bg-surface-secondary focus:bg-surface-secondary',
'group/button flex items-center gap-1.5 rounded-md px-1.5',
'text-text-secondary transition-colors duration-150',
'hover:bg-surface-tertiary hover:text-text-primary',
'focus-visible:bg-surface-tertiary focus-visible:text-text-primary',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
className,
)}
aria-label={`${text} ${endpoint.label}`}
>
<div className="flex w-[28px] items-center gap-1 whitespace-nowrap transition-all duration-300 ease-in-out group-hover:w-auto group-focus/button:w-auto">
<SettingsIcon className="h-4 w-4 flex-shrink-0" aria-hidden="true" />
<span className="max-w-0 overflow-hidden whitespace-nowrap opacity-0 transition-all duration-300 ease-in-out group-hover:max-w-[100px] group-hover:opacity-100 group-focus/button:max-w-[100px] group-focus/button:opacity-100">
{text}
</span>
</div>
<SettingsIcon className="size-4 shrink-0" aria-hidden="true" />
<span
aria-hidden="true"
className={cn(
'grid overflow-hidden transition-[grid-template-columns,opacity] duration-150 ease-out',
'grid-cols-[0fr] opacity-0',
'group-hover/button:grid-cols-[1fr] group-hover/button:opacity-100',
'group-focus-visible/button:grid-cols-[1fr] group-focus-visible/button:opacity-100',
)}
>
<span className="min-w-0 truncate pr-0.5">{text}</span>
</span>
</button>
);
};
@ -88,21 +112,17 @@ export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
[endpointRequiresUserKey, endpoint.value],
);
const isAssistantsNotLoaded =
isAssistantsEndpoint(endpoint.value) && endpoint.models === undefined;
const renderIconLabel = () => (
<div className="flex items-center gap-2">
<div className="flex min-w-0 items-center gap-2">
{endpoint.icon && (
<div className="flex flex-shrink-0 items-center justify-center overflow-hidden">
<div className="flex shrink-0 items-center justify-center" aria-hidden="true">
{endpoint.icon}
</div>
)}
<span
className={cn(
'truncate text-left',
isUserProvided ? 'group-hover:w-24 group-focus:w-24' : '',
)}
>
{endpoint.label}
</span>
<span className="truncate text-left">{endpoint.label}</span>
</div>
);
@ -124,17 +144,14 @@ export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
<Menu
id={`endpoint-${endpoint.value}-menu`}
key={`endpoint-${endpoint.value}-item`}
className="transition-opacity duration-200 ease-in-out"
defaultOpen={endpoint.value === selectedEndpoint}
searchValue={searchValue}
onSearch={(value) => setEndpointSearchValue(endpoint.value, value)}
combobox={<input placeholder=" " />}
comboboxLabel={placeholder}
onClick={() => handleSelectEndpoint(endpoint)}
label={
<div
onClick={() => handleSelectEndpoint(endpoint)}
className="group flex w-full flex-shrink cursor-pointer items-center justify-between rounded-xl px-1 py-1 text-sm"
>
<div className="group flex w-full min-w-0 items-center justify-between gap-1.5 py-1 text-sm">
{renderIconLabel()}
{isUserProvided && (
<SettingsButton endpoint={endpoint} handleOpenKeyDialog={handleOpenKeyDialog} />
@ -143,8 +160,12 @@ export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
}
>
{isAssistantsEndpoint(endpoint.value) && endpoint.models === undefined ? (
<div className="flex items-center justify-center p-2">
<Spinner />
<div
className="flex items-center justify-center p-2"
role="status"
aria-label={localize('com_ui_loading')}
>
<Spinner aria-hidden="true" />
</div>
) : (
<>
@ -179,32 +200,27 @@ export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
id={`endpoint-${endpoint.value}-menu`}
key={`endpoint-${endpoint.value}-item`}
onClick={() => handleSelectEndpoint(endpoint)}
className="flex h-8 w-full cursor-pointer items-center justify-between rounded-xl px-3 py-2 text-sm"
className="group flex w-full cursor-pointer items-center justify-between gap-1.5 py-2 text-sm"
>
<div className="group flex w-full min-w-0 items-center justify-between">
{renderIconLabel()}
<div className="flex items-center gap-2">
{endpointRequiresUserKey(endpoint.value) && (
<SettingsButton endpoint={endpoint} handleOpenKeyDialog={handleOpenKeyDialog} />
)}
{selectedEndpoint === endpoint.value && (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="block"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12ZM16.0755 7.93219C16.5272 8.25003 16.6356 8.87383 16.3178 9.32549L11.5678 16.0755C11.3931 16.3237 11.1152 16.4792 10.8123 16.4981C10.5093 16.517 10.2142 16.3973 10.0101 16.1727L7.51006 13.4227C7.13855 13.014 7.16867 12.3816 7.57733 12.0101C7.98598 11.6386 8.61843 11.6687 8.98994 12.0773L10.6504 13.9039L14.6822 8.17451C15 7.72284 15.6238 7.61436 16.0755 7.93219Z"
fill="currentColor"
/>
</svg>
)}
</div>
{renderIconLabel()}
<div className="flex shrink-0 items-center gap-2">
{endpointRequiresUserKey(endpoint.value) && (
<SettingsButton endpoint={endpoint} handleOpenKeyDialog={handleOpenKeyDialog} />
)}
{isAssistantsNotLoaded && (
<TooltipAnchor
description={localize('com_ui_click_to_view_var', { 0: endpoint.label })}
side="top"
render={
<span className="flex items-center">
<MousePointerClick className="size-4 text-text-secondary" aria-hidden="true" />
</span>
}
/>
)}
{selectedEndpoint === endpoint.value && !isAssistantsNotLoaded && (
<CheckCircle2 className="size-4 shrink-0 text-text-primary" aria-hidden="true" />
)}
</div>
</MenuItem>
);

View file

@ -29,7 +29,7 @@ export default function HeaderNewChat() {
variant="outline"
data-testid="wide-header-new-chat-button"
aria-label={localize('com_ui_new_chat')}
className="rounded-xl border border-border-light bg-surface-secondary p-2 hover:bg-surface-hover max-md:hidden"
className="rounded-xl duration-0 hover:bg-surface-active-alt max-md:hidden"
onClick={clickHandler}
>
<NewChatIcon />

View file

@ -1,3 +1,4 @@
import { startTransition } from 'react';
import { TooltipAnchor, Button, Sidebar } from '@librechat/client';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
@ -17,9 +18,13 @@ export default function OpenSidebar({
const localize = useLocalize();
const handleClick = () => {
setNavVisible((prev) => {
localStorage.setItem('navVisible', JSON.stringify(!prev));
return !prev;
// Use startTransition to mark this as a non-urgent update
// This prevents blocking the main thread during the cascade of re-renders
startTransition(() => {
setNavVisible((prev) => {
localStorage.setItem('navVisible', JSON.stringify(!prev));
return !prev;
});
});
// Delay focus until after the sidebar animation completes (200ms)
setTimeout(() => {
@ -39,10 +44,7 @@ export default function OpenSidebar({
aria-label={localize('com_nav_open_sidebar')}
aria-expanded={false}
aria-controls="chat-history-nav"
className={cn(
'rounded-xl border border-border-light bg-surface-secondary p-2 hover:bg-surface-hover',
className,
)}
className={cn('rounded-xl duration-0 hover:bg-surface-active-alt', className)}
onClick={handleClick}
>
<Sidebar aria-hidden="true" />

View file

@ -49,16 +49,22 @@ const PresetsMenu: FC = () => {
<Trigger asChild>
<TooltipAnchor
ref={presetsMenuTriggerRef}
id="presets-button"
aria-label={localize('com_endpoint_examples')}
description={localize('com_endpoint_examples')}
tabIndex={0}
role="button"
data-testid="presets-button"
className="inline-flex size-10 flex-shrink-0 items-center justify-center rounded-xl border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary"
>
<BookCopy size={16} aria-hidden="true" />
</TooltipAnchor>
render={
<Button
size="icon"
variant="outline"
tabIndex={0}
id="presets-button"
data-testid="presets-button"
aria-label={localize('com_endpoint_examples')}
className="rounded-xl p-2 duration-0 hover:bg-surface-active-alt max-md:hidden"
// className="inline-flex size-10 flex-shrink-0 items-center justify-center rounded-xl border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary"
>
<BookCopy className="icon-lg" aria-hidden="true" />
</Button>
}
></TooltipAnchor>
</Trigger>
<Portal>
<div
@ -74,7 +80,7 @@ const PresetsMenu: FC = () => {
<Content
side="bottom"
align="center"
className="mt-2 max-h-[495px] overflow-x-hidden rounded-lg border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-700 dark:text-white md:min-w-[400px]"
className="mt-2 max-h-[495px] overflow-x-hidden rounded-lg border border-border-light bg-surface-secondary text-text-primary shadow-lg md:min-w-[400px]"
>
<PresetItems
presets={presetsQuery.data}

View file

@ -216,7 +216,7 @@ function FeedbackButtons({
function buttonClasses(isActive: boolean, isLast: boolean) {
return cn(
'hover-button rounded-lg p-1.5 text-text-secondary-alt transition-colors duration-200',
'hover-button rounded-lg p-1.5 text-text-secondary-alt',
'hover:text-text-primary hover:bg-surface-hover',
'md:group-hover:visible md:group-focus-within:visible md:group-[.final-completion]:visible',
!isLast && 'md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100',

View file

@ -227,7 +227,7 @@ export default function Fork({
});
const buttonStyle = cn(
'hover-button rounded-lg p-1.5 text-text-secondary-alt transition-colors duration-200',
'hover-button rounded-lg p-1.5 text-text-secondary-alt',
'hover:text-text-primary hover:bg-surface-hover',
'md:group-hover:visible md:group-focus-within:visible md:group-[.final-completion]:visible',
!isLast && 'md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100',

View file

@ -82,7 +82,7 @@ const HoverButton = memo(
className = '',
}: HoverButtonProps) => {
const buttonStyle = cn(
'hover-button rounded-lg p-1.5 text-text-secondary-alt transition-colors duration-200',
'hover-button rounded-lg p-1.5 text-text-secondary-alt',
'hover:text-text-primary hover:bg-surface-hover',
'md:group-hover:visible md:group-focus-within:visible md:group-[.final-completion]:visible',
!isLast && 'md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100',

View file

@ -24,7 +24,7 @@ export default function SiblingSwitch({
};
const buttonStyle = cn(
'hover-button rounded-lg p-1.5 text-text-secondary-alt transition-colors duration-200',
'hover-button rounded-lg p-1.5 text-text-secondary-alt',
'hover:text-text-primary hover:bg-surface-hover',
'md:group-hover:visible md:group-focus-within:visible md:group-[.final-completion]:visible',
'focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white focus-visible:outline-none',

View file

@ -14,8 +14,6 @@ export function TemporaryChat() {
const temporaryBadge = {
id: 'temporary',
icon: MessageCircleDashed,
label: 'com_ui_temporary' as const,
atom: store.isTemporary,
isAvailable: true,
};
@ -37,26 +35,20 @@ export function TemporaryChat() {
return (
<div className="relative flex flex-wrap items-center gap-2">
<TooltipAnchor
description={localize(temporaryBadge.label)}
description={localize('com_ui_temporary')}
render={
<button
onClick={handleBadgeToggle}
aria-label={localize(temporaryBadge.label)}
aria-label={localize('com_ui_temporary')}
aria-pressed={isTemporary}
className={cn(
'inline-flex size-10 flex-shrink-0 items-center justify-center rounded-xl border border-border-light text-text-primary transition-all ease-in-out hover:bg-surface-tertiary',
'inline-flex size-10 flex-shrink-0 items-center justify-center rounded-xl border border-border-light text-text-primary transition-all ease-in-out',
isTemporary
? 'bg-surface-active shadow-md'
: 'bg-transparent shadow-sm hover:bg-surface-hover hover:shadow-md',
'active:shadow-inner',
? 'bg-surface-active'
: 'bg-presentation shadow-sm hover:bg-surface-active-alt',
)}
>
{temporaryBadge.icon && (
<temporaryBadge.icon
className={cn('relative h-5 w-5 md:h-4 md:w-4', !temporaryBadge.label && 'mx-auto')}
aria-hidden="true"
/>
)}
<MessageCircleDashed className="icon-lg" aria-hidden="true" />
</button>
}
/>