LibreChat/client/src/components/Chat/Input/ArtifactsSubMenu.tsx

154 lines
5.5 KiB
TypeScript

import React from 'react';
import * as Ariakit from '@ariakit/react';
import { PinIcon } from '@librechat/client';
import { ChevronRight, WandSparkles } from 'lucide-react';
import { ArtifactModes } from 'librechat-data-provider';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
interface ArtifactsSubMenuProps {
isArtifactsPinned: boolean;
setIsArtifactsPinned: (value: boolean) => void;
artifactsMode: string;
handleArtifactsToggle: () => void;
handleShadcnToggle: () => void;
handleCustomToggle: () => void;
}
const ArtifactsSubMenu = React.forwardRef<HTMLDivElement, ArtifactsSubMenuProps>(
(
{
isArtifactsPinned,
setIsArtifactsPinned,
artifactsMode,
handleArtifactsToggle,
handleShadcnToggle,
handleCustomToggle,
...props
},
ref,
) => {
const localize = useLocalize();
const menuStore = Ariakit.useMenuStore({
focusLoop: true,
showTimeout: 100,
placement: 'right',
});
const isEnabled = artifactsMode !== '' && artifactsMode !== undefined;
const isShadcnEnabled = artifactsMode === ArtifactModes.SHADCNUI;
const isCustomEnabled = artifactsMode === ArtifactModes.CUSTOM;
return (
<div ref={ref}>
<Ariakit.MenuProvider store={menuStore}>
<Ariakit.MenuItem
{...props}
hideOnClick={false}
render={
<Ariakit.MenuButton
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
handleArtifactsToggle();
}}
onMouseEnter={() => {
if (isEnabled) {
menuStore.show();
}
}}
className="flex w-full cursor-pointer items-center justify-between rounded-lg p-2 hover:bg-surface-hover"
/>
}
>
<div className="flex items-center gap-2">
<WandSparkles className="icon-md" />
<span>{localize('com_ui_artifacts')}</span>
{isEnabled && <ChevronRight className="ml-auto h-3 w-3" />}
</div>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setIsArtifactsPinned(!isArtifactsPinned);
}}
className={cn(
'rounded p-1 transition-all duration-200',
'hover:bg-surface-tertiary hover:shadow-sm',
!isArtifactsPinned && 'text-text-secondary hover:text-text-primary',
)}
aria-label={isArtifactsPinned ? 'Unpin' : 'Pin'}
>
<div className="h-4 w-4">
<PinIcon unpin={isArtifactsPinned} />
</div>
</button>
</Ariakit.MenuItem>
{isEnabled && (
<Ariakit.Menu
portal={true}
unmountOnHide={true}
className={cn(
'animate-popover-left z-50 ml-3 mt-6 flex min-w-[250px] flex-col rounded-xl',
'border border-border-light bg-surface-secondary shadow-lg',
)}
>
<div className="px-2 py-1.5">
<div className="mb-2 text-xs font-medium text-text-secondary">
{localize('com_ui_artifacts_options')}
</div>
{/* Include shadcn/ui Option */}
<Ariakit.MenuItem
hideOnClick={false}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleShadcnToggle();
}}
className={cn(
'mb-1 flex items-center justify-between gap-2 rounded-lg px-2 py-2',
'cursor-pointer bg-surface-secondary text-text-primary outline-none transition-colors',
'hover:bg-surface-hover data-[active-item]:bg-surface-hover',
isShadcnEnabled && 'bg-surface-active',
)}
>
<span className="text-sm">{localize('com_ui_include_shadcnui' as any)}</span>
<div className="ml-auto flex items-center">
<Ariakit.MenuItemCheck checked={isShadcnEnabled} />
</div>
</Ariakit.MenuItem>
{/* Custom Prompt Mode Option */}
<Ariakit.MenuItem
hideOnClick={false}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleCustomToggle();
}}
className={cn(
'mb-1 flex items-center justify-between gap-2 rounded-lg px-2 py-2',
'cursor-pointer bg-surface-secondary text-text-primary outline-none transition-colors',
'hover:bg-surface-hover data-[active-item]:bg-surface-hover',
isCustomEnabled && 'bg-surface-active',
)}
>
<span className="text-sm">{localize('com_ui_custom_prompt_mode' as any)}</span>
<div className="ml-auto flex items-center">
<Ariakit.MenuItemCheck checked={isCustomEnabled} />
</div>
</Ariakit.MenuItem>
</div>
</Ariakit.Menu>
)}
</Ariakit.MenuProvider>
</div>
);
},
);
ArtifactsSubMenu.displayName = 'ArtifactsSubMenu';
export default React.memo(ArtifactsSubMenu);