| 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 flex min-w-[250px] flex-col rounded-xl', |
| 'border border-border-light bg-surface-secondary px-1.5 py-1 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(); |
| }} |
| disabled={isCustomEnabled} |
| className={cn( |
| 'mb-1 flex items-center justify-between rounded-lg px-2 py-2', |
| 'cursor-pointer text-text-primary outline-none transition-colors', |
| 'hover:bg-black/[0.075] dark:hover:bg-white/10', |
| 'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10', |
| isCustomEnabled && 'cursor-not-allowed opacity-50', |
| )} |
| > |
| <div className="flex items-center gap-2"> |
| <Ariakit.MenuItemCheck checked={isShadcnEnabled} /> |
| <span className="text-sm">{localize('com_ui_include_shadcnui' as any)}</span> |
| </div> |
| </Ariakit.MenuItem> |
| |
| {/* Custom Prompt Mode Option */} |
| <Ariakit.MenuItem |
| hideOnClick={false} |
| onClick={(event) => { |
| event.preventDefault(); |
| event.stopPropagation(); |
| handleCustomToggle(); |
| }} |
| className={cn( |
| 'flex items-center justify-between rounded-lg px-2 py-2', |
| 'cursor-pointer text-text-primary outline-none transition-colors', |
| 'hover:bg-black/[0.075] dark:hover:bg-white/10', |
| 'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10', |
| )} |
| > |
| <div className="flex items-center gap-2"> |
| <Ariakit.MenuItemCheck checked={isCustomEnabled} /> |
| <span className="text-sm">{localize('com_ui_custom_prompt_mode' as any)}</span> |
| </div> |
| </Ariakit.MenuItem> |
| </div> |
| </Ariakit.Menu> |
| )} |
| </Ariakit.MenuProvider> |
| </div> |
| ); |
| }, |
| ); |
| |
| ArtifactsSubMenu.displayName = 'ArtifactsSubMenu'; |
| |
| export default React.memo(ArtifactsSubMenu); |
| |