mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🎨 style: Enhance UI/UX for Font Size, Mentions, and Prompts (#3575)
* style: fix prompts icon shrinking in command popover * fix: scroll into view behavior for mentions * fix: always apply default font size if not found * refactor: Update useMessageScrolling threshold and debounceRate
This commit is contained in:
parent
2bb0842650
commit
b3821c1404
7 changed files with 36 additions and 19 deletions
|
|
@ -103,15 +103,18 @@ export default function Mention({
|
|||
};
|
||||
}, []);
|
||||
|
||||
const type = commandChar !== '@' ? 'add-convo' : 'mention';
|
||||
useEffect(() => {
|
||||
const currentActiveItem = document.getElementById(`mention-item-${activeIndex}`);
|
||||
const currentActiveItem = document.getElementById(`${type}-item-${activeIndex}`);
|
||||
currentActiveItem?.scrollIntoView({ behavior: 'instant', block: 'nearest' });
|
||||
}, [activeIndex]);
|
||||
}, [type, activeIndex]);
|
||||
|
||||
return (
|
||||
<div className="absolute bottom-16 z-10 w-full space-y-2">
|
||||
<div className="popover border-token-border-light rounded-2xl border bg-white p-2 shadow-lg dark:bg-gray-700">
|
||||
<input
|
||||
// The user expects focus to transition to the input field when the popover is opened
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
ref={inputRef}
|
||||
placeholder={localize(placeholder)}
|
||||
|
|
@ -155,6 +158,7 @@ export default function Mention({
|
|||
<div className="max-h-40 overflow-y-auto">
|
||||
{(matches as MentionOption[]).map((mention, index) => (
|
||||
<MentionItem
|
||||
type={type}
|
||||
index={index}
|
||||
key={`${mention.value}-${index}`}
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -9,37 +9,37 @@ export default function MentionItem({
|
|||
icon,
|
||||
isActive,
|
||||
description,
|
||||
type = 'mention',
|
||||
}: {
|
||||
name: string;
|
||||
onClick: () => void;
|
||||
index: number;
|
||||
type?: 'prompt' | 'mention' | 'add-convo';
|
||||
icon?: React.ReactNode;
|
||||
isActive?: boolean;
|
||||
description?: string;
|
||||
}) {
|
||||
return (
|
||||
<div tabIndex={index} onClick={onClick} id={`mention-item-${index}`} className="cursor-pointer">
|
||||
<button tabIndex={index} onClick={onClick} id={`${type}-item-${index}`} className="w-full">
|
||||
<div
|
||||
className={cn(
|
||||
'text-token-text-primary bg-token-main-surface-secondary group flex h-10 items-center gap-2 rounded-lg px-2 text-sm font-medium hover:bg-gray-100 dark:hover:bg-gray-600',
|
||||
isActive ? 'bg-gray-100 dark:bg-gray-600' : '',
|
||||
'text-token-text-primary bg-token-main-surface-secondary group flex h-10 items-center gap-2 rounded-lg px-2 text-sm font-medium hover:bg-surface-secondary',
|
||||
isActive ? 'bg-surface-active' : 'bg-transparent',
|
||||
)}
|
||||
>
|
||||
{icon ? icon : null}
|
||||
<div className="flex h-fit grow flex-row justify-between space-x-2 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
<div className="flex flex-row space-x-2">
|
||||
<span className="shrink-0 truncate">{name}</span>
|
||||
<div className="flex h-5 w-5 flex-shrink-0 items-center justify-center">{icon}</div>
|
||||
<div className="flex min-w-0 flex-grow items-center justify-between">
|
||||
<div className="truncate">
|
||||
<span className="font-medium">{name}</span>
|
||||
{description ? (
|
||||
<span className="text-token-text-tertiary flex-grow truncate text-sm font-light sm:max-w-xs lg:max-w-md">
|
||||
<span className="text-token-text-tertiary ml-2 text-sm font-light">
|
||||
{description}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<span className="shrink-0 self-center">
|
||||
<Clock4 size={16} className="icon-sm" />
|
||||
</span>
|
||||
<Clock4 size={16} className="ml-2 flex-shrink-0" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ function PromptsCommand({
|
|||
label: `${group.command ? `/${group.command} - ` : ''}${group.name}: ${
|
||||
group.oneliner?.length ? group.oneliner : group.productionPrompt?.prompt ?? ''
|
||||
}`,
|
||||
icon: <CategoryIcon category={group.category ?? ''} />,
|
||||
icon: <CategoryIcon category={group.category ?? ''} className="h-5 w-5" />,
|
||||
}));
|
||||
|
||||
const promptsMap = mapPromptGroups(data);
|
||||
|
|
@ -108,7 +108,7 @@ function PromptsCommand({
|
|||
}
|
||||
|
||||
const group = promptsMap[mention.id];
|
||||
const hasVariables = detectVariables(group?.productionPrompt?.prompt ?? '');
|
||||
const hasVariables = detectVariables(group.productionPrompt?.prompt ?? '');
|
||||
if (group && hasVariables) {
|
||||
if (e && e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
|
|
@ -154,6 +154,8 @@ function PromptsCommand({
|
|||
<div className="absolute bottom-16 z-10 w-full space-y-2">
|
||||
<div className="popover border-token-border-light rounded-2xl border bg-surface-tertiary-alt p-2 shadow-lg">
|
||||
<input
|
||||
// The user expects focus to transition to the input field when the popover is opened
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
ref={inputRef}
|
||||
placeholder={localize('com_ui_command_usage_placeholder')}
|
||||
|
|
@ -204,6 +206,7 @@ function PromptsCommand({
|
|||
return (matches as PromptOption[]).map((mention, index) => (
|
||||
<MentionItem
|
||||
index={index}
|
||||
type="prompt"
|
||||
key={`${mention.value}-${index}`}
|
||||
onClick={() => {
|
||||
if (timeoutRef.current) {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import useScrollToRef from '~/hooks/useScrollToRef';
|
|||
import { useChatContext } from '~/Providers';
|
||||
import store from '~/store';
|
||||
|
||||
const threshold = 0.14;
|
||||
const debounceRate = 250;
|
||||
const threshold = 0.85;
|
||||
const debounceRate = 150;
|
||||
|
||||
export default function useMessageScrolling(messagesTree?: TMessage[] | null) {
|
||||
const autoScroll = useRecoilValue(store.autoScroll);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
//ThemeContext.js
|
||||
// source: https://plainenglish.io/blog/light-and-dark-mode-in-react-web-application-with-tailwind-css-89674496b942
|
||||
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import React, { createContext, useState, useEffect } from 'react';
|
||||
import { getInitialTheme, applyFontSize } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
type ProviderValue = {
|
||||
theme: string;
|
||||
|
|
@ -27,6 +28,7 @@ export const ThemeContext = createContext<ProviderValue>(defaultContextValue);
|
|||
|
||||
export const ThemeProvider = ({ initialTheme, children }) => {
|
||||
const [theme, setTheme] = useState(getInitialTheme);
|
||||
const setFontSize = useSetRecoilState(store.fontSize);
|
||||
|
||||
const rawSetTheme = (rawTheme: string) => {
|
||||
const root = window.document.documentElement;
|
||||
|
|
@ -54,9 +56,14 @@ export const ThemeProvider = ({ initialTheme, children }) => {
|
|||
useEffect(() => {
|
||||
const fontSize = localStorage.getItem('fontSize');
|
||||
if (fontSize == null) {
|
||||
setFontSize('text-base');
|
||||
applyFontSize('text-base');
|
||||
localStorage.setItem('fontSize', 'text-base');
|
||||
return;
|
||||
}
|
||||
applyFontSize(JSON.parse(fontSize));
|
||||
// Reason: This effect should only run once, and `setFontSize` is a stable function
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (initialTheme) {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ html {
|
|||
--text-secondary:var(--gray-600);
|
||||
--text-secondary-alt:var(--gray-500);
|
||||
--text-tertiary:var(--gray-500);
|
||||
--surface-active:var(--gray-100);
|
||||
--surface-primary:var(--white);
|
||||
--surface-primary-alt:var(--white);
|
||||
--surface-primary-contrast:var(--gray-100);
|
||||
|
|
@ -49,6 +50,7 @@ html {
|
|||
--text-secondary:var(--gray-300);
|
||||
--text-secondary-alt:var(--gray-400);
|
||||
--text-tertiary:var(--gray-500);
|
||||
--surface-active:var(--gray-600);
|
||||
--surface-primary:var(--gray-900);
|
||||
--surface-primary-alt:var(--gray-850);
|
||||
--surface-primary-contrast:var(--gray-850);
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ module.exports = {
|
|||
'text-secondary': 'var(--text-secondary)',
|
||||
'text-secondary-alt': 'var(--text-secondary-alt)',
|
||||
'text-tertiary': 'var(--text-tertiary)',
|
||||
'surface-active': 'var(--surface-active)',
|
||||
'surface-primary': 'var(--surface-primary)',
|
||||
'surface-primary-alt': 'var(--surface-primary-alt)',
|
||||
'surface-primary-contrast': 'var(--surface-primary-contrast)',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue