mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
improvement(popover): added keyboard nav to tag dropdown popover to iterate over parent & child items (#1903)
* improvement(popover): added keyboard nav to tag dropdown popover to iterate over parent & child items * code cleanup * ack PR comments
This commit is contained in:
@@ -28,7 +28,7 @@ import {
|
||||
import {
|
||||
checkTagTrigger,
|
||||
TagDropdown,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { WandPromptBar } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
import {
|
||||
checkTagTrigger,
|
||||
TagDropdown,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import { useTagSelection } from '@/hooks/use-tag-selection'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Input } from '@/components/ui/input'
|
||||
import { MAX_TAG_SLOTS } from '@/lib/knowledge/consts'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/formatted-text'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Textarea } from '@/components/emcn/components/textarea/textarea'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/formatted-text'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Input } from '@/components/emcn/components/input/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/formatted-text'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
|
||||
@@ -9,7 +9,7 @@ import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/
|
||||
import {
|
||||
checkTagTrigger,
|
||||
TagDropdown,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useKnowledgeBaseTagDefinitions } from '@/hooks/use-knowledge-base-tag-definitions'
|
||||
|
||||
@@ -18,7 +18,7 @@ import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/
|
||||
import {
|
||||
checkTagTrigger,
|
||||
TagDropdown,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import { useMcpTools } from '@/hooks/use-mcp-tools'
|
||||
|
||||
@@ -14,7 +14,7 @@ import type { ComboboxOption } from '@/components/emcn/components/combobox/combo
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/formatted-text'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EnvVarDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/env-var-dropdown'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
|
||||
/**
|
||||
* Props for the SubBlockDropdowns component.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type React from 'react'
|
||||
import { EnvVarDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/env-var-dropdown'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useTagSelection } from '@/hooks/use-tag-selection'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { EnvVarDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/env-var-dropdown'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/formatted-text'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-input'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { KeyboardNavigationHandler } from './keyboard-navigation-handler'
|
||||
@@ -0,0 +1,256 @@
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { usePopoverContext } from '@/components/emcn'
|
||||
import type { BlockTagGroup, NestedBlockTagGroup } from '../types'
|
||||
|
||||
/**
|
||||
* Keyboard navigation handler component that uses popover context
|
||||
* to enable folder navigation with arrow keys
|
||||
*/
|
||||
interface KeyboardNavigationHandlerProps {
|
||||
visible: boolean
|
||||
selectedIndex: number
|
||||
setSelectedIndex: (index: number) => void
|
||||
flatTagList: Array<{ tag: string; group?: BlockTagGroup }>
|
||||
nestedBlockTagGroups: NestedBlockTagGroup[]
|
||||
handleTagSelect: (tag: string, group?: BlockTagGroup) => void
|
||||
}
|
||||
|
||||
export const KeyboardNavigationHandler: React.FC<KeyboardNavigationHandlerProps> = ({
|
||||
visible,
|
||||
selectedIndex,
|
||||
setSelectedIndex,
|
||||
flatTagList,
|
||||
nestedBlockTagGroups,
|
||||
handleTagSelect,
|
||||
}) => {
|
||||
const { openFolder, closeFolder, isInFolder, currentFolder } = usePopoverContext()
|
||||
|
||||
const visibleIndices = useMemo(() => {
|
||||
const indices: number[] = []
|
||||
|
||||
if (isInFolder && currentFolder) {
|
||||
for (const group of nestedBlockTagGroups) {
|
||||
for (const nestedTag of group.nestedTags) {
|
||||
const folderId = `${group.blockId}-${nestedTag.key}`
|
||||
if (folderId === currentFolder && nestedTag.children) {
|
||||
// First, add the parent tag itself (so it's navigable as the first item)
|
||||
if (nestedTag.parentTag) {
|
||||
const parentIdx = flatTagList.findIndex((item) => item.tag === nestedTag.parentTag)
|
||||
if (parentIdx >= 0) {
|
||||
indices.push(parentIdx)
|
||||
}
|
||||
}
|
||||
// Then add all children
|
||||
for (const child of nestedTag.children) {
|
||||
const idx = flatTagList.findIndex((item) => item.tag === child.fullTag)
|
||||
if (idx >= 0) {
|
||||
indices.push(idx)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We're at root level, show all non-child items
|
||||
// (variables and parent tags, but not their children)
|
||||
for (let i = 0; i < flatTagList.length; i++) {
|
||||
const tag = flatTagList[i].tag
|
||||
|
||||
// Check if this is a child of a parent folder
|
||||
let isChild = false
|
||||
for (const group of nestedBlockTagGroups) {
|
||||
for (const nestedTag of group.nestedTags) {
|
||||
if (nestedTag.children) {
|
||||
for (const child of nestedTag.children) {
|
||||
if (child.fullTag === tag) {
|
||||
isChild = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isChild) break
|
||||
}
|
||||
if (isChild) break
|
||||
}
|
||||
|
||||
if (!isChild) {
|
||||
indices.push(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return indices
|
||||
}, [isInFolder, currentFolder, flatTagList, nestedBlockTagGroups])
|
||||
|
||||
// Auto-select first visible item when entering/exiting folders
|
||||
useEffect(() => {
|
||||
if (!visible || visibleIndices.length === 0) return
|
||||
|
||||
if (!visibleIndices.includes(selectedIndex)) {
|
||||
setSelectedIndex(visibleIndices[0])
|
||||
}
|
||||
}, [visible, isInFolder, currentFolder, visibleIndices, selectedIndex, setSelectedIndex])
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible || !flatTagList.length) return
|
||||
|
||||
// Helper to open a folder with proper selection callback and parent selection
|
||||
const openFolderWithSelection = (
|
||||
folderId: string,
|
||||
folderTitle: string,
|
||||
parentTag: string,
|
||||
group: BlockTagGroup
|
||||
) => {
|
||||
const selectionCallback = () => handleTagSelect(parentTag, group)
|
||||
|
||||
// Find parent tag index (which is first in visible items when in folder)
|
||||
let parentIndex = 0
|
||||
for (const g of nestedBlockTagGroups) {
|
||||
for (const nestedTag of g.nestedTags) {
|
||||
if (nestedTag.parentTag === parentTag) {
|
||||
const idx = flatTagList.findIndex((item) => item.tag === nestedTag.parentTag)
|
||||
parentIndex = idx >= 0 ? idx : 0
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openFolder(folderId, folderTitle, undefined, selectionCallback)
|
||||
setSelectedIndex(parentIndex)
|
||||
}
|
||||
|
||||
const handleKeyboardEvent = (e: KeyboardEvent) => {
|
||||
const selected = flatTagList[selectedIndex]
|
||||
if (!selected && e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return
|
||||
|
||||
let currentFolderInfo: {
|
||||
id: string
|
||||
title: string
|
||||
parentTag: string
|
||||
group: BlockTagGroup
|
||||
} | null = null
|
||||
|
||||
if (selected) {
|
||||
for (const group of nestedBlockTagGroups) {
|
||||
for (const nestedTag of group.nestedTags) {
|
||||
if (
|
||||
nestedTag.parentTag === selected.tag &&
|
||||
nestedTag.children &&
|
||||
nestedTag.children.length > 0
|
||||
) {
|
||||
currentFolderInfo = {
|
||||
id: `${selected.group?.blockId}-${nestedTag.key}`,
|
||||
title: nestedTag.display,
|
||||
parentTag: nestedTag.parentTag,
|
||||
group,
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if (currentFolderInfo) break
|
||||
}
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (visibleIndices.length > 0) {
|
||||
const currentVisibleIndex = visibleIndices.indexOf(selectedIndex)
|
||||
if (currentVisibleIndex === -1) {
|
||||
setSelectedIndex(visibleIndices[0])
|
||||
} else if (currentVisibleIndex < visibleIndices.length - 1) {
|
||||
setSelectedIndex(visibleIndices[currentVisibleIndex + 1])
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'ArrowUp':
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (visibleIndices.length > 0) {
|
||||
const currentVisibleIndex = visibleIndices.indexOf(selectedIndex)
|
||||
if (currentVisibleIndex === -1) {
|
||||
setSelectedIndex(visibleIndices[0])
|
||||
} else if (currentVisibleIndex > 0) {
|
||||
setSelectedIndex(visibleIndices[currentVisibleIndex - 1])
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'Enter':
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (selected && selectedIndex >= 0 && selectedIndex < flatTagList.length) {
|
||||
if (currentFolderInfo && !isInFolder) {
|
||||
// It's a folder, open it
|
||||
openFolderWithSelection(
|
||||
currentFolderInfo.id,
|
||||
currentFolderInfo.title,
|
||||
currentFolderInfo.parentTag,
|
||||
currentFolderInfo.group
|
||||
)
|
||||
} else {
|
||||
// Not a folder, select it
|
||||
handleTagSelect(selected.tag, selected.group)
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'ArrowRight':
|
||||
if (currentFolderInfo && !isInFolder) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
openFolderWithSelection(
|
||||
currentFolderInfo.id,
|
||||
currentFolderInfo.title,
|
||||
currentFolderInfo.parentTag,
|
||||
currentFolderInfo.group
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'ArrowLeft':
|
||||
if (isInFolder) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
closeFolder()
|
||||
let firstRootIndex = 0
|
||||
for (let i = 0; i < flatTagList.length; i++) {
|
||||
const tag = flatTagList[i].tag
|
||||
const isVariable = !tag.includes('.')
|
||||
let isParent = false
|
||||
for (const group of nestedBlockTagGroups) {
|
||||
for (const nestedTag of group.nestedTags) {
|
||||
if (nestedTag.parentTag === tag) {
|
||||
isParent = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (isParent) break
|
||||
}
|
||||
if (isVariable || isParent) {
|
||||
firstRootIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
setSelectedIndex(firstRootIndex)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeyboardEvent, true)
|
||||
return () => window.removeEventListener('keydown', handleKeyboardEvent, true)
|
||||
}, [
|
||||
visible,
|
||||
selectedIndex,
|
||||
visibleIndices,
|
||||
flatTagList,
|
||||
nestedBlockTagGroups,
|
||||
openFolder,
|
||||
closeFolder,
|
||||
isInFolder,
|
||||
setSelectedIndex,
|
||||
handleTagSelect,
|
||||
])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
import {
|
||||
Popover,
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
PopoverItem,
|
||||
PopoverScrollArea,
|
||||
PopoverSection,
|
||||
usePopoverContext,
|
||||
} from '@/components/emcn'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { extractFieldsFromSchema, parseResponseFormatSafely } from '@/lib/response-format'
|
||||
@@ -25,37 +26,11 @@ import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import type { BlockState } from '@/stores/workflows/workflow/types'
|
||||
import { getTool } from '@/tools/utils'
|
||||
import { KeyboardNavigationHandler } from './components/keyboard-navigation-handler'
|
||||
import type { BlockTagGroup, NestedBlockTagGroup, NestedTag } from './types'
|
||||
|
||||
const logger = createLogger('TagDropdown')
|
||||
|
||||
/**
|
||||
* Block tag group for organizing tags by block
|
||||
*/
|
||||
interface BlockTagGroup {
|
||||
blockName: string
|
||||
blockId: string
|
||||
blockType: string
|
||||
tags: string[]
|
||||
distance: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested tag structure for hierarchical display
|
||||
*/
|
||||
interface NestedTag {
|
||||
key: string
|
||||
display: string
|
||||
fullTag?: string
|
||||
children?: Array<{ key: string; display: string; fullTag: string }>
|
||||
}
|
||||
|
||||
/**
|
||||
* Block tag group with nested tag structure
|
||||
*/
|
||||
interface NestedBlockTagGroup extends BlockTagGroup {
|
||||
nestedTags: NestedTag[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the TagDropdown component
|
||||
*/
|
||||
@@ -378,6 +353,55 @@ const TagIcon: React.FC<{ icon: string; color: string }> = ({ icon, color }) =>
|
||||
</div>
|
||||
)
|
||||
|
||||
/**
|
||||
* Wrapper for PopoverBackButton that handles parent tag navigation
|
||||
*/
|
||||
const TagDropdownBackButton: React.FC<{
|
||||
selectedIndex: number
|
||||
setSelectedIndex: (index: number) => void
|
||||
flatTagList: Array<{ tag: string; group?: BlockTagGroup }>
|
||||
nestedBlockTagGroups: NestedBlockTagGroup[]
|
||||
itemRefs: React.MutableRefObject<Map<number, HTMLElement>>
|
||||
}> = ({ selectedIndex, setSelectedIndex, flatTagList, nestedBlockTagGroups, itemRefs }) => {
|
||||
const { currentFolder } = usePopoverContext()
|
||||
|
||||
// Find parent tag info for current folder
|
||||
const parentTagInfo = useMemo(() => {
|
||||
if (!currentFolder) return null
|
||||
|
||||
for (const group of nestedBlockTagGroups) {
|
||||
for (const nestedTag of group.nestedTags) {
|
||||
const folderId = `${group.blockId}-${nestedTag.key}`
|
||||
if (folderId === currentFolder && nestedTag.parentTag) {
|
||||
const parentIdx = flatTagList.findIndex((item) => item.tag === nestedTag.parentTag)
|
||||
return parentIdx >= 0 ? { index: parentIdx } : null
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}, [currentFolder, nestedBlockTagGroups, flatTagList])
|
||||
|
||||
if (!parentTagInfo) {
|
||||
return <PopoverBackButton />
|
||||
}
|
||||
|
||||
const isActive = parentTagInfo.index === selectedIndex
|
||||
|
||||
return (
|
||||
<PopoverBackButton
|
||||
folderTitleRef={(el) => {
|
||||
if (el) {
|
||||
itemRefs.current.set(parentTagInfo.index, el)
|
||||
}
|
||||
}}
|
||||
folderTitleActive={isActive}
|
||||
onFolderTitleMouseEnter={() => {
|
||||
setSelectedIndex(parentTagInfo.index)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* TagDropdown component that displays available tags (variables and block outputs)
|
||||
* for selection in input fields. Uses the Popover component system for consistent styling.
|
||||
@@ -395,6 +419,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
||||
inputRef,
|
||||
}) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
const itemRefs = useRef<Map<number, HTMLElement>>(new Map())
|
||||
|
||||
const { blocks, edges, loops, parallels } = useWorkflowStore(
|
||||
(state) => ({
|
||||
@@ -1053,11 +1078,23 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
||||
})
|
||||
|
||||
Object.entries(groupedTags).forEach(([parent, children]) => {
|
||||
nestedTags.push({
|
||||
key: parent,
|
||||
display: parent,
|
||||
children: children,
|
||||
})
|
||||
const firstChildTag = children[0]?.fullTag
|
||||
if (firstChildTag) {
|
||||
const tagParts = firstChildTag.split('.')
|
||||
const parentTag = `${tagParts[0]}.${parent}`
|
||||
nestedTags.push({
|
||||
key: parent,
|
||||
display: parent,
|
||||
parentTag: parentTag,
|
||||
children: children,
|
||||
})
|
||||
} else {
|
||||
nestedTags.push({
|
||||
key: parent,
|
||||
display: parent,
|
||||
children: children,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
directTags.forEach((directTag) => {
|
||||
@@ -1080,15 +1117,36 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
||||
|
||||
nestedBlockTagGroups.forEach((group) => {
|
||||
group.nestedTags.forEach((nestedTag) => {
|
||||
if (nestedTag.parentTag) {
|
||||
list.push({ tag: nestedTag.parentTag, group })
|
||||
}
|
||||
if (nestedTag.fullTag) {
|
||||
list.push({ tag: nestedTag.fullTag, group })
|
||||
}
|
||||
if (nestedTag.children) {
|
||||
nestedTag.children.forEach((child) => {
|
||||
list.push({ tag: child.fullTag, group })
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return list
|
||||
}, [variableTags, nestedBlockTagGroups])
|
||||
|
||||
// Auto-scroll selected item into view
|
||||
useEffect(() => {
|
||||
if (!visible || selectedIndex < 0) return
|
||||
|
||||
const element = itemRefs.current.get(selectedIndex)
|
||||
if (element) {
|
||||
element.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
})
|
||||
}
|
||||
}, [selectedIndex, visible])
|
||||
|
||||
const handleTagSelect = useCallback(
|
||||
(tag: string, blockGroup?: BlockTagGroup) => {
|
||||
let liveCursor = cursorPosition
|
||||
@@ -1192,27 +1250,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
const handleKeyboardEvent = (e: KeyboardEvent) => {
|
||||
if (!flatTagList.length) return
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setSelectedIndex((prev) => Math.min(prev + 1, flatTagList.length - 1))
|
||||
break
|
||||
case 'ArrowUp':
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setSelectedIndex((prev) => Math.max(prev - 1, 0))
|
||||
break
|
||||
case 'Enter':
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (selectedIndex >= 0 && selectedIndex < flatTagList.length) {
|
||||
const selected = flatTagList[selectedIndex]
|
||||
handleTagSelect(selected.tag, selected.group)
|
||||
}
|
||||
break
|
||||
case 'Escape':
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
@@ -1224,7 +1262,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
||||
window.addEventListener('keydown', handleKeyboardEvent, true)
|
||||
return () => window.removeEventListener('keydown', handleKeyboardEvent, true)
|
||||
}
|
||||
}, [visible, selectedIndex, flatTagList, handleTagSelect, onClose])
|
||||
}, [visible, onClose])
|
||||
|
||||
if (!visible || tags.length === 0 || flatTagList.length === 0) return null
|
||||
|
||||
@@ -1257,6 +1295,14 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
||||
}}
|
||||
/>
|
||||
</PopoverAnchor>
|
||||
<KeyboardNavigationHandler
|
||||
visible={visible}
|
||||
selectedIndex={selectedIndex}
|
||||
setSelectedIndex={setSelectedIndex}
|
||||
flatTagList={flatTagList}
|
||||
nestedBlockTagGroups={nestedBlockTagGroups}
|
||||
handleTagSelect={handleTagSelect}
|
||||
/>
|
||||
<PopoverContent
|
||||
maxHeight={240}
|
||||
className='min-w-[280px]'
|
||||
@@ -1266,7 +1312,13 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||
onCloseAutoFocus={(e) => e.preventDefault()}
|
||||
>
|
||||
<PopoverBackButton />
|
||||
<TagDropdownBackButton
|
||||
selectedIndex={selectedIndex}
|
||||
setSelectedIndex={setSelectedIndex}
|
||||
flatTagList={flatTagList}
|
||||
nestedBlockTagGroups={nestedBlockTagGroups}
|
||||
itemRefs={itemRefs}
|
||||
/>
|
||||
<PopoverScrollArea>
|
||||
{flatTagList.length === 0 ? (
|
||||
<div className='px-[6px] py-[8px] text-[#FFFFFF]/60 text-[12px]'>
|
||||
@@ -1277,7 +1329,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
||||
{variableTags.length > 0 && (
|
||||
<>
|
||||
<PopoverSection>Variables</PopoverSection>
|
||||
{variableTags.map((tag: string, index: number) => {
|
||||
{variableTags.map((tag: string) => {
|
||||
const variableInfo = variableInfoMap?.[tag] || null
|
||||
const globalIndex = flatTagList.findIndex((item) => item.tag === tag)
|
||||
|
||||
@@ -1294,6 +1346,11 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
||||
e.stopPropagation()
|
||||
handleTagSelect(tag)
|
||||
}}
|
||||
ref={(el) => {
|
||||
if (el && globalIndex >= 0) {
|
||||
itemRefs.current.set(globalIndex, el)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TagIcon icon='V' color={BLOCK_COLORS.VARIABLE} />
|
||||
<span className='flex-1 truncate'>
|
||||
@@ -1333,15 +1390,31 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
||||
if (hasChildren) {
|
||||
const folderId = `${group.blockId}-${nestedTag.key}`
|
||||
|
||||
const parentGlobalIndex = nestedTag.parentTag
|
||||
? flatTagList.findIndex((item) => item.tag === nestedTag.parentTag)
|
||||
: -1
|
||||
|
||||
return (
|
||||
<PopoverFolder
|
||||
key={folderId}
|
||||
id={folderId}
|
||||
title={nestedTag.display}
|
||||
icon={<TagIcon icon={tagIcon} color={blockColor} />}
|
||||
active={parentGlobalIndex === selectedIndex && parentGlobalIndex >= 0}
|
||||
onSelect={() => {
|
||||
if (nestedTag.parentTag) {
|
||||
handleTagSelect(nestedTag.parentTag, group)
|
||||
}
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
// Clear selection when hovering folder to prevent highlighting items
|
||||
setSelectedIndex(-1)
|
||||
if (parentGlobalIndex >= 0) {
|
||||
setSelectedIndex(parentGlobalIndex)
|
||||
}
|
||||
}}
|
||||
ref={(el) => {
|
||||
if (el && parentGlobalIndex >= 0) {
|
||||
itemRefs.current.set(parentGlobalIndex, el)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{nestedTag.children!.map((child) => {
|
||||
@@ -1383,6 +1456,11 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
||||
e.stopPropagation()
|
||||
handleTagSelect(child.fullTag, group)
|
||||
}}
|
||||
ref={(el) => {
|
||||
if (el && childGlobalIndex >= 0) {
|
||||
itemRefs.current.set(childGlobalIndex, el)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TagIcon icon={tagIcon} color={blockColor} />
|
||||
<span className='flex-1 truncate'>{child.display}</span>
|
||||
@@ -1457,6 +1535,11 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
||||
handleTagSelect(nestedTag.fullTag, group)
|
||||
}
|
||||
}}
|
||||
ref={(el) => {
|
||||
if (el && globalIndex >= 0) {
|
||||
itemRefs.current.set(globalIndex, el)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TagIcon icon={displayIcon} color={blockColor} />
|
||||
<span className='flex-1 truncate'>{nestedTag.display}</span>
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Block tag group for organizing tags by block
|
||||
*/
|
||||
export interface BlockTagGroup {
|
||||
blockName: string
|
||||
blockId: string
|
||||
blockType: string
|
||||
tags: string[]
|
||||
distance: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested tag structure for hierarchical display
|
||||
*/
|
||||
export interface NestedTag {
|
||||
key: string
|
||||
display: string
|
||||
fullTag?: string
|
||||
parentTag?: string // Tag for the parent object when it has children
|
||||
children?: Array<{ key: string; display: string; fullTag: string }>
|
||||
}
|
||||
|
||||
/**
|
||||
* Block tag group with nested tag structure
|
||||
*/
|
||||
export interface NestedBlockTagGroup extends BlockTagGroup {
|
||||
nestedTags: NestedTag[]
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
import {
|
||||
checkTagTrigger,
|
||||
TagDropdown,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { CodeEditor } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tool-input/components/code-editor/code-editor'
|
||||
import { WandPromptBar } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar'
|
||||
import { useWand } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-wand'
|
||||
|
||||
@@ -17,7 +17,7 @@ import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/
|
||||
import {
|
||||
checkTagTrigger,
|
||||
TagDropdown,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import { useVariablesStore } from '@/stores/panel/variables/store'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { checkEnvVarTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/env-var-dropdown'
|
||||
import { checkTagTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
import { checkTagTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useTagSelection } from '@/hooks/use-tag-selection'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { checkEnvVarTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/env-var-dropdown'
|
||||
import { checkTagTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
import { checkTagTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useTagSelection } from '@/hooks/use-tag-selection'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { ChevronUp } from 'lucide-react'
|
||||
import SimpleCodeEditor from 'react-simple-code-editor'
|
||||
import { Code as CodeEditor, Combobox, getCodeEditorProps, Input, Label } from '@/components/emcn'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import type { BlockState } from '@/stores/workflows/workflow/types'
|
||||
import type { ConnectedBlock } from '../../hooks/use-block-connections'
|
||||
import { useSubflowEditor } from '../../hooks/use-subflow-editor'
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
SYSTEM_REFERENCE_PREFIXES,
|
||||
splitReferenceSegment,
|
||||
} from '@/lib/workflows/references'
|
||||
import { checkTagTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown'
|
||||
import { checkTagTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
|
||||
import { normalizeBlockName } from '@/stores/workflows/utils'
|
||||
|
||||
@@ -40,6 +40,7 @@ export {
|
||||
PopoverSection,
|
||||
type PopoverSectionProps,
|
||||
PopoverTrigger,
|
||||
usePopoverContext,
|
||||
} from './popover/popover'
|
||||
export { Textarea } from './textarea/textarea'
|
||||
export { Tooltip } from './tooltip/tooltip'
|
||||
|
||||
@@ -84,11 +84,17 @@ const POPOVER_ITEM_HOVER_CLASSES = {
|
||||
type PopoverVariant = 'default' | 'primary'
|
||||
|
||||
interface PopoverContextValue {
|
||||
openFolder: (id: string, title: string, onLoad?: () => void | Promise<void>) => void
|
||||
openFolder: (
|
||||
id: string,
|
||||
title: string,
|
||||
onLoad?: () => void | Promise<void>,
|
||||
onSelect?: () => void
|
||||
) => void
|
||||
closeFolder: () => void
|
||||
currentFolder: string | null
|
||||
isInFolder: boolean
|
||||
folderTitle: string | null
|
||||
onFolderSelect: (() => void) | null
|
||||
variant: PopoverVariant
|
||||
searchQuery: string
|
||||
setSearchQuery: (query: string) => void
|
||||
@@ -126,12 +132,14 @@ export interface PopoverProps extends PopoverPrimitive.PopoverProps {
|
||||
const Popover: React.FC<PopoverProps> = ({ children, variant = 'default', ...props }) => {
|
||||
const [currentFolder, setCurrentFolder] = React.useState<string | null>(null)
|
||||
const [folderTitle, setFolderTitle] = React.useState<string | null>(null)
|
||||
const [onFolderSelect, setOnFolderSelect] = React.useState<(() => void) | null>(null)
|
||||
const [searchQuery, setSearchQuery] = React.useState<string>('')
|
||||
|
||||
const openFolder = React.useCallback(
|
||||
(id: string, title: string, onLoad?: () => void | Promise<void>) => {
|
||||
(id: string, title: string, onLoad?: () => void | Promise<void>, onSelect?: () => void) => {
|
||||
setCurrentFolder(id)
|
||||
setFolderTitle(title)
|
||||
setOnFolderSelect(onSelect ?? null)
|
||||
if (onLoad) {
|
||||
void Promise.resolve(onLoad())
|
||||
}
|
||||
@@ -142,6 +150,7 @@ const Popover: React.FC<PopoverProps> = ({ children, variant = 'default', ...pro
|
||||
const closeFolder = React.useCallback(() => {
|
||||
setCurrentFolder(null)
|
||||
setFolderTitle(null)
|
||||
setOnFolderSelect(null)
|
||||
}, [])
|
||||
|
||||
const contextValue: PopoverContextValue = React.useMemo(
|
||||
@@ -151,11 +160,12 @@ const Popover: React.FC<PopoverProps> = ({ children, variant = 'default', ...pro
|
||||
currentFolder,
|
||||
isInFolder: currentFolder !== null,
|
||||
folderTitle,
|
||||
onFolderSelect,
|
||||
variant,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
}),
|
||||
[openFolder, closeFolder, currentFolder, folderTitle, variant, searchQuery]
|
||||
[openFolder, closeFolder, currentFolder, folderTitle, onFolderSelect, variant, searchQuery]
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -426,6 +436,10 @@ export interface PopoverFolderProps extends Omit<React.HTMLAttributes<HTMLDivEle
|
||||
* Function to call when folder is opened (for lazy loading)
|
||||
*/
|
||||
onOpen?: () => void | Promise<void>
|
||||
/**
|
||||
* Function to call when the folder title is selected (from within the folder view)
|
||||
*/
|
||||
onSelect?: () => void
|
||||
/**
|
||||
* Children to render when folder is open
|
||||
*/
|
||||
@@ -449,7 +463,7 @@ export interface PopoverFolderProps extends Omit<React.HTMLAttributes<HTMLDivEle
|
||||
* ```
|
||||
*/
|
||||
const PopoverFolder = React.forwardRef<HTMLDivElement, PopoverFolderProps>(
|
||||
({ className, id, title, icon, onOpen, children, active, ...props }, ref) => {
|
||||
({ className, id, title, icon, onOpen, onSelect, children, active, ...props }, ref) => {
|
||||
const { openFolder, currentFolder, isInFolder, variant } = usePopoverContext()
|
||||
|
||||
// Don't render if we're in a different folder
|
||||
@@ -462,6 +476,12 @@ const PopoverFolder = React.forwardRef<HTMLDivElement, PopoverFolderProps>(
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
// Handle click anywhere on folder item
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
openFolder(id, title, onOpen, onSelect)
|
||||
}
|
||||
|
||||
// Otherwise, render as a clickable folder item
|
||||
return (
|
||||
<div
|
||||
@@ -474,7 +494,7 @@ const PopoverFolder = React.forwardRef<HTMLDivElement, PopoverFolderProps>(
|
||||
role='menuitem'
|
||||
aria-haspopup='true'
|
||||
aria-expanded={false}
|
||||
onClick={() => openFolder(id, title, onOpen)}
|
||||
onClick={handleClick}
|
||||
{...props}
|
||||
>
|
||||
{icon}
|
||||
@@ -487,7 +507,20 @@ const PopoverFolder = React.forwardRef<HTMLDivElement, PopoverFolderProps>(
|
||||
|
||||
PopoverFolder.displayName = 'PopoverFolder'
|
||||
|
||||
export interface PopoverBackButtonProps extends React.HTMLAttributes<HTMLDivElement> {}
|
||||
export interface PopoverBackButtonProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* Ref callback for the folder title element (when selectable)
|
||||
*/
|
||||
folderTitleRef?: (el: HTMLElement | null) => void
|
||||
/**
|
||||
* Whether the folder title is currently active/selected
|
||||
*/
|
||||
folderTitleActive?: boolean
|
||||
/**
|
||||
* Callback when mouse enters the folder title
|
||||
*/
|
||||
onFolderTitleMouseEnter?: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Back button component that appears when inside a folder.
|
||||
@@ -504,8 +537,8 @@ export interface PopoverBackButtonProps extends React.HTMLAttributes<HTMLDivElem
|
||||
* ```
|
||||
*/
|
||||
const PopoverBackButton = React.forwardRef<HTMLDivElement, PopoverBackButtonProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
const { isInFolder, closeFolder, folderTitle, variant } = usePopoverContext()
|
||||
({ className, folderTitleRef, folderTitleActive, onFolderTitleMouseEnter, ...props }, ref) => {
|
||||
const { isInFolder, closeFolder, folderTitle, onFolderSelect, variant } = usePopoverContext()
|
||||
|
||||
if (!isInFolder) {
|
||||
return null
|
||||
@@ -523,7 +556,26 @@ const PopoverBackButton = React.forwardRef<HTMLDivElement, PopoverBackButtonProp
|
||||
<ChevronLeft className='h-3 w-3' />
|
||||
<span>Back</span>
|
||||
</div>
|
||||
{folderTitle && (
|
||||
{folderTitle && onFolderSelect && (
|
||||
<div
|
||||
ref={folderTitleRef}
|
||||
className={cn(
|
||||
POPOVER_ITEM_BASE_CLASSES,
|
||||
folderTitleActive
|
||||
? POPOVER_ITEM_ACTIVE_CLASSES[variant]
|
||||
: POPOVER_ITEM_HOVER_CLASSES[variant]
|
||||
)}
|
||||
role='button'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onFolderSelect()
|
||||
}}
|
||||
onMouseEnter={onFolderTitleMouseEnter}
|
||||
>
|
||||
<span>{folderTitle}</span>
|
||||
</div>
|
||||
)}
|
||||
{folderTitle && !onFolderSelect && (
|
||||
<div className='px-[6px] py-[4px] font-base text-[#AEAEAE] text-[12px] dark:text-[#AEAEAE]'>
|
||||
{folderTitle}
|
||||
</div>
|
||||
@@ -605,4 +657,5 @@ export {
|
||||
PopoverFolder,
|
||||
PopoverBackButton,
|
||||
PopoverSearch,
|
||||
usePopoverContext,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user