mirror of https://github.com/langgenius/dify.git
feat: Implement Dropdown and Menu components for breadcrumb navigation in Online Drive
This commit is contained in:
parent
1ff608dfa9
commit
1bd664e655
|
|
@ -0,0 +1,61 @@
|
|||
import React, { useCallback, useState } from 'react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { RiMoreFill } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import Menu from './menu'
|
||||
|
||||
type DropdownProps = {
|
||||
startIndex: number
|
||||
breadcrumbs: string[]
|
||||
onBreadcrumbClick: (index: number) => void
|
||||
}
|
||||
|
||||
const Dropdown = ({
|
||||
startIndex,
|
||||
breadcrumbs,
|
||||
onBreadcrumbClick,
|
||||
}: DropdownProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const handleTrigger = useCallback(() => {
|
||||
setOpen(prev => !prev)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-start'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: -13,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||
<button
|
||||
type='button'
|
||||
className={cn(
|
||||
'rounded-md p-1',
|
||||
open ? 'bg-state-base-hover' : 'hover:bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<RiMoreFill className='size-4 text-text-tertiary' />
|
||||
</button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[11]'>
|
||||
<Menu
|
||||
breadcrumbs={breadcrumbs}
|
||||
startIndex={startIndex}
|
||||
onBreadcrumbClick={onBreadcrumbClick}
|
||||
/>
|
||||
</PortalToFollowElemContent>
|
||||
<span className='system-xs-regular text-divider-deep'>/</span>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Dropdown)
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import React, { useCallback } from 'react'
|
||||
|
||||
type ItemProps = {
|
||||
name: string
|
||||
index: number
|
||||
onBreadcrumbClick: (index: number) => void
|
||||
}
|
||||
|
||||
const Item = ({
|
||||
name,
|
||||
index,
|
||||
onBreadcrumbClick,
|
||||
}: ItemProps) => {
|
||||
const handleClick = useCallback(() => {
|
||||
onBreadcrumbClick(index)
|
||||
}, [index, onBreadcrumbClick])
|
||||
|
||||
return (
|
||||
<div
|
||||
className='system-md-regular rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'
|
||||
onClick={handleClick}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Item)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import React from 'react'
|
||||
import Item from './item'
|
||||
|
||||
type MenuProps = {
|
||||
breadcrumbs: string[]
|
||||
startIndex: number
|
||||
onBreadcrumbClick: (index: number) => void
|
||||
}
|
||||
|
||||
const Menu = ({
|
||||
breadcrumbs,
|
||||
startIndex,
|
||||
onBreadcrumbClick,
|
||||
}: MenuProps) => {
|
||||
return (
|
||||
<div className='flex w-[136px] flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]'>
|
||||
{breadcrumbs.map((breadcrumb, index) => {
|
||||
return (
|
||||
<Item
|
||||
name={breadcrumb}
|
||||
index={startIndex + index}
|
||||
onBreadcrumbClick={onBreadcrumbClick}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Menu)
|
||||
|
|
@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'
|
|||
import { useDataSourceStore } from '../../../../store'
|
||||
import Bucket from './bucket'
|
||||
import BreadcrumbItem from './item'
|
||||
import Dropdown from './dropdown'
|
||||
|
||||
type BreadcrumbsProps = {
|
||||
prefix: string[]
|
||||
|
|
@ -55,11 +56,11 @@ const Breadcrumbs = ({
|
|||
}, [setFileList, setPrefix, setSelectedFileList])
|
||||
|
||||
const handleClickBreadcrumb = useCallback((index: number) => {
|
||||
const newPrefix = breadcrumbs.prefixBreadcrumbs.slice(0, index - 1)
|
||||
const newPrefix = prefix.slice(0, index - 1)
|
||||
setFileList([])
|
||||
setSelectedFileList([])
|
||||
setPrefix(newPrefix)
|
||||
}, [breadcrumbs.prefixBreadcrumbs, setFileList, setPrefix, setSelectedFileList])
|
||||
}, [prefix, setFileList, setPrefix, setSelectedFileList])
|
||||
|
||||
return (
|
||||
<div className='flex grow items-center py-1'>
|
||||
|
|
@ -106,6 +107,33 @@ const Breadcrumbs = ({
|
|||
})}
|
||||
</>
|
||||
)}
|
||||
{breadcrumbs.needCollapsed && (
|
||||
<>
|
||||
{breadcrumbs.prefixBreadcrumbs.map((breadcrumb, index) => {
|
||||
return (
|
||||
<BreadcrumbItem
|
||||
key={`${breadcrumb}-${index}`}
|
||||
index={index}
|
||||
handleClick={handleClickBreadcrumb}
|
||||
name={breadcrumb}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<Dropdown
|
||||
startIndex={breadcrumbs.prefixBreadcrumbs.length}
|
||||
breadcrumbs={breadcrumbs.collapsedBreadcrumbs}
|
||||
onBreadcrumbClick={handleClickBreadcrumb}
|
||||
/>
|
||||
<BreadcrumbItem
|
||||
index={prefix.length - 1}
|
||||
handleClick={handleClickBreadcrumb}
|
||||
name={breadcrumbs.lastBreadcrumb}
|
||||
isActive={true}
|
||||
disabled={true}
|
||||
showSeparator={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ const OnlineDrive = ({
|
|||
},
|
||||
{
|
||||
onDataSourceNodeCompleted: (documentsData: DataSourceNodeCompletedResponse) => {
|
||||
const newFileList = convertOnlineDriveDataToFileList(documentsData.data)
|
||||
const newFileList = convertOnlineDriveDataToFileList(documentsData.data, prefix)
|
||||
setFileList([...fileList, ...newFileList])
|
||||
setIsLoading(false)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,14 +7,18 @@ export const isFile = (path: string): boolean => {
|
|||
return filePathRegex.test(path)
|
||||
}
|
||||
|
||||
export const hasBuckets = (data: OnlineDriveData[]): boolean => {
|
||||
export const isBucketListInitiation = (data: OnlineDriveData[], prefix: string[]): boolean => {
|
||||
if (prefix.length > 0) return false
|
||||
return data.length > 1 || (data.length === 1 && data[0].files.length === 0)
|
||||
}
|
||||
|
||||
export const convertOnlineDriveDataToFileList = (data: OnlineDriveData[]): OnlineDriveFile[] => {
|
||||
export const convertOnlineDriveDataToFileList = (data: OnlineDriveData[], prefix: string[]): OnlineDriveFile[] => {
|
||||
const fileList: OnlineDriveFile[] = []
|
||||
|
||||
if (hasBuckets(data)) {
|
||||
if (data.length === 0)
|
||||
return fileList
|
||||
|
||||
if (isBucketListInitiation(data, prefix)) {
|
||||
data.forEach((item) => {
|
||||
fileList.push({
|
||||
key: item.bucket,
|
||||
|
|
|
|||
Loading…
Reference in New Issue