mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-30 07:08:50 +01:00
🧠 style: Expanded Thinking footer, Banner links, and Copy Thoughts accessibility (#11142)
* feat(Thinking): Add ThinkingFooter component for improved UX * style(Banner): auto-style hyperlinks in banner messages * fix: Simplify ThinkingFooter component by removing unused content prop and update aria-label for accessibility * fix: Correct import order for consistency in Thinking component * fix(ThinkingFooter): Update documentation to clarify footer functionality
This commit is contained in:
parent
28f4800e95
commit
a59bab4dc7
4 changed files with 53 additions and 9 deletions
|
|
@ -38,10 +38,13 @@ export const Banner = ({ onHeightChange }: { onHeightChange?: (height: number) =
|
|||
return (
|
||||
<div
|
||||
ref={bannerRef}
|
||||
className="sticky top-0 z-20 flex items-center bg-surface-secondary px-2 py-1 text-text-primary dark:bg-gradient-to-r md:relative"
|
||||
className="sticky top-0 z-20 flex items-center bg-presentation px-2 py-1 text-text-primary dark:bg-gradient-to-r md:relative"
|
||||
>
|
||||
<div
|
||||
className={cn('text-md w-full truncate text-center', !banner.persistable && 'px-4')}
|
||||
className={cn(
|
||||
'text-md w-full truncate text-center [&_a]:text-blue-700 [&_a]:underline dark:[&_a]:text-blue-400',
|
||||
!banner.persistable && 'px-4',
|
||||
)}
|
||||
dangerouslySetInnerHTML={{ __html: banner.message }}
|
||||
></div>
|
||||
{!banner.persistable && (
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { memo, useMemo, useState, useCallback } from 'react';
|
|||
import { useAtom } from 'jotai';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { ContentTypes } from 'librechat-data-provider';
|
||||
import { ThinkingContent, ThinkingButton } from './Thinking';
|
||||
import { ThinkingContent, ThinkingButton, ThinkingFooter } from './Thinking';
|
||||
import { showThinkingAtom } from '~/store/showThinking';
|
||||
import { useMessageContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
|
@ -69,7 +69,7 @@ const Reasoning = memo(({ reasoning, isLast }: ReasoningProps) => {
|
|||
return (
|
||||
<div className="group/reasoning">
|
||||
<div className="group/thinking-container">
|
||||
<div className="sticky top-0 z-10 mb-2 bg-presentation pb-2 pt-2">
|
||||
<div className="mb-2 pb-2 pt-2">
|
||||
<ThinkingButton
|
||||
isExpanded={isExpanded}
|
||||
onClick={handleClick}
|
||||
|
|
@ -88,6 +88,7 @@ const Reasoning = memo(({ reasoning, isLast }: ReasoningProps) => {
|
|||
>
|
||||
<div className="overflow-hidden">
|
||||
<ThinkingContent>{reasoningText}</ThinkingContent>
|
||||
<ThinkingFooter onClick={handleClick} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState, useMemo, memo, useCallback } from 'react';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Lightbulb, ChevronDown } from 'lucide-react';
|
||||
import { Clipboard, CheckMark } from '@librechat/client';
|
||||
import { Lightbulb, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import type { MouseEvent, FC } from 'react';
|
||||
import { showThinkingAtom } from '~/store/showThinking';
|
||||
import { fontSizeAtom } from '~/store/fontSize';
|
||||
|
|
@ -90,13 +90,13 @@ export const ThinkingButton = memo(
|
|||
<button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
title={
|
||||
aria-label={
|
||||
isCopied
|
||||
? localize('com_ui_copied_to_clipboard')
|
||||
: localize('com_ui_copy_thoughts_to_clipboard')
|
||||
}
|
||||
className={cn(
|
||||
'rounded-lg p-1.5 text-text-secondary-alt transition-colors duration-200',
|
||||
'rounded-lg p-1.5 text-text-secondary-alt',
|
||||
isExpanded
|
||||
? 'opacity-0 group-focus-within/thinking-container:opacity-100 group-hover/thinking-container:opacity-100'
|
||||
: 'opacity-0',
|
||||
|
|
@ -104,7 +104,16 @@ export const ThinkingButton = memo(
|
|||
'focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white',
|
||||
)}
|
||||
>
|
||||
{isCopied ? <CheckMark className="h-[18px] w-[18px]" /> : <Clipboard size="19" />}
|
||||
<span className="sr-only">
|
||||
{isCopied
|
||||
? localize('com_ui_copied_to_clipboard')
|
||||
: localize('com_ui_copy_thoughts_to_clipboard')}
|
||||
</span>
|
||||
{isCopied ? (
|
||||
<CheckMark className="h-[18px] w-[18px]" aria-hidden="true" />
|
||||
) : (
|
||||
<Clipboard size="19" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -112,6 +121,34 @@ export const ThinkingButton = memo(
|
|||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* ThinkingFooter - Footer with collapse button shown at the bottom of expanded content
|
||||
* Allows users to collapse without scrolling back to the top
|
||||
*/
|
||||
export const ThinkingFooter = memo(
|
||||
({ onClick }: { onClick: (e: MouseEvent<HTMLButtonElement>) => void }) => {
|
||||
const localize = useLocalize();
|
||||
|
||||
return (
|
||||
<div className="mt-3 flex items-center justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
aria-label={localize('com_ui_collapse_thoughts')}
|
||||
className={cn(
|
||||
'rounded-lg p-1.5 text-text-secondary-alt',
|
||||
'hover:bg-surface-hover hover:text-text-primary',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white',
|
||||
)}
|
||||
>
|
||||
<span className="sr-only">{localize('com_ui_collapse_thoughts')}</span>
|
||||
<ChevronUp className="h-[18px] w-[18px]" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Thinking Component (LEGACY SYSTEM)
|
||||
*
|
||||
|
|
@ -153,7 +190,7 @@ const Thinking: React.ElementType = memo(({ children }: { children: React.ReactN
|
|||
|
||||
return (
|
||||
<div className="group/thinking-container">
|
||||
<div className="sticky top-0 z-10 mb-4 bg-presentation pb-2 pt-2">
|
||||
<div className="mb-4 pb-2 pt-2">
|
||||
<ThinkingButton
|
||||
isExpanded={isExpanded}
|
||||
onClick={handleClick}
|
||||
|
|
@ -169,6 +206,7 @@ const Thinking: React.ElementType = memo(({ children }: { children: React.ReactN
|
|||
>
|
||||
<div className="overflow-hidden">
|
||||
<ThinkingContent>{children}</ThinkingContent>
|
||||
<ThinkingFooter onClick={handleClick} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -177,6 +215,7 @@ const Thinking: React.ElementType = memo(({ children }: { children: React.ReactN
|
|||
|
||||
ThinkingButton.displayName = 'ThinkingButton';
|
||||
ThinkingContent.displayName = 'ThinkingContent';
|
||||
ThinkingFooter.displayName = 'ThinkingFooter';
|
||||
Thinking.displayName = 'Thinking';
|
||||
|
||||
export default memo(Thinking);
|
||||
|
|
|
|||
|
|
@ -798,6 +798,7 @@
|
|||
"com_ui_close_window": "Close Window",
|
||||
"com_ui_code": "Code",
|
||||
"com_ui_collapse": "Collapse",
|
||||
"com_ui_collapse_thoughts": "Collapse Thoughts",
|
||||
"com_ui_collapse_chat": "Collapse Chat",
|
||||
"com_ui_command_placeholder": "Optional: Enter a command for the prompt or name will be used",
|
||||
"com_ui_command_usage_placeholder": "Select a Prompt by command or name",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue