mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
👐 a11y: Improve Fork and SplitText Accessibility (#7147)
* refactor: Replace Popover with Ariakit components for improved accessibility and UX * wip: first pass, fork a11y * feat(i18n): Add localization for fork options and related UI elements * fix: Ensure Dropdown component has correct z-index for proper layering * style: Update Fork PopoverButton styles and remove unused sideOffset prop * style: Update text colors and spacing in Fork component for improved readability * style: Enhance Fork component's UI by adding select-none class to prevent text selection * chore: Remove unused Checkbox import from Fork component * fix: Add sr-only span for accessibility in SplitText component * chore: Reorder imports in Fork component for better organization
This commit is contained in:
parent
a6f0a8244f
commit
dd23559d1f
4 changed files with 286 additions and 207 deletions
|
|
@ -1,21 +1,13 @@
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
import * as Ariakit from '@ariakit/react';
|
||||||
|
import { VisuallyHidden } from '@ariakit/react';
|
||||||
import { GitFork, InfoIcon } from 'lucide-react';
|
import { GitFork, InfoIcon } from 'lucide-react';
|
||||||
import * as Popover from '@radix-ui/react-popover';
|
|
||||||
import { ForkOptions } from 'librechat-data-provider';
|
import { ForkOptions } from 'librechat-data-provider';
|
||||||
import { GitCommit, GitBranchPlus, ListTree } from 'lucide-react';
|
import { GitCommit, GitBranchPlus, ListTree } from 'lucide-react';
|
||||||
import {
|
|
||||||
Checkbox,
|
|
||||||
HoverCard,
|
|
||||||
HoverCardTrigger,
|
|
||||||
HoverCardPortal,
|
|
||||||
HoverCardContent,
|
|
||||||
} from '~/components/ui';
|
|
||||||
import OptionHover from '~/components/SidePanel/Parameters/OptionHover';
|
|
||||||
import { TranslationKeys, useLocalize, useNavigateToConvo } from '~/hooks';
|
import { TranslationKeys, useLocalize, useNavigateToConvo } from '~/hooks';
|
||||||
import { useForkConvoMutation } from '~/data-provider';
|
import { useForkConvoMutation } from '~/data-provider';
|
||||||
import { useToastContext } from '~/Providers';
|
import { useToastContext } from '~/Providers';
|
||||||
import { ESide } from '~/common';
|
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
|
|
@ -24,11 +16,11 @@ interface PopoverButtonProps {
|
||||||
setting: ForkOptions;
|
setting: ForkOptions;
|
||||||
onClick: (setting: ForkOptions) => void;
|
onClick: (setting: ForkOptions) => void;
|
||||||
setActiveSetting: React.Dispatch<React.SetStateAction<TranslationKeys>>;
|
setActiveSetting: React.Dispatch<React.SetStateAction<TranslationKeys>>;
|
||||||
sideOffset?: number;
|
|
||||||
timeoutRef: React.MutableRefObject<NodeJS.Timeout | null>;
|
timeoutRef: React.MutableRefObject<NodeJS.Timeout | null>;
|
||||||
hoverInfo?: React.ReactNode | string;
|
hoverInfo?: React.ReactNode | string;
|
||||||
hoverTitle?: React.ReactNode | string;
|
hoverTitle?: React.ReactNode | string;
|
||||||
hoverDescription?: React.ReactNode | string;
|
hoverDescription?: React.ReactNode | string;
|
||||||
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const optionLabels: Record<ForkOptions, TranslationKeys> = {
|
const optionLabels: Record<ForkOptions, TranslationKeys> = {
|
||||||
|
|
@ -38,20 +30,34 @@ const optionLabels: Record<ForkOptions, TranslationKeys> = {
|
||||||
[ForkOptions.DEFAULT]: 'com_ui_fork_from_message',
|
[ForkOptions.DEFAULT]: 'com_ui_fork_from_message',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const chevronDown = (
|
||||||
|
<svg width="1em" height="1em" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
const PopoverButton: React.FC<PopoverButtonProps> = ({
|
const PopoverButton: React.FC<PopoverButtonProps> = ({
|
||||||
children,
|
children,
|
||||||
setting,
|
setting,
|
||||||
onClick,
|
onClick,
|
||||||
setActiveSetting,
|
setActiveSetting,
|
||||||
sideOffset = 30,
|
|
||||||
timeoutRef,
|
timeoutRef,
|
||||||
hoverInfo,
|
hoverInfo,
|
||||||
hoverTitle,
|
hoverTitle,
|
||||||
hoverDescription,
|
hoverDescription,
|
||||||
|
label,
|
||||||
}) => {
|
}) => {
|
||||||
|
const localize = useLocalize();
|
||||||
return (
|
return (
|
||||||
<HoverCard openDelay={200}>
|
<Ariakit.HovercardProvider>
|
||||||
<Popover.Close
|
<div className="flex flex-col items-center">
|
||||||
|
<Ariakit.HovercardAnchor
|
||||||
|
render={
|
||||||
|
<Ariakit.Button
|
||||||
onClick={() => onClick(setting)}
|
onClick={() => onClick(setting)}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
if (timeoutRef.current) {
|
if (timeoutRef.current) {
|
||||||
|
|
@ -68,27 +74,39 @@ const PopoverButton: React.FC<PopoverButtonProps> = ({
|
||||||
setActiveSetting(optionLabels[ForkOptions.DEFAULT]);
|
setActiveSetting(optionLabels[ForkOptions.DEFAULT]);
|
||||||
}, 175);
|
}, 175);
|
||||||
}}
|
}}
|
||||||
className="mx-1 max-w-14 flex-1 rounded-lg border-2 bg-white text-gray-700 transition duration-300 ease-in-out hover:bg-gray-200 hover:text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-600 dark:hover:text-gray-100"
|
className="mx-1 max-w-14 flex-1 rounded-lg border-2 border-border-medium bg-surface-secondary text-text-secondary transition duration-300 ease-in-out hover:border-border-xheavy hover:bg-surface-hover hover:text-text-primary"
|
||||||
type="button"
|
aria-label={label}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Popover.Close>
|
<VisuallyHidden>{label}</VisuallyHidden>
|
||||||
|
</Ariakit.Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Ariakit.HovercardDisclosure className="rounded-full text-text-secondary focus:outline-none focus:ring-2 focus:ring-ring">
|
||||||
|
<VisuallyHidden>
|
||||||
|
{localize('com_ui_fork_more_details_about', { 0: label })}
|
||||||
|
</VisuallyHidden>
|
||||||
|
{chevronDown}
|
||||||
|
</Ariakit.HovercardDisclosure>
|
||||||
{((hoverInfo != null && hoverInfo !== '') ||
|
{((hoverInfo != null && hoverInfo !== '') ||
|
||||||
(hoverTitle != null && hoverTitle !== '') ||
|
(hoverTitle != null && hoverTitle !== '') ||
|
||||||
(hoverDescription != null && hoverDescription !== '')) && (
|
(hoverDescription != null && hoverDescription !== '')) && (
|
||||||
<HoverCardPortal>
|
<Ariakit.Hovercard
|
||||||
<HoverCardContent side="right" className="z-[999] w-80 dark:bg-gray-700" sideOffset={sideOffset}>
|
gutter={16}
|
||||||
|
className="z-[999] w-80 rounded-md border border-border-medium bg-surface-secondary p-4 text-text-primary shadow-md"
|
||||||
|
portal={true}
|
||||||
|
>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="flex flex-col gap-2 text-sm text-gray-600 dark:text-gray-300">
|
<p className="flex flex-col gap-2 text-sm text-text-secondary">
|
||||||
{hoverInfo && hoverInfo}
|
{hoverInfo && hoverInfo}
|
||||||
{hoverTitle && <span className="flex flex-wrap gap-1 font-bold">{hoverTitle}</span>}
|
{hoverTitle && <span className="flex flex-wrap gap-1 font-bold">{hoverTitle}</span>}
|
||||||
{hoverDescription && hoverDescription}
|
{hoverDescription && hoverDescription}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</HoverCardContent>
|
</Ariakit.Hovercard>
|
||||||
</HoverCardPortal>
|
|
||||||
)}
|
)}
|
||||||
</HoverCard>
|
</div>
|
||||||
|
</Ariakit.HovercardProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -114,6 +132,9 @@ export default function Fork({
|
||||||
const [activeSetting, setActiveSetting] = useState(optionLabels.default);
|
const [activeSetting, setActiveSetting] = useState(optionLabels.default);
|
||||||
const [splitAtTarget, setSplitAtTarget] = useRecoilState(store.splitAtTarget);
|
const [splitAtTarget, setSplitAtTarget] = useRecoilState(store.splitAtTarget);
|
||||||
const [rememberGlobal, setRememberGlobal] = useRecoilState(store.rememberDefaultFork);
|
const [rememberGlobal, setRememberGlobal] = useRecoilState(store.rememberDefaultFork);
|
||||||
|
const popoverStore = Ariakit.usePopoverStore({
|
||||||
|
placement: 'top',
|
||||||
|
});
|
||||||
const forkConvo = useForkConvoMutation({
|
const forkConvo = useForkConvoMutation({
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
navigateToConvo(data.conversation);
|
navigateToConvo(data.conversation);
|
||||||
|
|
@ -157,11 +178,11 @@ export default function Fork({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover.Root>
|
<>
|
||||||
<Popover.Trigger asChild>
|
<Ariakit.PopoverAnchor store={popoverStore}>
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
'hover-button active rounded-md p-1 text-gray-500 hover:bg-gray-100 hover:text-gray-500 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible ',
|
'hover-button active rounded-md p-1 text-gray-500 hover:bg-gray-100 hover:text-gray-500 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible',
|
||||||
'data-[state=open]:active focus:opacity-100 data-[state=open]:bg-gray-100 data-[state=open]:text-gray-500 data-[state=open]:dark:bg-gray-700 data-[state=open]:dark:text-gray-200',
|
'data-[state=open]:active focus:opacity-100 data-[state=open]:bg-gray-100 data-[state=open]:text-gray-500 data-[state=open]:dark:bg-gray-700 data-[state=open]:dark:text-gray-200',
|
||||||
!isLast ? 'data-[state=open]:opacity-100 md:opacity-0 md:group-hover:opacity-100' : '',
|
!isLast ? 'data-[state=open]:opacity-100 md:opacity-0 md:group-hover:opacity-100' : '',
|
||||||
)}
|
)}
|
||||||
|
|
@ -175,38 +196,52 @@ export default function Fork({
|
||||||
option: forkSetting,
|
option: forkSetting,
|
||||||
latestMessageId,
|
latestMessageId,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
popoverStore.toggle();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
title={localize('com_ui_fork')}
|
aria-label={localize('com_ui_fork')}
|
||||||
>
|
>
|
||||||
<GitFork className="h-4 w-4 hover:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400" />
|
<GitFork className="h-4 w-4 hover:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400" />
|
||||||
</button>
|
</button>
|
||||||
</Popover.Trigger>
|
</Ariakit.PopoverAnchor>
|
||||||
<Popover.Portal>
|
<Ariakit.Popover
|
||||||
<div dir="ltr">
|
store={popoverStore}
|
||||||
<Popover.Content
|
gutter={5}
|
||||||
side="top"
|
className="flex min-h-[120px] min-w-[215px] flex-col gap-3 overflow-hidden rounded-lg border border-border-heavy bg-surface-secondary p-2 px-3 shadow-lg"
|
||||||
role="menu"
|
style={{
|
||||||
className="bg-token-surface-primary flex min-h-[120px] min-w-[215px] flex-col gap-3 overflow-hidden rounded-lg bg-white p-2 px-3 shadow-lg dark:bg-gray-700"
|
outline: 'none',
|
||||||
style={{ outline: 'none', pointerEvents: 'auto', boxSizing: 'border-box' }}
|
pointerEvents: 'auto',
|
||||||
tabIndex={-1}
|
zIndex: 50,
|
||||||
sideOffset={5}
|
}}
|
||||||
align="center"
|
portal={true}
|
||||||
>
|
>
|
||||||
<div className="flex h-6 w-full items-center justify-center text-sm dark:text-gray-200">
|
<div className="flex h-8 w-full items-center justify-center text-sm text-text-primary">
|
||||||
{localize(activeSetting )}
|
{localize(activeSetting)}
|
||||||
<HoverCard openDelay={50}>
|
<Ariakit.HovercardProvider>
|
||||||
<HoverCardTrigger asChild>
|
<div className="ml-auto flex h-6 w-6 items-center justify-center gap-1">
|
||||||
<InfoIcon className="ml-auto flex h-4 w-4 gap-2 text-gray-500 dark:text-white/50" />
|
<Ariakit.HovercardAnchor
|
||||||
</HoverCardTrigger>
|
render={
|
||||||
<HoverCardPortal>
|
<button
|
||||||
<HoverCardContent
|
className="flex h-5 w-5 items-center rounded-full text-text-secondary"
|
||||||
side="right"
|
aria-label={localize('com_ui_fork_info_button_label')}
|
||||||
className="z-[999] w-80 dark:bg-gray-700"
|
|
||||||
sideOffset={19}
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-2 space-y-2 text-sm text-gray-600 dark:text-gray-300">
|
<InfoIcon />
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Ariakit.HovercardDisclosure className="rounded-full text-text-secondary focus:outline-none focus:ring-2 focus:ring-ring">
|
||||||
|
<VisuallyHidden>{localize('com_ui_fork_more_info_options')}</VisuallyHidden>
|
||||||
|
{chevronDown}
|
||||||
|
</Ariakit.HovercardDisclosure>
|
||||||
|
</div>
|
||||||
|
<Ariakit.Hovercard
|
||||||
|
gutter={19}
|
||||||
|
className="z-[999] w-80 rounded-md border border-border-medium bg-surface-secondary p-4 text-text-primary shadow-md"
|
||||||
|
portal={true}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-2 space-y-2 text-sm text-text-secondary">
|
||||||
<span>{localize('com_ui_fork_info_1')}</span>
|
<span>{localize('com_ui_fork_info_1')}</span>
|
||||||
<span>{localize('com_ui_fork_info_2')}</span>
|
<span>{localize('com_ui_fork_info_2')}</span>
|
||||||
<span>
|
<span>
|
||||||
|
|
@ -215,17 +250,16 @@ export default function Fork({
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</HoverCardContent>
|
</Ariakit.Hovercard>
|
||||||
</HoverCardPortal>
|
</Ariakit.HovercardProvider>
|
||||||
</HoverCard>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-full w-full items-center justify-center gap-1">
|
<div className="flex h-full w-full items-center justify-center gap-1">
|
||||||
<PopoverButton
|
<PopoverButton
|
||||||
sideOffset={155}
|
|
||||||
setActiveSetting={setActiveSetting}
|
setActiveSetting={setActiveSetting}
|
||||||
timeoutRef={timeoutRef}
|
timeoutRef={timeoutRef}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
setting={ForkOptions.DIRECT_PATH}
|
setting={ForkOptions.DIRECT_PATH}
|
||||||
|
label={localize(optionLabels[ForkOptions.DIRECT_PATH])}
|
||||||
hoverTitle={
|
hoverTitle={
|
||||||
<>
|
<>
|
||||||
<GitCommit className="h-5 w-5 rotate-90" />
|
<GitCommit className="h-5 w-5 rotate-90" />
|
||||||
|
|
@ -234,16 +268,14 @@ export default function Fork({
|
||||||
}
|
}
|
||||||
hoverDescription={localize('com_ui_fork_info_visible')}
|
hoverDescription={localize('com_ui_fork_info_visible')}
|
||||||
>
|
>
|
||||||
<HoverCardTrigger asChild>
|
|
||||||
<GitCommit className="h-full w-full rotate-90 p-2" />
|
<GitCommit className="h-full w-full rotate-90 p-2" />
|
||||||
</HoverCardTrigger>
|
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
<PopoverButton
|
<PopoverButton
|
||||||
sideOffset={90}
|
|
||||||
setActiveSetting={setActiveSetting}
|
setActiveSetting={setActiveSetting}
|
||||||
timeoutRef={timeoutRef}
|
timeoutRef={timeoutRef}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
setting={ForkOptions.INCLUDE_BRANCHES}
|
setting={ForkOptions.INCLUDE_BRANCHES}
|
||||||
|
label={localize(optionLabels[ForkOptions.INCLUDE_BRANCHES])}
|
||||||
hoverTitle={
|
hoverTitle={
|
||||||
<>
|
<>
|
||||||
<GitBranchPlus className="h-4 w-4 rotate-180" />
|
<GitBranchPlus className="h-4 w-4 rotate-180" />
|
||||||
|
|
@ -252,16 +284,14 @@ export default function Fork({
|
||||||
}
|
}
|
||||||
hoverDescription={localize('com_ui_fork_info_branches')}
|
hoverDescription={localize('com_ui_fork_info_branches')}
|
||||||
>
|
>
|
||||||
<HoverCardTrigger asChild>
|
|
||||||
<GitBranchPlus className="h-full w-full rotate-180 p-2" />
|
<GitBranchPlus className="h-full w-full rotate-180 p-2" />
|
||||||
</HoverCardTrigger>
|
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
<PopoverButton
|
<PopoverButton
|
||||||
sideOffset={25}
|
|
||||||
setActiveSetting={setActiveSetting}
|
setActiveSetting={setActiveSetting}
|
||||||
timeoutRef={timeoutRef}
|
timeoutRef={timeoutRef}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
setting={ForkOptions.TARGET_LEVEL}
|
setting={ForkOptions.TARGET_LEVEL}
|
||||||
|
label={localize(optionLabels[ForkOptions.TARGET_LEVEL])}
|
||||||
hoverTitle={
|
hoverTitle={
|
||||||
<>
|
<>
|
||||||
<ListTree className="h-5 w-5" />
|
<ListTree className="h-5 w-5" />
|
||||||
|
|
@ -272,58 +302,97 @@ export default function Fork({
|
||||||
}
|
}
|
||||||
hoverDescription={localize('com_ui_fork_info_target')}
|
hoverDescription={localize('com_ui_fork_info_target')}
|
||||||
>
|
>
|
||||||
<HoverCardTrigger asChild>
|
|
||||||
<ListTree className="h-full w-full p-2" />
|
<ListTree className="h-full w-full p-2" />
|
||||||
</HoverCardTrigger>
|
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
</div>
|
</div>
|
||||||
<HoverCard openDelay={50}>
|
<Ariakit.HovercardProvider>
|
||||||
<HoverCardTrigger asChild>
|
<div className="flex items-center">
|
||||||
<div className="flex h-6 w-full items-center justify-start text-sm dark:text-gray-300 dark:hover:text-gray-200">
|
<Ariakit.HovercardAnchor
|
||||||
<Checkbox
|
render={
|
||||||
|
<div className="flex h-6 w-full select-none items-center justify-start rounded-md text-sm text-text-secondary hover:text-text-primary">
|
||||||
|
<Ariakit.Checkbox
|
||||||
|
id="split-target-checkbox"
|
||||||
checked={splitAtTarget}
|
checked={splitAtTarget}
|
||||||
onCheckedChange={(checked: boolean) => setSplitAtTarget(checked)}
|
onChange={(event) => setSplitAtTarget(event.target.checked)}
|
||||||
className="m-2 transition duration-300 ease-in-out"
|
className="m-2 h-4 w-4 rounded-sm border border-primary ring-offset-background transition duration-300 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground"
|
||||||
|
aria-label={localize('com_ui_fork_split_target')}
|
||||||
/>
|
/>
|
||||||
|
<label htmlFor="split-target-checkbox" className="ml-2 cursor-pointer">
|
||||||
{localize('com_ui_fork_split_target')}
|
{localize('com_ui_fork_split_target')}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</HoverCardTrigger>
|
}
|
||||||
<OptionHover
|
|
||||||
side={ESide.Right}
|
|
||||||
description="com_ui_fork_info_start"
|
|
||||||
langCode={true}
|
|
||||||
sideOffset={20}
|
|
||||||
/>
|
/>
|
||||||
</HoverCard>
|
<Ariakit.HovercardDisclosure className="rounded-full text-text-secondary focus:outline-none focus:ring-2 focus:ring-ring">
|
||||||
<HoverCard openDelay={50}>
|
<VisuallyHidden>
|
||||||
<HoverCardTrigger asChild>
|
{localize('com_ui_fork_more_info_split_target', {
|
||||||
<div className="flex h-6 w-full items-center justify-start text-sm dark:text-gray-300 dark:hover:text-gray-200">
|
0: localize('com_ui_fork_split_target'),
|
||||||
<Checkbox
|
})}
|
||||||
|
</VisuallyHidden>
|
||||||
|
{chevronDown}
|
||||||
|
</Ariakit.HovercardDisclosure>
|
||||||
|
</div>
|
||||||
|
<Ariakit.Hovercard
|
||||||
|
gutter={32}
|
||||||
|
className="z-[999] w-80 select-none rounded-md border border-border-medium bg-surface-secondary p-4 text-text-primary shadow-md"
|
||||||
|
portal={true}
|
||||||
|
>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-sm text-text-secondary">{localize('com_ui_fork_info_start')}</p>
|
||||||
|
</div>
|
||||||
|
</Ariakit.Hovercard>
|
||||||
|
</Ariakit.HovercardProvider>
|
||||||
|
<Ariakit.HovercardProvider>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Ariakit.HovercardAnchor
|
||||||
|
render={
|
||||||
|
<div
|
||||||
|
onClick={() => setRemember((prev) => !prev)}
|
||||||
|
className="flex h-6 w-full select-none items-center justify-start rounded-md text-sm text-text-secondary hover:text-text-primary"
|
||||||
|
>
|
||||||
|
<Ariakit.Checkbox
|
||||||
|
id="remember-checkbox"
|
||||||
checked={remember}
|
checked={remember}
|
||||||
onCheckedChange={(checked: boolean) => {
|
onChange={(event) => {
|
||||||
|
const checked = event.target.checked;
|
||||||
|
console.log('checked', checked);
|
||||||
if (checked) {
|
if (checked) {
|
||||||
showToast({
|
showToast({
|
||||||
message: localize('com_ui_fork_remember_checked'),
|
message: localize('com_ui_fork_remember_checked'),
|
||||||
status: 'info',
|
status: 'info',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setRemember(checked);
|
return setRemember(checked);
|
||||||
}}
|
}}
|
||||||
className="m-2 transition duration-300 ease-in-out"
|
className="m-2 h-4 w-4 rounded-sm border border-primary ring-offset-background transition duration-300 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground"
|
||||||
|
aria-label={localize('com_ui_fork_remember')}
|
||||||
/>
|
/>
|
||||||
|
<label htmlFor="remember-checkbox" className="ml-2 cursor-pointer">
|
||||||
{localize('com_ui_fork_remember')}
|
{localize('com_ui_fork_remember')}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</HoverCardTrigger>
|
}
|
||||||
<OptionHover
|
|
||||||
side={ESide.Right}
|
|
||||||
description="com_ui_fork_info_remember"
|
|
||||||
langCode={true}
|
|
||||||
sideOffset={20}
|
|
||||||
/>
|
/>
|
||||||
</HoverCard>
|
<Ariakit.HovercardDisclosure className="rounded-full text-text-secondary focus:outline-none focus:ring-2 focus:ring-ring">
|
||||||
</Popover.Content>
|
<VisuallyHidden>
|
||||||
|
{localize('com_ui_fork_more_info_remember', {
|
||||||
|
0: localize('com_ui_fork_remember'),
|
||||||
|
})}
|
||||||
|
</VisuallyHidden>
|
||||||
|
{chevronDown}
|
||||||
|
</Ariakit.HovercardDisclosure>
|
||||||
</div>
|
</div>
|
||||||
</Popover.Portal>
|
<Ariakit.Hovercard
|
||||||
</Popover.Root>
|
gutter={14}
|
||||||
|
className="z-[999] w-80 rounded-md border border-border-medium bg-surface-secondary p-4 text-text-primary shadow-md"
|
||||||
|
portal={true}
|
||||||
|
>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-sm text-text-secondary">{localize('com_ui_fork_info_remember')}</p>
|
||||||
|
</div>
|
||||||
|
</Ariakit.Hovercard>
|
||||||
|
</Ariakit.HovercardProvider>
|
||||||
|
</Ariakit.Popover>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ export const ForkSettings = () => {
|
||||||
options={forkOptions}
|
options={forkOptions}
|
||||||
sizeClasses="w-[200px]"
|
sizeClasses="w-[200px]"
|
||||||
testId="fork-setting-dropdown"
|
testId="fork-setting-dropdown"
|
||||||
|
className="z-[50]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -90,10 +90,13 @@ const SplitText: React.FC<SplitTextProps> = ({
|
||||||
}, [inView, text, onLineCountChange]);
|
}, [inView, text, onLineCountChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<span className="sr-only">{text}</span>
|
||||||
<p
|
<p
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={`split-parent inline overflow-hidden ${className}`}
|
className={`split-parent inline overflow-hidden ${className}`}
|
||||||
style={{ textAlign, whiteSpace: 'normal', wordWrap: 'break-word' }}
|
style={{ textAlign, whiteSpace: 'normal', wordWrap: 'break-word' }}
|
||||||
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
{words.map((word, wordIndex) => (
|
{words.map((word, wordIndex) => (
|
||||||
<span key={wordIndex} style={{ display: 'inline-block', whiteSpace: 'nowrap' }}>
|
<span key={wordIndex} style={{ display: 'inline-block', whiteSpace: 'nowrap' }}>
|
||||||
|
|
@ -117,6 +120,7 @@ const SplitText: React.FC<SplitTextProps> = ({
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</p>
|
</p>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -864,5 +864,10 @@
|
||||||
"com_ui_yes": "Yes",
|
"com_ui_yes": "Yes",
|
||||||
"com_ui_zoom": "Zoom",
|
"com_ui_zoom": "Zoom",
|
||||||
"com_user_message": "You",
|
"com_user_message": "You",
|
||||||
"com_warning_resubmit_unsupported": "Resubmitting the AI message is not supported for this endpoint."
|
"com_warning_resubmit_unsupported": "Resubmitting the AI message is not supported for this endpoint.",
|
||||||
|
"com_ui_fork_more_details_about": "View additional information and details about the \"{{0}}\" fork option",
|
||||||
|
"com_ui_fork_more_info_options": "View detailed explanation of all fork options and their behaviors",
|
||||||
|
"com_ui_fork_info_button_label": "View information about forking conversations",
|
||||||
|
"com_ui_fork_more_info_split_target": "View explanation of how the \"{{0}}\" option affects which messages are included in your fork",
|
||||||
|
"com_ui_fork_more_info_remember": "View explanation of how the \"{{0}}\" option saves your preferences for future forks"
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue