mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 08:58:09 +08:00
fix(workflow): make FileTree and ArtifactsSection scroll independently
The sidebar layout was broken when ArtifactsSection expanded - it would squeeze the FileTree and neither area could scroll. This restructures the layout so each section has its own scroll container with proper height constraints.
This commit is contained in:
@ -45,45 +45,47 @@ const ArtifactsSection: FC<ArtifactsSectionProps> = ({ className }) => {
|
||||
const showSpinner = isLoading
|
||||
|
||||
return (
|
||||
<div className={cn('shrink-0 border-t border-divider-regular p-1', className)}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleToggle}
|
||||
className={cn(
|
||||
'flex w-full items-center rounded-md py-1 pl-2 pr-1.5',
|
||||
'hover:bg-state-base-hover',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-components-input-border-active',
|
||||
)}
|
||||
aria-expanded={isExpanded}
|
||||
aria-label={t('skillSidebar.artifacts.openArtifacts')}
|
||||
>
|
||||
<div className="flex flex-1 items-center gap-1 py-0.5">
|
||||
<div className="flex size-5 items-center justify-center">
|
||||
<FolderSpark className="size-4 text-text-secondary" aria-hidden="true" />
|
||||
<div className={cn('flex max-h-[40%] flex-col border-t border-divider-regular', className)}>
|
||||
<div className="shrink-0 p-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleToggle}
|
||||
className={cn(
|
||||
'flex w-full items-center rounded-md py-1 pl-2 pr-1.5',
|
||||
'hover:bg-state-base-hover',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-components-input-border-active',
|
||||
)}
|
||||
aria-expanded={isExpanded}
|
||||
aria-label={t('skillSidebar.artifacts.openArtifacts')}
|
||||
>
|
||||
<div className="flex flex-1 items-center gap-1 py-0.5">
|
||||
<div className="flex size-5 items-center justify-center">
|
||||
<FolderSpark className="size-4 text-text-secondary" aria-hidden="true" />
|
||||
</div>
|
||||
<span className="system-sm-semibold uppercase text-text-secondary">
|
||||
{t('skillSidebar.artifacts.title')}
|
||||
</span>
|
||||
</div>
|
||||
<span className="system-sm-semibold uppercase text-text-secondary">
|
||||
{t('skillSidebar.artifacts.title')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative flex items-center">
|
||||
{showSpinner
|
||||
? <RiLoader2Line className="size-3.5 animate-spin text-text-tertiary" aria-hidden="true" />
|
||||
: (
|
||||
<>
|
||||
{showBlueDot && (
|
||||
<div className="absolute -left-2 size-[7px] rounded-full border border-white bg-state-accent-solid" />
|
||||
)}
|
||||
{isExpanded
|
||||
? <RiArrowDownSLine className="size-4 text-text-tertiary" aria-hidden="true" />
|
||||
: <RiArrowRightSLine className="size-4 text-text-tertiary" aria-hidden="true" />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
<div className="relative flex items-center">
|
||||
{showSpinner
|
||||
? <RiLoader2Line className="size-3.5 animate-spin text-text-tertiary" aria-hidden="true" />
|
||||
: (
|
||||
<>
|
||||
{showBlueDot && (
|
||||
<div className="absolute -left-2 size-[7px] rounded-full border border-white bg-state-accent-solid" />
|
||||
)}
|
||||
{isExpanded
|
||||
? <RiArrowDownSLine className="size-4 text-text-tertiary" aria-hidden="true" />
|
||||
: <RiArrowRightSLine className="size-4 text-text-tertiary" aria-hidden="true" />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isExpanded && !isLoading && (
|
||||
<div className="flex flex-col gap-px">
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-1 pb-1">
|
||||
{hasFiles
|
||||
? (
|
||||
<ArtifactsTree
|
||||
@ -93,7 +95,7 @@ const ArtifactsSection: FC<ArtifactsSectionProps> = ({ className }) => {
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div className="px-2.5 pb-1.5 pt-0.5">
|
||||
<div className="px-1.5 pb-0.5">
|
||||
<div className="rounded-lg bg-background-section p-3">
|
||||
<p className="system-xs-regular text-text-tertiary">
|
||||
{t('skillSidebar.artifacts.emptyState')}
|
||||
|
||||
@ -25,7 +25,6 @@ import { useSkillAssetTreeData } from '../hooks/use-skill-asset-tree'
|
||||
import { useSkillShortcuts } from '../hooks/use-skill-shortcuts'
|
||||
import { useSyncTreeWithActiveTab } from '../hooks/use-sync-tree-with-active-tab'
|
||||
import { isDescendantOf } from '../utils/tree-utils'
|
||||
import ArtifactsSection from './artifacts-section'
|
||||
import DragActionTooltip from './drag-action-tooltip'
|
||||
import TreeContextMenu from './tree-context-menu'
|
||||
import TreeNode from './tree-node'
|
||||
@ -235,41 +234,34 @@ const FileTree: React.FC<FileTreeProps> = ({ className }) => {
|
||||
|
||||
if (treeChildren.length === 0 && !hasPendingCreate) {
|
||||
return (
|
||||
<>
|
||||
<div className={cn('flex min-h-0 flex-1 flex-col', className)}>
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-2 px-4 text-center">
|
||||
<span className="system-xs-regular text-text-tertiary">
|
||||
{t('skillSidebar.empty')}
|
||||
</span>
|
||||
</div>
|
||||
<DropTip />
|
||||
<div className={cn('flex min-h-[150px] flex-1 flex-col overflow-y-auto', className)}>
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-2 px-4 text-center">
|
||||
<span className="system-xs-regular text-text-tertiary">
|
||||
{t('skillSidebar.empty')}
|
||||
</span>
|
||||
</div>
|
||||
<ArtifactsSection />
|
||||
</>
|
||||
<DropTip />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Search has no matching results
|
||||
if (hasSearchNoResults) {
|
||||
return (
|
||||
<>
|
||||
<div className={cn('flex min-h-0 flex-1 flex-col', className)}>
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-2 pb-20">
|
||||
<SearchMenu className="size-8 text-text-tertiary" aria-hidden="true" />
|
||||
<span className="system-xs-regular text-text-secondary">
|
||||
{t('skillSidebar.searchNoResults')}
|
||||
</span>
|
||||
<Button
|
||||
variant="secondary-accent"
|
||||
size="small"
|
||||
onClick={() => storeApi.getState().setFileTreeSearchTerm('')}
|
||||
>
|
||||
{t('skillSidebar.resetFilter')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className={cn('flex min-h-[150px] flex-1 flex-col overflow-y-auto', className)}>
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-2 pb-20">
|
||||
<SearchMenu className="size-8 text-text-tertiary" aria-hidden="true" />
|
||||
<span className="system-xs-regular text-text-secondary">
|
||||
{t('skillSidebar.searchNoResults')}
|
||||
</span>
|
||||
<Button
|
||||
variant="secondary-accent"
|
||||
size="small"
|
||||
onClick={() => storeApi.getState().setFileTreeSearchTerm('')}
|
||||
>
|
||||
{t('skillSidebar.resetFilter')}
|
||||
</Button>
|
||||
</div>
|
||||
<ArtifactsSection />
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -278,7 +270,7 @@ const FileTree: React.FC<FileTreeProps> = ({ className }) => {
|
||||
<div
|
||||
data-skill-tree-container
|
||||
className={cn(
|
||||
'flex min-h-0 flex-1 flex-col',
|
||||
'flex min-h-[150px] flex-1 flex-col overflow-y-auto',
|
||||
isMutating && 'pointer-events-none opacity-50',
|
||||
className,
|
||||
)}
|
||||
@ -287,7 +279,6 @@ const FileTree: React.FC<FileTreeProps> = ({ className }) => {
|
||||
ref={containerRef}
|
||||
className={cn(
|
||||
'flex min-h-0 flex-1 flex-col overflow-hidden px-1 pt-1',
|
||||
// Root dropzone highlight - dashed border without layout shift
|
||||
isRootDropzone && 'relative rounded-lg bg-state-accent-hover after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:border-[1.5px] after:border-dashed after:border-state-accent-solid after:content-[\'\']',
|
||||
)}
|
||||
onClick={handleBlankAreaClick}
|
||||
@ -321,11 +312,10 @@ const FileTree: React.FC<FileTreeProps> = ({ className }) => {
|
||||
{renderTreeNode}
|
||||
</Tree>
|
||||
</div>
|
||||
{dragOverFolderId
|
||||
? <DragActionTooltip action={currentDragType ?? 'upload'} />
|
||||
: <DropTip />}
|
||||
</div>
|
||||
{dragOverFolderId
|
||||
? <DragActionTooltip action={currentDragType ?? 'upload'} />
|
||||
: <DropTip />}
|
||||
<ArtifactsSection />
|
||||
<TreeContextMenu treeRef={treeRef} />
|
||||
</>
|
||||
)
|
||||
|
||||
@ -8,6 +8,7 @@ import ContentBody from './content-body'
|
||||
import FileContentPanel from './file-content-panel'
|
||||
import FileTabs from './file-tabs'
|
||||
import FileTree from './file-tree'
|
||||
import ArtifactsSection from './file-tree/artifacts-section'
|
||||
import { useSkillAutoSave } from './hooks/use-skill-auto-save'
|
||||
import { SkillSaveProvider } from './hooks/use-skill-save-manager'
|
||||
import Sidebar from './sidebar'
|
||||
@ -31,6 +32,7 @@ const SkillMain: FC = () => {
|
||||
<Sidebar>
|
||||
<SidebarSearchAdd />
|
||||
<FileTree />
|
||||
<ArtifactsSection />
|
||||
</Sidebar>
|
||||
<ContentArea>
|
||||
<FileTabs />
|
||||
|
||||
@ -5,7 +5,7 @@ type SidebarProps = PropsWithChildren
|
||||
|
||||
const Sidebar: FC<SidebarProps> = ({ children }) => {
|
||||
return (
|
||||
<aside className="flex w-[320px] shrink-0 flex-col gap-px overflow-hidden rounded-[10px] border border-components-panel-border-subtle bg-components-panel-bg">
|
||||
<aside className="flex h-full w-[320px] shrink-0 flex-col gap-px overflow-hidden rounded-[10px] border border-components-panel-border-subtle bg-components-panel-bg">
|
||||
{children}
|
||||
</aside>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user