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:
yyh
2026-01-27 00:14:25 +08:00
parent b57b1a6926
commit bf12445960
4 changed files with 66 additions and 72 deletions

View File

@ -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')}

View File

@ -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} />
</>
)

View File

@ -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 />

View File

@ -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>
)