mirror of
https://github.com/langgenius/dify.git
synced 2026-03-19 21:57:33 +08:00
refactor: simplify the scroll area API for sidebar layouts (#33761)
This commit is contained in:
@ -4,6 +4,7 @@ import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaCorner,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
@ -19,7 +20,7 @@ const renderScrollArea = (options: {
|
||||
horizontalThumbClassName?: string
|
||||
} = {}) => {
|
||||
return render(
|
||||
<ScrollArea className={options.rootClassName ?? 'h-40 w-40'} data-testid="scroll-area-root">
|
||||
<ScrollAreaRoot className={options.rootClassName ?? 'h-40 w-40'} data-testid="scroll-area-root">
|
||||
<ScrollAreaViewport data-testid="scroll-area-viewport" className={options.viewportClassName}>
|
||||
<ScrollAreaContent data-testid="scroll-area-content">
|
||||
<div className="h-48 w-48">Scrollable content</div>
|
||||
@ -43,7 +44,7 @@ const renderScrollArea = (options: {
|
||||
className={options.horizontalThumbClassName}
|
||||
/>
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>,
|
||||
</ScrollAreaRoot>,
|
||||
)
|
||||
}
|
||||
|
||||
@ -62,6 +63,38 @@ describe('scroll-area wrapper', () => {
|
||||
expect(screen.getByTestId('scroll-area-horizontal-thumb')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should render the convenience wrapper and apply slot props', async () => {
|
||||
render(
|
||||
<>
|
||||
<p id="installed-apps-label">Installed apps</p>
|
||||
<ScrollArea
|
||||
className="h-40 w-40"
|
||||
slotClassNames={{
|
||||
content: 'custom-content-class',
|
||||
scrollbar: 'custom-scrollbar-class',
|
||||
viewport: 'custom-viewport-class',
|
||||
}}
|
||||
labelledBy="installed-apps-label"
|
||||
data-testid="scroll-area-wrapper-root"
|
||||
>
|
||||
<div className="h-48 w-20">Scrollable content</div>
|
||||
</ScrollArea>
|
||||
</>,
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
const root = screen.getByTestId('scroll-area-wrapper-root')
|
||||
const viewport = screen.getByRole('region', { name: 'Installed apps' })
|
||||
const content = screen.getByText('Scrollable content').parentElement
|
||||
|
||||
expect(root).toBeInTheDocument()
|
||||
expect(viewport).toHaveClass('custom-viewport-class')
|
||||
expect(viewport).toHaveAccessibleName('Installed apps')
|
||||
expect(content).toHaveClass('custom-content-class')
|
||||
expect(screen.getByText('Scrollable content')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Scrollbar', () => {
|
||||
@ -219,7 +252,7 @@ describe('scroll-area wrapper', () => {
|
||||
|
||||
try {
|
||||
render(
|
||||
<ScrollArea className="h-40 w-40" data-testid="scroll-area-root">
|
||||
<ScrollAreaRoot className="h-40 w-40" data-testid="scroll-area-root">
|
||||
<ScrollAreaViewport data-testid="scroll-area-viewport">
|
||||
<ScrollAreaContent data-testid="scroll-area-content">
|
||||
<div className="h-48 w-48">Scrollable content</div>
|
||||
@ -236,7 +269,7 @@ describe('scroll-area wrapper', () => {
|
||||
<ScrollAreaThumb data-testid="scroll-area-horizontal-thumb" />
|
||||
</ScrollAreaScrollbar>
|
||||
<ScrollAreaCorner data-testid="scroll-area-corner" />
|
||||
</ScrollArea>,
|
||||
</ScrollAreaRoot>,
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@ -4,9 +4,9 @@ import * as React from 'react'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaCorner,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
@ -14,7 +14,7 @@ import {
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Layout/ScrollArea',
|
||||
component: ScrollArea,
|
||||
component: ScrollAreaRoot,
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
docs: {
|
||||
@ -24,7 +24,7 @@ const meta = {
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof ScrollArea>
|
||||
} satisfies Meta<typeof ScrollAreaRoot>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
@ -135,7 +135,7 @@ const StoryCard = ({
|
||||
|
||||
const VerticalPanelPane = () => (
|
||||
<div className={cn(panelClassName, 'h-[360px]')}>
|
||||
<ScrollArea className={insetScrollAreaClassName}>
|
||||
<ScrollAreaRoot className={insetScrollAreaClassName}>
|
||||
<ScrollAreaViewport className={insetViewportClassName}>
|
||||
<ScrollAreaContent className="space-y-3 p-4 pr-6">
|
||||
<div className="space-y-1">
|
||||
@ -161,13 +161,13 @@ const VerticalPanelPane = () => (
|
||||
<ScrollAreaScrollbar className={insetScrollbarClassName}>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</ScrollAreaRoot>
|
||||
</div>
|
||||
)
|
||||
|
||||
const StickyListPane = () => (
|
||||
<div className={cn(panelClassName, 'h-[360px]')}>
|
||||
<ScrollArea className={insetScrollAreaClassName}>
|
||||
<ScrollAreaRoot className={insetScrollAreaClassName}>
|
||||
<ScrollAreaViewport className={cn(insetViewportClassName, '[mask-image:linear-gradient(to_bottom,transparent_0px,black_10px,black_calc(100%-14px),transparent_100%)]')}>
|
||||
<ScrollAreaContent className="min-h-full">
|
||||
<div className="sticky top-0 z-10 border-b border-divider-subtle bg-components-panel-bg px-4 pb-3 pt-4">
|
||||
@ -200,7 +200,7 @@ const StickyListPane = () => (
|
||||
<ScrollAreaScrollbar className={insetScrollbarClassName}>
|
||||
<ScrollAreaThumb className="rounded-full" />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</ScrollAreaRoot>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -216,7 +216,7 @@ const WorkbenchPane = ({
|
||||
className?: string
|
||||
}) => (
|
||||
<div className={cn(panelClassName, 'min-h-0', className)}>
|
||||
<ScrollArea className={insetScrollAreaClassName}>
|
||||
<ScrollAreaRoot className={insetScrollAreaClassName}>
|
||||
<ScrollAreaViewport className={insetViewportClassName}>
|
||||
<ScrollAreaContent className="space-y-3 p-4 pr-6">
|
||||
<div className="space-y-1">
|
||||
@ -229,13 +229,13 @@ const WorkbenchPane = ({
|
||||
<ScrollAreaScrollbar className={insetScrollbarClassName}>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</ScrollAreaRoot>
|
||||
</div>
|
||||
)
|
||||
|
||||
const HorizontalRailPane = () => (
|
||||
<div className={cn(panelClassName, 'h-[272px] min-w-0 max-w-full')}>
|
||||
<ScrollArea className={insetScrollAreaClassName}>
|
||||
<ScrollAreaRoot className={insetScrollAreaClassName}>
|
||||
<ScrollAreaViewport className={insetViewportClassName}>
|
||||
<ScrollAreaContent className="min-h-full min-w-max space-y-4 p-4 pb-6">
|
||||
<div className="space-y-1">
|
||||
@ -262,7 +262,7 @@ const HorizontalRailPane = () => (
|
||||
<ScrollAreaScrollbar orientation="horizontal" className={insetScrollbarClassName}>
|
||||
<ScrollAreaThumb className="rounded-full" />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</ScrollAreaRoot>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -319,7 +319,7 @@ const ScrollbarStatePane = ({
|
||||
<p className="text-text-secondary system-sm-regular">{description}</p>
|
||||
</div>
|
||||
<div className="mt-4 min-w-0 rounded-[24px] border border-divider-subtle bg-components-panel-bg p-3">
|
||||
<ScrollArea className="h-[320px] p-1">
|
||||
<ScrollAreaRoot className="h-[320px] p-1">
|
||||
<ScrollAreaViewport id={viewportId} className="rounded-[20px] bg-components-panel-bg">
|
||||
<ScrollAreaContent className="min-w-0 space-y-2 p-4 pr-6">
|
||||
{scrollbarShowcaseRows.map(item => (
|
||||
@ -333,7 +333,7 @@ const ScrollbarStatePane = ({
|
||||
<ScrollAreaScrollbar className={insetScrollbarClassName}>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</ScrollAreaRoot>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -347,7 +347,7 @@ const HorizontalScrollbarShowcasePane = () => (
|
||||
<p className="text-text-secondary system-sm-regular">Current design delivery defines the horizontal scrollbar body, but not a horizontal edge hint.</p>
|
||||
</div>
|
||||
<div className="mt-4 min-w-0 rounded-[24px] border border-divider-subtle bg-components-panel-bg p-3">
|
||||
<ScrollArea className="h-[240px] p-1">
|
||||
<ScrollAreaRoot className="h-[240px] p-1">
|
||||
<ScrollAreaViewport className="rounded-[20px] bg-components-panel-bg">
|
||||
<ScrollAreaContent className="min-h-full min-w-max space-y-4 p-4 pb-6">
|
||||
<div className="space-y-1">
|
||||
@ -367,7 +367,7 @@ const HorizontalScrollbarShowcasePane = () => (
|
||||
<ScrollAreaScrollbar orientation="horizontal" className={insetScrollbarClassName}>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</ScrollAreaRoot>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -375,7 +375,7 @@ const HorizontalScrollbarShowcasePane = () => (
|
||||
const OverlayPane = () => (
|
||||
<div className="flex h-[420px] min-w-0 items-center justify-center rounded-[28px] bg-[radial-gradient(circle_at_top,_rgba(21,90,239,0.12),_transparent_45%),linear-gradient(180deg,rgba(16,24,40,0.03),transparent)] p-6">
|
||||
<div className={cn(blurPanelClassName, 'w-full max-w-[360px]')}>
|
||||
<ScrollArea className="h-[320px] p-1">
|
||||
<ScrollAreaRoot className="h-[320px] p-1">
|
||||
<ScrollAreaViewport className="overscroll-contain rounded-[20px] bg-components-panel-bg-blur">
|
||||
<ScrollAreaContent className="space-y-2 p-3 pr-6">
|
||||
<div className="sticky top-0 z-10 rounded-xl border border-divider-subtle bg-components-panel-bg-blur px-3 py-3 backdrop-blur-[6px]">
|
||||
@ -400,14 +400,14 @@ const OverlayPane = () => (
|
||||
<ScrollAreaScrollbar className={insetScrollbarClassName}>
|
||||
<ScrollAreaThumb className="rounded-full bg-state-base-handle hover:bg-state-base-handle-hover" />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</ScrollAreaRoot>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const CornerPane = () => (
|
||||
<div className={cn(panelClassName, 'h-[320px] w-full max-w-[440px]')}>
|
||||
<ScrollArea className={cn(insetScrollAreaClassName, 'overflow-hidden')}>
|
||||
<ScrollAreaRoot className={cn(insetScrollAreaClassName, 'overflow-hidden')}>
|
||||
<ScrollAreaViewport className={cn(insetViewportClassName, 'bg-[linear-gradient(180deg,var(--color-components-panel-bg),var(--color-components-panel-bg-alt))]')}>
|
||||
<ScrollAreaContent className="min-h-[420px] min-w-[620px] space-y-4 p-4">
|
||||
<div className="flex items-start justify-between gap-6">
|
||||
@ -443,7 +443,7 @@ const CornerPane = () => (
|
||||
<ScrollAreaThumb className="rounded-full" />
|
||||
</ScrollAreaScrollbar>
|
||||
<ScrollAreaCorner className="bg-[linear-gradient(180deg,var(--color-components-panel-bg),var(--color-components-panel-bg-alt))]" />
|
||||
</ScrollArea>
|
||||
</ScrollAreaRoot>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -475,7 +475,7 @@ const ExploreSidebarWebAppsPane = () => {
|
||||
</div>
|
||||
|
||||
<div className="h-[304px]">
|
||||
<ScrollArea className={sidebarScrollAreaClassName}>
|
||||
<ScrollAreaRoot className={sidebarScrollAreaClassName}>
|
||||
<ScrollAreaViewport className={sidebarViewportClassName}>
|
||||
<ScrollAreaContent className={sidebarContentClassName}>
|
||||
{webAppsRows.map((item, index) => (
|
||||
@ -519,7 +519,7 @@ const ExploreSidebarWebAppsPane = () => {
|
||||
<ScrollAreaScrollbar className={sidebarScrollbarClassName}>
|
||||
<ScrollAreaThumb className="rounded-full" />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</ScrollAreaRoot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -654,7 +654,7 @@ export const PrimitiveComposition: Story = {
|
||||
description="A stripped-down example for teams that want to start from the base API and add their own shell classes around it. The outer shell adds inset padding so the tracks sit inside the rounded surface instead of colliding with the panel corners."
|
||||
>
|
||||
<div className={cn(panelClassName, 'h-[260px] max-w-[420px]')}>
|
||||
<ScrollArea className={insetScrollAreaClassName}>
|
||||
<ScrollAreaRoot className={insetScrollAreaClassName}>
|
||||
<ScrollAreaViewport className={insetViewportClassName}>
|
||||
<ScrollAreaContent className="min-w-[560px] space-y-3 p-4 pr-6">
|
||||
{Array.from({ length: 8 }, (_, index) => (
|
||||
@ -673,7 +673,7 @@ export const PrimitiveComposition: Story = {
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
<ScrollAreaCorner />
|
||||
</ScrollArea>
|
||||
</ScrollAreaRoot>
|
||||
</div>
|
||||
</StoryCard>
|
||||
),
|
||||
|
||||
@ -5,12 +5,26 @@ import * as React from 'react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export const ScrollArea = BaseScrollArea.Root
|
||||
export const ScrollAreaRoot = BaseScrollArea.Root
|
||||
export type ScrollAreaRootProps = React.ComponentPropsWithRef<typeof BaseScrollArea.Root>
|
||||
|
||||
export const ScrollAreaContent = BaseScrollArea.Content
|
||||
export type ScrollAreaContentProps = React.ComponentPropsWithRef<typeof BaseScrollArea.Content>
|
||||
|
||||
export type ScrollAreaSlotClassNames = {
|
||||
viewport?: string
|
||||
content?: string
|
||||
scrollbar?: string
|
||||
}
|
||||
|
||||
export type ScrollAreaProps = Omit<ScrollAreaRootProps, 'children'> & {
|
||||
children: React.ReactNode
|
||||
orientation?: 'vertical' | 'horizontal'
|
||||
slotClassNames?: ScrollAreaSlotClassNames
|
||||
label?: string
|
||||
labelledBy?: string
|
||||
}
|
||||
|
||||
export const scrollAreaScrollbarClassName = cn(
|
||||
styles.scrollbar,
|
||||
'flex touch-none select-none overflow-clip p-1 opacity-100 transition-opacity motion-reduce:transition-none',
|
||||
@ -88,3 +102,31 @@ export function ScrollAreaCorner({
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function ScrollArea({
|
||||
children,
|
||||
className,
|
||||
orientation = 'vertical',
|
||||
slotClassNames,
|
||||
label,
|
||||
labelledBy,
|
||||
...props
|
||||
}: ScrollAreaProps) {
|
||||
return (
|
||||
<ScrollAreaRoot className={className} {...props}>
|
||||
<ScrollAreaViewport
|
||||
aria-label={label}
|
||||
aria-labelledby={labelledBy}
|
||||
className={slotClassNames?.viewport}
|
||||
role={label || labelledBy ? 'region' : undefined}
|
||||
>
|
||||
<ScrollAreaContent className={slotClassNames?.content}>
|
||||
{children}
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar orientation={orientation} className={slotClassNames?.scrollbar}>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
)
|
||||
}
|
||||
|
||||
@ -93,6 +93,13 @@ describe('SideBar', () => {
|
||||
expect(screen.getByText('explore.sidebar.title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should expose an accessible name for the discovery link when the text is hidden', () => {
|
||||
mockMediaType = MediaType.mobile
|
||||
renderSideBar()
|
||||
|
||||
expect(screen.getByRole('link', { name: 'explore.sidebar.title' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render workspace items when installed apps exist', () => {
|
||||
mockInstalledApps = [createInstalledApp()]
|
||||
renderSideBar()
|
||||
@ -136,6 +143,15 @@ describe('SideBar', () => {
|
||||
const dividers = container.querySelectorAll('[class*="divider"], hr')
|
||||
expect(dividers.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should render a button for toggling the sidebar and update its accessible name', () => {
|
||||
renderSideBar()
|
||||
|
||||
const toggleButton = screen.getByRole('button', { name: 'layout.sidebar.collapseSidebar' })
|
||||
fireEvent.click(toggleButton)
|
||||
|
||||
expect(screen.getByRole('button', { name: 'layout.sidebar.expandSidebar' })).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('User Interactions', () => {
|
||||
|
||||
@ -13,13 +13,7 @@ import {
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@/app/components/base/ui/alert-dialog'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@/app/components/base/ui/scroll-area'
|
||||
import { ScrollArea } from '@/app/components/base/ui/scroll-area'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import Link from '@/next/link'
|
||||
@ -30,11 +24,9 @@ import Item from './app-nav-item'
|
||||
import NoApps from './no-apps'
|
||||
|
||||
const expandedSidebarScrollAreaClassNames = {
|
||||
root: 'h-full',
|
||||
viewport: 'overscroll-contain',
|
||||
content: 'space-y-0.5',
|
||||
scrollbar: 'data-[orientation=vertical]:my-2 data-[orientation=vertical]:[margin-inline-end:-0.75rem]',
|
||||
thumb: 'rounded-full',
|
||||
viewport: 'overscroll-contain',
|
||||
} as const
|
||||
|
||||
const SideBar = () => {
|
||||
@ -104,10 +96,11 @@ const SideBar = () => {
|
||||
<div className={cn(isDiscoverySelected ? 'text-text-accent' : 'text-text-tertiary')}>
|
||||
<Link
|
||||
href="/explore/apps"
|
||||
aria-label={isMobile || isFold ? t('sidebar.title', { ns: 'explore' }) : undefined}
|
||||
className={cn(isDiscoverySelected ? 'bg-state-base-active' : 'hover:bg-state-base-hover', 'flex h-8 items-center gap-2 rounded-lg px-1 mobile:w-fit mobile:justify-center pc:w-full pc:justify-start')}
|
||||
>
|
||||
<div className="flex size-6 shrink-0 items-center justify-center rounded-md bg-components-icon-bg-blue-solid">
|
||||
<span className="i-ri-apps-fill size-3.5 text-components-avatar-shape-fill-stop-100" />
|
||||
<span aria-hidden="true" className="i-ri-apps-fill size-3.5 text-components-avatar-shape-fill-stop-100" />
|
||||
</div>
|
||||
{!isMobile && !isFold && <div className={cn('truncate', isDiscoverySelected ? 'text-components-menu-item-text-active system-sm-semibold' : 'text-components-menu-item-text system-sm-regular')}>{t('sidebar.title', { ns: 'explore' })}</div>}
|
||||
</Link>
|
||||
@ -126,19 +119,12 @@ const SideBar = () => {
|
||||
{shouldUseExpandedScrollArea
|
||||
? (
|
||||
<div className="min-h-0 flex-1">
|
||||
<ScrollArea className={expandedSidebarScrollAreaClassNames.root}>
|
||||
<ScrollAreaViewport
|
||||
aria-labelledby={webAppsLabelId}
|
||||
className={expandedSidebarScrollAreaClassNames.viewport}
|
||||
role="region"
|
||||
>
|
||||
<ScrollAreaContent className={expandedSidebarScrollAreaClassNames.content}>
|
||||
{installedAppItems}
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar className={expandedSidebarScrollAreaClassNames.scrollbar}>
|
||||
<ScrollAreaThumb className={expandedSidebarScrollAreaClassNames.thumb} />
|
||||
</ScrollAreaScrollbar>
|
||||
<ScrollArea
|
||||
className="h-full"
|
||||
slotClassNames={expandedSidebarScrollAreaClassNames}
|
||||
labelledBy={webAppsLabelId}
|
||||
>
|
||||
{installedAppItems}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)
|
||||
@ -154,13 +140,18 @@ const SideBar = () => {
|
||||
|
||||
{!isMobile && (
|
||||
<div className="mt-auto flex pb-3 pt-3">
|
||||
<div className="flex size-8 cursor-pointer items-center justify-center text-text-tertiary" onClick={toggleIsFold}>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={isFold ? t('sidebar.expandSidebar', { ns: 'layout' }) : t('sidebar.collapseSidebar', { ns: 'layout' })}
|
||||
className="flex size-8 items-center justify-center rounded-lg text-text-tertiary transition-colors hover:bg-state-base-hover focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-components-input-border-hover"
|
||||
onClick={toggleIsFold}
|
||||
>
|
||||
{isFold
|
||||
? <span className="i-ri-expand-right-line" />
|
||||
? <span aria-hidden="true" className="i-ri-expand-right-line" />
|
||||
: (
|
||||
<span className="i-ri-layout-left-2-line" />
|
||||
<span aria-hidden="true" className="i-ri-layout-left-2-line" />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user