mirror of
https://github.com/langgenius/dify.git
synced 2026-08-29 03:45:08 +08:00
fix(ui): keep scroll area focus indicator visible (#41404)
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,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>
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
+2
-2
@@ -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"
|
||||
|
||||
+3
-3
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
+2
-2
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user