fix(ui): keep scroll area focus indicator visible (#41404)

This commit is contained in:
yyh
2026-08-28 01:37:00 +00:00
committed by GitHub
parent 458c96d5ba
commit 290acf1b73
28 changed files with 174 additions and 164 deletions
@@ -403,7 +403,7 @@ const VirtualizedModelList = ({
}, [virtualizer, virtualizerRef])
return (
<ScrollArea className="relative overflow-hidden">
<ScrollArea className="overflow-hidden">
<ScrollAreaViewport
ref={scrollRef}
role="region"
@@ -381,7 +381,7 @@ const OutsideScrollingContentDemo = () => {
<ScrollAreaViewport
aria-label="Scrollable dialog viewport"
role="region"
className="h-full max-h-full max-w-full overscroll-contain group-data-ending-style/dialog:pointer-events-none"
className="overscroll-contain group-data-ending-style/dialog:pointer-events-none"
>
<ScrollAreaContent className="flex min-h-full items-center justify-center px-4 py-16">
<DialogPopup
@@ -488,11 +488,11 @@ const InsideScrollingContentDemo = () => {
title="Release notes"
description="Highlights from the latest workspace update."
/>
<ScrollArea className="relative flex min-h-0 flex-auto overflow-hidden">
<ScrollArea className="flex min-h-0 flex-auto overflow-hidden">
<ScrollAreaViewport
aria-label="Release note improvements"
role="region"
className="h-full max-h-full max-w-full overscroll-contain"
className="overscroll-contain"
>
<ScrollAreaContent>
<ReleaseNoteSections />
@@ -728,7 +728,7 @@ export const MobileNavigation: Story = {
className="h-full transition-transform duration-600 ease-[cubic-bezier(0.45,1.005,0,1.005)] group-data-ending-style:pointer-events-none group-data-starting-style:translate-y-[100dvh] motion-reduce:transition-none"
>
<ScrollAreaViewport
className="size-full touch-auto overscroll-contain"
className="touch-auto overscroll-contain"
role="region"
aria-label="Mobile drawer viewport"
>
@@ -86,6 +86,12 @@ describe('scroll area', () => {
await expect.element(screen.getByTestId('scroll-area-root')).toBeInTheDocument()
await expect.element(screen.getByTestId('scroll-area-viewport')).toBeInTheDocument()
await expect
.element(screen.getByTestId('scroll-area-root'))
.toHaveAttribute('data-dify-scroll-area')
await expect
.element(screen.getByTestId('scroll-area-viewport'))
.toHaveAttribute('data-dify-scroll-area-viewport')
await expect
.element(screen.getByTestId('scroll-area-content'))
.toHaveTextContent('Scrollable content')
@@ -192,7 +198,7 @@ describe('scroll area', () => {
.toHaveAttribute('data-orientation', 'vertical')
await expect
.element(screen.getByTestId('scroll-area-vertical-scrollbar'))
.toHaveAttribute('data-dify-scrollbar')
.toHaveAttribute('data-dify-scroll-area-scrollbar')
await expect
.element(screen.getByTestId('scroll-area-vertical-thumb'))
.toHaveAttribute('data-orientation', 'vertical')
@@ -206,7 +212,7 @@ describe('scroll area', () => {
.toHaveAttribute('data-orientation', 'horizontal')
await expect
.element(screen.getByTestId('scroll-area-horizontal-scrollbar'))
.toHaveAttribute('data-dify-scrollbar')
.toHaveAttribute('data-dify-scroll-area-scrollbar')
await expect
.element(screen.getByTestId('scroll-area-horizontal-thumb'))
.toHaveAttribute('data-orientation', 'horizontal')
@@ -18,7 +18,7 @@ const meta = {
docs: {
description: {
component:
'Compound scroll container built on Base UI Scroll Area. ScrollArea is the structural root; ScrollAreaViewport is the actual scroll and focus owner, so place scroll refs, events, role, accessible-name attributes, and overscroll behavior on the viewport. Add role="region" only when the content is important enough to be a landmark, preferring aria-labelledby when a visible heading exists. Base UI sets Viewport overflow and Content min-width inline: use style={{ overflowX: "hidden" }} for an axis override and style={{ minWidth: 0 }} for vertical content that should truncate; regular overflow-x-hidden and min-w-0 utilities cannot override those inline defaults. This is a targeted override contract for those Base UI defaults, not a general preference for inline styles over className.',
'Compound scroll container built on Base UI Scroll Area. ScrollAreaViewport is the actual scroll and focus owner, while ScrollArea reflects its focus-visible state as a root-owned overlay border above the isolated viewport contents. Place scroll refs, events, role, accessible-name attributes, and overscroll behavior on the viewport. Add role="region" only when the content is important enough to be a landmark, preferring aria-labelledby when a visible heading exists. Base UI sets Viewport overflow and Content min-width inline: use style={{ overflowX: "hidden" }} for an axis override and style={{ minWidth: 0 }} for vertical content that should truncate; regular overflow-x-hidden and min-w-0 utilities cannot override those inline defaults. This is a targeted override contract for those Base UI defaults, not a general preference for inline styles over className.',
},
},
},
@@ -109,11 +109,11 @@ export const Anatomy: Story = {
titleId="scroll-area-anatomy-title"
description="The baseline story uses ScrollArea as the structural root, with Viewport, Content, Scrollbar, and Thumb composed explicitly. Keyboard focus and accessible naming belong to the viewport."
>
<ScrollArea className="relative h-75 w-full max-w-105 min-w-0">
<ScrollArea className="h-75 w-full max-w-105 min-w-0 rounded-xl">
<ScrollAreaViewport
aria-labelledby="scroll-area-anatomy-title"
role="region"
className="h-full max-h-full max-w-full rounded-xl border-[0.5px] border-divider-subtle bg-components-panel-bg"
className="border-[0.5px] border-divider-subtle bg-components-panel-bg"
>
<VerticalContent className="flex flex-col gap-4 py-2 pr-5 pl-3 system-sm-regular leading-6 text-text-secondary">
{articleParagraphs.map((paragraph) => (
@@ -134,13 +134,13 @@ export const Vertical: Story = {
<StorySection
eyebrow="Vertical"
title="Long form content"
description="Vertical overflow keeps the official viewport focus pattern while constraining content width so text never leaks outside the frame."
description="Vertical overflow keeps focus on the viewport while the root paints the visible indicator above scrolling content."
>
<ScrollArea className="relative h-90 w-full max-w-130 min-w-0">
<ScrollArea className="h-90 w-full max-w-130 min-w-0 rounded-xl">
<ScrollAreaViewport
aria-label="Long form content"
role="region"
className="h-full max-h-full max-w-full rounded-xl border-[0.5px] border-divider-subtle bg-components-panel-bg"
className="border-[0.5px] border-divider-subtle bg-components-panel-bg"
>
<VerticalContent className="flex flex-col gap-4 p-4 pr-6 system-sm-regular leading-6 text-text-secondary">
<div className="space-y-1">
@@ -169,11 +169,11 @@ export const VerticalTruncation: Story = {
title="Constrained content width"
description="Pass style={{ minWidth: 0 }} to ScrollAreaContent when a vertical-only list should truncate long labels instead of creating horizontal scroll. A regular min-w-0 utility cannot override Base UI's inline fit-content default."
>
<ScrollArea className="relative h-48 w-full max-w-80 min-w-0">
<ScrollArea className="h-48 w-full max-w-80 min-w-0 rounded-xl">
<ScrollAreaViewport
aria-label="Vertical file list"
role="region"
className="h-full max-h-full max-w-full rounded-xl border-[0.5px] border-divider-subtle bg-components-panel-bg"
className="border-[0.5px] border-divider-subtle bg-components-panel-bg"
>
<VerticalContent className="flex flex-col gap-0.5 p-2">
{fileRows.map((file) => (
@@ -202,19 +202,14 @@ export const ScrollFade: Story = {
<StorySection
eyebrow="Fade"
title="Viewport mask with root focus"
description="This mirrors the Base UI scroll-fade example: the viewport owns the mask and the root owns the focus outline so the indicator is never clipped."
description="The viewport owns the mask and semantic focus while the shared root indicator stays outside the masked paint layer."
>
<ScrollArea
className={cn(
'relative h-90 w-full max-w-130 min-w-0',
'has-[>_:first-child:focus-visible]:outline-2 has-[>_:first-child:focus-visible]:outline-offset-0 has-[>_:first-child:focus-visible]:outline-state-accent-solid',
)}
>
<ScrollArea className="h-90 w-full max-w-130 min-w-0 rounded-xl">
<ScrollAreaViewport
aria-label="Scroll fade article"
role="region"
className={cn(
'h-full max-h-full max-w-full rounded-xl bg-components-panel-bg outline-none focus-visible:outline-none',
'bg-components-panel-bg',
'mask-linear-[to_bottom,transparent_0,black_min(40px,var(--scroll-area-overflow-y-start)),black_calc(100%-min(40px,var(--scroll-area-overflow-y-end,40px))),transparent_100%] mask-no-repeat',
)}
>
@@ -239,14 +234,14 @@ export const Horizontal: Story = {
<StorySection
eyebrow="Horizontal"
title="Single axis row"
description="Horizontal overflow keeps Base UI's content sizing behavior and uses the same viewport focus treatment on the scrollable element."
description="Horizontal overflow keeps Base UI's content sizing behavior and the same root-owned visible focus treatment."
className="mx-auto max-w-190"
>
<ScrollArea className="relative h-46 w-full max-w-130 min-w-0">
<ScrollArea className="h-46 w-full max-w-130 min-w-0 rounded-xl">
<ScrollAreaViewport
aria-label="Horizontal numbered row"
role="region"
className="h-full max-h-full max-w-full rounded-xl border-[0.5px] border-divider-subtle bg-components-panel-bg"
className="border-[0.5px] border-divider-subtle bg-components-panel-bg"
>
<ScrollAreaContent className="min-h-full min-w-max p-4 pb-6">
<div className="grid grid-cols-[repeat(18,6.25rem)] gap-3">
@@ -276,11 +271,11 @@ export const BothAxes: Story = {
title="Numbered grid"
description="This follows the official two-axis example: both scrollbars are rendered and Corner reserves the intersection."
>
<ScrollArea className="relative h-85 w-full max-w-140 min-w-0">
<ScrollArea className="h-85 w-full max-w-140 min-w-0 rounded-xl">
<ScrollAreaViewport
aria-label="Numbered grid"
role="region"
className="h-full max-h-full max-w-full rounded-xl border-[0.5px] border-divider-subtle bg-components-panel-bg"
className="border-[0.5px] border-divider-subtle bg-components-panel-bg"
>
<ScrollAreaContent className="pt-3 pr-6 pb-6 pl-3">
<div className="grid grid-cols-[repeat(10,6.25rem)] grid-rows-[repeat(10,6.25rem)] gap-3">
+17 -13
View File
@@ -3,8 +3,15 @@
import { ScrollArea as BaseScrollArea } from '@base-ui/react/scroll-area'
import { cn } from '../cn'
const ScrollArea = BaseScrollArea.Root
type ScrollAreaProps = BaseScrollArea.Root.Props
type ScrollAreaProps = Omit<BaseScrollArea.Root.Props, 'className'> & {
className?: string
}
function ScrollArea({ className, ...props }: ScrollAreaProps) {
return (
<BaseScrollArea.Root {...props} data-dify-scroll-area="" className={cn('isolate', className)} />
)
}
const ScrollAreaContent = BaseScrollArea.Content
type ScrollAreaContentProps = BaseScrollArea.Content.Props
@@ -26,20 +33,17 @@ const scrollAreaThumbClassName = cn(
'active:bg-state-base-handle-hover',
)
const scrollAreaViewportClassName = cn(
'size-full min-h-0 min-w-0',
'focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-state-accent-solid focus-visible:outline-solid',
)
const scrollAreaCornerClassName = 'bg-transparent'
type ScrollAreaViewportProps = Omit<BaseScrollArea.Viewport.Props, 'className'> & {
className?: string
}
function ScrollAreaViewport({ className, ...props }: ScrollAreaViewportProps) {
return (
<BaseScrollArea.Viewport className={cn(scrollAreaViewportClassName, className)} {...props} />
<BaseScrollArea.Viewport
{...props}
data-dify-scroll-area-viewport=""
className={cn('isolate size-full rounded-[inherit] outline-none', className)}
/>
)
}
@@ -50,9 +54,9 @@ type ScrollAreaScrollbarProps = Omit<BaseScrollArea.Scrollbar.Props, 'className'
function ScrollAreaScrollbar({ className, ...props }: ScrollAreaScrollbarProps) {
return (
<BaseScrollArea.Scrollbar
data-dify-scrollbar=""
className={cn(scrollAreaScrollbarClassName, className)}
{...props}
data-dify-scroll-area-scrollbar=""
className={cn(scrollAreaScrollbarClassName, className)}
/>
)
}
@@ -70,7 +74,7 @@ type ScrollAreaCornerProps = Omit<BaseScrollArea.Corner.Props, 'className'> & {
}
function ScrollAreaCorner({ className, ...props }: ScrollAreaCornerProps) {
return <BaseScrollArea.Corner className={cn(scrollAreaCornerClassName, className)} {...props} />
return <BaseScrollArea.Corner className={cn('bg-transparent', className)} {...props} />
}
export {
+31 -12
View File
@@ -1,5 +1,20 @@
[data-dify-scrollbar]::before,
[data-dify-scrollbar]::after {
[data-dify-scroll-area]::after {
content: '';
position: absolute;
inset: 0;
z-index: 1;
border: 0 solid transparent;
border-radius: inherit;
pointer-events: none;
}
[data-dify-scroll-area]:has(> [data-dify-scroll-area-viewport]:focus-visible)::after {
border-width: 2px;
border-color: var(--color-state-accent-solid);
}
[data-dify-scroll-area-scrollbar]::before,
[data-dify-scroll-area-scrollbar]::after {
content: '';
position: absolute;
z-index: 1;
@@ -9,7 +24,7 @@
transition: opacity 150ms ease;
}
[data-dify-scrollbar][data-orientation='vertical']::before {
[data-dify-scroll-area-scrollbar][data-orientation='vertical']::before {
left: 50%;
top: 4px;
width: 4px;
@@ -18,7 +33,7 @@
background: linear-gradient(to bottom, var(--color-components-panel-bg), transparent);
}
[data-dify-scrollbar][data-orientation='vertical']::after {
[data-dify-scroll-area-scrollbar][data-orientation='vertical']::after {
left: 50%;
bottom: 4px;
width: 4px;
@@ -27,7 +42,7 @@
background: linear-gradient(to top, var(--color-components-panel-bg), transparent);
}
[data-dify-scrollbar][data-orientation='horizontal']::before {
[data-dify-scroll-area-scrollbar][data-orientation='horizontal']::before {
top: 50%;
left: 4px;
width: 12px;
@@ -36,7 +51,7 @@
background: linear-gradient(to right, var(--color-components-panel-bg), transparent);
}
[data-dify-scrollbar][data-orientation='horizontal']::after {
[data-dify-scroll-area-scrollbar][data-orientation='horizontal']::after {
top: 50%;
right: 4px;
width: 12px;
@@ -45,25 +60,29 @@
background: linear-gradient(to left, var(--color-components-panel-bg), transparent);
}
[data-dify-scrollbar][data-orientation='vertical']:not([data-overflow-y-start])::before {
[data-dify-scroll-area-scrollbar][data-orientation='vertical']:not(
[data-overflow-y-start]
)::before {
opacity: 1;
}
[data-dify-scrollbar][data-orientation='vertical']:not([data-overflow-y-end])::after {
[data-dify-scroll-area-scrollbar][data-orientation='vertical']:not([data-overflow-y-end])::after {
opacity: 1;
}
[data-dify-scrollbar][data-orientation='horizontal']:not([data-overflow-x-start])::before {
[data-dify-scroll-area-scrollbar][data-orientation='horizontal']:not(
[data-overflow-x-start]
)::before {
opacity: 1;
}
[data-dify-scrollbar][data-orientation='horizontal']:not([data-overflow-x-end])::after {
[data-dify-scroll-area-scrollbar][data-orientation='horizontal']:not([data-overflow-x-end])::after {
opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
[data-dify-scrollbar]::before,
[data-dify-scrollbar]::after {
[data-dify-scroll-area-scrollbar]::before,
[data-dify-scroll-area-scrollbar]::after {
transition: none;
}
}
@@ -140,7 +140,7 @@ export default function AddMemberOrGroupDialog({
className="relative flex max-h-[400px] w-[400px] flex-col overflow-hidden bg-components-panel-bg-blur p-0 backdrop-blur-[5px]"
>
<PopoverTitle className="sr-only">{searchLabel}</PopoverTitle>
<ScrollArea className="relative min-h-0 flex-1 overflow-hidden">
<ScrollArea className="min-h-0 flex-1 overflow-hidden">
<ScrollAreaViewport
ref={scrollRootRef}
role="region"
@@ -413,9 +413,12 @@ const SettingsModal: FC<ISettingsModalProps> = ({
</div>
)}
{/* form body */}
<ScrollArea className="relative row-start-2 min-h-0 overflow-hidden">
<ScrollAreaViewport className="max-h-full overflow-y-auto overscroll-contain">
<ScrollAreaContent className="flex min-w-0 flex-col gap-y-5 px-6 py-3">
<ScrollArea className="row-start-2 min-h-0 overflow-hidden">
<ScrollAreaViewport className="overscroll-contain">
<ScrollAreaContent
className="flex flex-col gap-y-5 px-6 py-3"
style={{ minWidth: 0 }}
>
{/* name & icon */}
<div className="flex gap-4">
<Field name="title" className="grow">
+1 -1
View File
@@ -160,7 +160,7 @@ export function List({ onCreateLearnDify, onTryLearnDify }: Props) {
style={{ overflowX: 'hidden' }}
>
<ScrollAreaContent
className="flex min-h-full w-full max-w-full min-w-0 flex-col"
className="flex min-h-full w-full max-w-full flex-col"
style={{ minWidth: 0 }}
>
<AppListCatalog
@@ -10,14 +10,6 @@ import Pricing from '../index'
let mockConsoleState: Record<string, unknown> = {}
vi.mock('../header', () => ({
default: ({ onClose }: { onClose: () => void }) => (
<button type="button" onClick={onClose}>
close
</button>
),
}))
vi.mock('../plan-switcher', () => ({
default: () => <div>plan-switcher</div>,
}))
@@ -76,7 +68,7 @@ describe('Pricing dialog lifecycle', () => {
const { wrapper } = createConsoleQueryWrapper()
render(<Pricing onCancel={onCancel} />, { wrapper })
await user.click(screen.getByRole('button', { name: 'close' }))
await user.click(screen.getByRole('button', { name: 'common.operation.close' }))
expect(onCancel).toHaveBeenCalledTimes(1)
})
+1 -15
View File
@@ -1,16 +1,11 @@
import { cn } from '@langgenius/dify-ui/cn'
import { DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { DifyLogo } from '../../base/logo/dify-logo'
import styles from './header.module.css'
type HeaderProps = {
onClose: () => void
}
const Header = ({ onClose }: HeaderProps) => {
const Header = () => {
const { t } = useTranslation()
return (
@@ -32,15 +27,6 @@ const Header = ({ onClose }: HeaderProps) => {
<DialogDescription className="system-sm-regular text-text-tertiary">
{t(($) => $['plansCommon.title.description'], { ns: 'billing' })}
</DialogDescription>
<IconButton
variant="secondary"
size="xl"
className="absolute -right-4.5 bottom-[40.5px] z-10 rounded-full"
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
onClick={onClose}
>
<span aria-hidden="true" className="i-ri-close-line size-5" />
</IconButton>
</div>
</div>
)
+18 -3
View File
@@ -1,7 +1,8 @@
'use client'
import type { FC } from 'react'
import type { Category } from './types'
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
import { Dialog, DialogClose, DialogContent } from '@langgenius/dify-ui/dialog'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import {
ScrollArea,
ScrollAreaContent,
@@ -14,6 +15,7 @@ import { useQuery } from '@tanstack/react-query'
import { useAtomValue } from 'jotai'
import * as React from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useGetPricingPageLanguage } from '@/context/i18n'
import { useProviderContext } from '@/context/provider-context'
import { isCurrentWorkspaceManagerAtom } from '@/context/workspace-state'
@@ -31,6 +33,7 @@ type PricingProps = {
}
const Pricing: FC<PricingProps> = ({ onCancel }) => {
const { t } = useTranslation()
const { plan, enableEducationPlan } = useProviderContext()
const { data: isEducationAccount = false } = useQuery(
consoleQuery.account.education.get.queryOptions({
@@ -59,14 +62,26 @@ const Pricing: FC<PricingProps> = ({ onCancel }) => {
}}
>
<DialogContent className="inset-0 size-full max-h-none max-w-none translate-0 overflow-hidden rounded-none border-none bg-saas-background p-0 shadow-none">
<ScrollArea className="relative h-full w-full overflow-hidden">
<DialogClose
render={
<IconButton
variant="secondary"
size="xl"
className="absolute inset-e-5.5 top-6 z-10 rounded-full"
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
>
<span aria-hidden="true" className="i-ri-close-line size-5" />
</IconButton>
}
/>
<ScrollArea className="h-full w-full overflow-hidden">
<ScrollAreaViewport className="overscroll-contain">
<ScrollAreaContent className="min-h-full min-w-300">
<div className="relative grid min-h-full grid-rows-[1fr_auto_auto_1fr] overflow-hidden">
<div className="absolute inset-x-0 -top-12 -z-10">
<NoiseTop />
</div>
<Header onClose={onCancel} />
<Header />
<PlanSwitcher
currentCategory={currentCategory}
onChangeCategory={setCurrentCategory}
+1 -1
View File
@@ -551,7 +551,7 @@ function GotoAnythingDialog() {
<AutocompleteStatus className="sr-only">{autocompleteStatus}</AutocompleteStatus>
<ScrollArea className="relative h-88 min-h-0 overflow-hidden">
<ScrollArea className="h-88 min-h-0 overflow-hidden">
<ScrollAreaViewport
aria-busy={isLoading || undefined}
className="scroll-py-1 overscroll-contain"
@@ -40,7 +40,7 @@ type ModelSelectorScrollBodyProps = {
export function ModelSelectorScrollBody({ children, label }: ModelSelectorScrollBodyProps) {
return (
<ScrollArea className="relative min-h-0 overflow-hidden">
<ScrollArea className="min-h-0 overflow-hidden">
<ScrollAreaViewport
aria-label={label}
style={{ overflowX: 'hidden' }}
@@ -49,7 +49,7 @@ export function ModelSelectorScrollBody({ children, label }: ModelSelectorScroll
>
<ScrollAreaContent style={{ minWidth: 0 }}>{children}</ScrollAreaContent>
</ScrollAreaViewport>
<ScrollAreaScrollbar className="z-2">
<ScrollAreaScrollbar>
<ScrollAreaThumb />
</ScrollAreaScrollbar>
</ScrollArea>
@@ -184,7 +184,7 @@ const ProviderList = ({ category, contentInset = 'default', layout }: ProviderLi
const body = (
<>
<div className="relative flex h-0 shrink-0 grow flex-col overflow-hidden bg-components-panel-bg">
<ScrollArea className="relative min-h-0 grow overflow-hidden bg-components-panel-bg">
<ScrollArea className="min-h-0 grow overflow-hidden bg-components-panel-bg">
<ScrollAreaViewport
ref={containerRef}
aria-label={t(($) => $['menus.tools'], { ns: 'common' })}
@@ -240,7 +240,7 @@ const WebAppsSectionContent = () => {
</div>
</CollapsiblePanel>
{appsExpanded && (
<ScrollArea className="relative min-h-0 flex-1 overflow-hidden">
<ScrollArea className="min-h-0 flex-1 overflow-hidden">
<ScrollAreaViewport
ref={scrollRef}
aria-busy={installedAppsQuery.isFetchingNextPage}
@@ -186,7 +186,7 @@ export function AppPicker({
)}
</ComboboxInputGroup>
</div>
<ScrollArea className="relative min-h-0 flex-1 overflow-hidden">
<ScrollArea className="min-h-0 flex-1 overflow-hidden">
<ScrollAreaViewport
role="region"
aria-label={t(($) => $['appSelector.label'], { ns: 'app' })}
@@ -154,7 +154,7 @@ const Snippets = ({ searchText, onSearchTextChange, insertPayload, onInserted }:
) : !snippets.length ? (
<SnippetEmptyState />
) : (
<ScrollArea className="relative max-h-120 max-w-125 overflow-hidden">
<ScrollArea className="max-h-120 max-w-125 overflow-hidden">
<ScrollAreaViewport ref={viewportRef}>
<ScrollAreaContent className="p-1">
{snippets.map((item) => {
@@ -171,7 +171,7 @@ const TriggerPluginItem: FC<Props> = ({
</BlockSelectorRow>
{!notShowProvider && hasAction && !isFold && (
<ScrollArea className="relative max-h-60 overflow-hidden">
<ScrollArea className="max-h-60 overflow-hidden">
<ScrollAreaViewport
aria-label={t(($) => $['tabs.allTriggers'], { ns: 'workflow' })}
className="max-h-60 overscroll-contain"
@@ -9,14 +9,14 @@ export function AgentOrchestrateBottomActions({
shrinkOnOpen?: boolean
}) {
return (
<div className="pointer-events-none absolute inset-x-0 bottom-0 z-10 flex h-18 flex-col items-center justify-end px-4 pt-4 pb-2 transition-[height] duration-150 ease-out has-data-open:h-76.75 motion-reduce:transition-none">
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex h-18 flex-col items-center justify-end px-4 pt-4 pb-2 transition-[height] duration-150 ease-out has-data-open:h-76.75 motion-reduce:transition-none">
<div
aria-hidden
className="pointer-events-none absolute inset-0 z-0 bg-linear-to-t from-components-panel-bg to-components-panel-bg-transparent mask-[linear-gradient(to_top,black,transparent)] backdrop-blur-[2px] [-webkit-mask-image:linear-gradient(to_top,black,transparent)]"
className="pointer-events-none absolute inset-0 bg-linear-to-t from-components-panel-bg to-components-panel-bg-transparent mask-[linear-gradient(to_top,black,transparent)] backdrop-blur-[2px] [-webkit-mask-image:linear-gradient(to_top,black,transparent)]"
/>
<div
className={cn(
'relative z-10 flex w-full max-w-126.5 flex-col items-center justify-end transition-[max-width] duration-150 ease-out motion-reduce:transition-none',
'relative flex w-full max-w-126.5 flex-col items-center justify-end transition-[max-width] duration-150 ease-out motion-reduce:transition-none',
shrinkOnOpen && 'has-data-open:max-w-96',
)}
>
@@ -13,13 +13,7 @@ import {
FileTreeLabel,
FileTreeList,
} from '@langgenius/dify-ui/file-tree'
import {
ScrollArea,
ScrollAreaContent,
ScrollAreaScrollbar,
ScrollAreaThumb,
ScrollAreaViewport,
} from '@langgenius/dify-ui/scroll-area'
import { ScrollArea, ScrollAreaContent, ScrollAreaViewport } from '@langgenius/dify-ui/scroll-area'
import { Fragment } from 'react'
type AgentFileTreeFolderOpenStrategy = (context: { file: AgentFileNode; depth: number }) => boolean
@@ -205,9 +199,6 @@ export function AgentFileTree({
</FileTree>
</ScrollAreaContent>
</ScrollAreaViewport>
<ScrollAreaScrollbar className="hidden">
<ScrollAreaThumb />
</ScrollAreaScrollbar>
</ScrollArea>
</div>
)
@@ -142,57 +142,55 @@ export function AgentOrchestratePanel({
/>
)}
<AgentOrchestrateViewingVersionContext value={!!selectedVersionSnapshot}>
<AgentOrchestrateReadOnlyContext value={readOnly}>
<div aria-readonly={readOnly} className="flex min-h-0 flex-1 flex-col">
<ScrollArea className="min-h-0 flex-1 overflow-hidden">
<ScrollAreaViewport
aria-label={showHeader ? undefined : orchestrateLabel}
aria-labelledby={showHeader ? orchestrateHeadingId : undefined}
className="overscroll-contain"
role="region"
>
<ScrollAreaContent
className={cn('min-h-full px-4 py-3', hasBottomAction && 'pb-20')}
>
<AgentConfigApiContextProvider value={configApiContext}>
<AgentOrchestrateAddActionsProvider>
<AgentBuildDraftChangedKeysProvider
changedKeys={
isBuildDraftActive
? buildDraftChangedKeys
: EMPTY_BUILD_DRAFT_CHANGED_KEYS
}
>
<AgentModelField
currentModel={currentModel}
textGenerationModelList={textGenerationModelList}
onSelect={onSelectModel}
/>
<AgentPromptEditor />
<AgentSkills />
<AgentFiles />
<AgentTools />
{ENABLE_AGENT_KNOWLEDGE_RETRIEVAL && <AgentKnowledgeRetrieval />}
<AgentAdvancedSettings />
</AgentBuildDraftChangedKeysProvider>
</AgentOrchestrateAddActionsProvider>
</AgentConfigApiContextProvider>
</ScrollAreaContent>
</ScrollAreaViewport>
<ScrollAreaScrollbar className={hasBottomAction ? 'z-20' : undefined}>
<ScrollAreaThumb />
</ScrollAreaScrollbar>
</ScrollArea>
</div>
</AgentOrchestrateReadOnlyContext>
</AgentOrchestrateViewingVersionContext>
{orchestrateBottomAction ? (
<AgentOrchestrateBottomActions shrinkOnOpen={!bottomAction}>
{orchestrateBottomAction}
</AgentOrchestrateBottomActions>
) : null}
<ScrollArea
className={cn(
'min-h-0 flex-1 overflow-hidden',
showHeader ? 'rounded-b-[inherit]' : 'rounded-[inherit]',
)}
>
<AgentOrchestrateViewingVersionContext value={!!selectedVersionSnapshot}>
<AgentOrchestrateReadOnlyContext value={readOnly}>
<ScrollAreaViewport
aria-label={showHeader ? undefined : orchestrateLabel}
aria-labelledby={showHeader ? orchestrateHeadingId : undefined}
className="overscroll-contain"
role="region"
>
<ScrollAreaContent className={cn('min-h-full px-4 py-3', hasBottomAction && 'pb-20')}>
<AgentConfigApiContextProvider value={configApiContext}>
<AgentOrchestrateAddActionsProvider>
<AgentBuildDraftChangedKeysProvider
changedKeys={
isBuildDraftActive ? buildDraftChangedKeys : EMPTY_BUILD_DRAFT_CHANGED_KEYS
}
>
<AgentModelField
currentModel={currentModel}
textGenerationModelList={textGenerationModelList}
onSelect={onSelectModel}
/>
<AgentPromptEditor />
<AgentSkills />
<AgentFiles />
<AgentTools />
{ENABLE_AGENT_KNOWLEDGE_RETRIEVAL && <AgentKnowledgeRetrieval />}
<AgentAdvancedSettings />
</AgentBuildDraftChangedKeysProvider>
</AgentOrchestrateAddActionsProvider>
</AgentConfigApiContextProvider>
</ScrollAreaContent>
</ScrollAreaViewport>
</AgentOrchestrateReadOnlyContext>
</AgentOrchestrateViewingVersionContext>
{orchestrateBottomAction ? (
<AgentOrchestrateBottomActions shrinkOnOpen={!bottomAction}>
{orchestrateBottomAction}
</AgentOrchestrateBottomActions>
) : null}
<ScrollAreaScrollbar>
<ScrollAreaThumb />
</ScrollAreaScrollbar>
</ScrollArea>
</div>
)
}
@@ -425,10 +425,10 @@ export function AgentSkillDetailDialog({
/>
</div>
</div>
<ScrollArea className="relative min-h-0 flex-1 overflow-hidden has-[>_:first-child:focus-visible]:outline-2 has-[>_:first-child:focus-visible]:outline-offset-0 has-[>_:first-child:focus-visible]:outline-state-accent-solid">
<ScrollArea className="min-h-0 flex-1 overflow-hidden rounded-br-2xl">
<ScrollAreaViewport
aria-labelledby={dialogTitleId}
className="overscroll-contain outline-none focus-visible:outline-none"
className="overscroll-contain"
role="region"
>
<ScrollAreaContent
@@ -52,7 +52,7 @@ export function AgentLogsTable({
</table>
</div>
<ScrollArea className="relative min-h-0 flex-1 overflow-hidden">
<ScrollArea className="min-h-0 flex-1 overflow-hidden">
<ScrollAreaViewport
aria-label={t(($) => $['agentDetail.logs.title'])}
role="region"
+1 -1
View File
@@ -108,7 +108,7 @@ export default function RosterPage() {
</div>
<div className="min-h-0 flex-1">
<ScrollArea className="relative h-full min-h-0 min-w-0 overflow-hidden">
<ScrollArea className="h-full min-h-0 min-w-0 overflow-hidden">
<ScrollAreaViewport tabIndex={-1} className="overscroll-contain">
<ScrollAreaContent className="min-h-full px-8 pt-2 pb-8">
<AgentRosterList
+3 -2
View File
@@ -1361,14 +1361,15 @@ export function FileTree({
}}
/>
</div>
<ScrollArea className="relative min-h-0 flex-1 overflow-hidden">
<ScrollArea className="min-h-0 flex-1 overflow-hidden">
<ScrollAreaViewport tabIndex={-1}>
<ScrollAreaContent
className={cn(
'relative flex min-h-full min-w-0 flex-col rounded-lg px-1 pt-1 pb-3',
'relative flex min-h-full flex-col rounded-lg px-1 pt-1 pb-3',
dropTarget?.path === '' &&
'bg-components-dropzone-bg-accent before:pointer-events-none before:absolute before:inset-0.5 before:z-10 before:rounded-lg before:border-[1.5px] before:border-dashed before:border-components-dropzone-border-accent',
)}
style={{ minWidth: 0 }}
onDragLeave={handleRootDragLeave}
onDragOver={handleRootDragOver}
onDrop={handleRootDrop}
+1 -1
View File
@@ -904,7 +904,7 @@ export default function SkillsPage() {
</div>
<div className="min-h-0 flex-1">
<ScrollArea className="relative h-full min-h-0 min-w-0 overflow-hidden">
<ScrollArea className="h-full min-h-0 min-w-0 overflow-hidden">
<ScrollAreaViewport
ref={listViewportRef}
tabIndex={-1}