Enhance Dashboard Tabs with Sticky Behavior and Improved Layout
- Introduced a new DashboardTabItem component to manage tab interactions, including drag-and-drop functionality. - Added CSS styles for sticky tab behavior, ensuring better visibility and interaction during scrolling. - Updated existing styles to improve layout consistency and responsiveness across dashboard components. - Implemented metrics measurement for tab dimensions to enhance the user experience during tab interactions.
This commit is contained in:
parent
5498aa3368
commit
84db1f19b7
@ -3470,6 +3470,7 @@ body.objectKanbanColumnResizing * {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-tabs-wrap {
|
.dashboard-tabs-wrap {
|
||||||
@ -3505,6 +3506,7 @@ body.objectKanbanColumnResizing * {
|
|||||||
.dashboard-tab-item-container {
|
.dashboard-tab-item-container {
|
||||||
height: 42px;
|
height: 42px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-tab-item-container-active {
|
.dashboard-tab-item-container-active {
|
||||||
@ -3566,12 +3568,19 @@ body.objectKanbanColumnResizing * {
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard-tab-item-body {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard-tab-item-icon {
|
.dashboard-tab-item-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-tab-item:focus-visible {
|
.dashboard-tab-item:focus-visible {
|
||||||
@ -3729,6 +3738,66 @@ body.objectKanbanColumnResizing * {
|
|||||||
|
|
||||||
.dashboard-tabs-label {
|
.dashboard-tabs-label {
|
||||||
max-width: 280px;
|
max-width: 280px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tab-item-container[data-sticky-placeholder='true'] > .dashboard-tab-item {
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tab-sticky-clone {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
height: 42px;
|
||||||
|
z-index: 5;
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: none;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tab-sticky-clone[data-edge] {
|
||||||
|
visibility: visible;
|
||||||
|
width: var(--tab-sticky-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tab-sticky-clone[data-edge] .dashboard-tab-item {
|
||||||
|
pointer-events: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: var(--tab-sticky-width);
|
||||||
|
min-width: var(--tab-sticky-width);
|
||||||
|
max-width: var(--tab-sticky-width);
|
||||||
|
justify-content: flex-start;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tab-sticky-clone[data-edge] .dashboard-tab-item-body {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tab-sticky-clone[data-edge] .dashboard-tab-item-icon {
|
||||||
|
margin-right: calc(6px * var(--tab-label-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tab-sticky-clone[data-edge] .dashboard-tabs-label {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
opacity: var(--tab-label-opacity, 1);
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tab-sticky-clone[data-edge] .dashboard-tabs-label .ant-typography {
|
||||||
|
max-width: 100% !important;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tab-sticky-clone[data-faded='true'] .dashboard-tabs-label {
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-tabs-close {
|
.dashboard-tabs-close {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useLayoutEffect, useRef, useState } from 'react'
|
||||||
import { Button, Flex, Typography } from 'antd'
|
import { Button, Flex, Typography } from 'antd'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import ScrollBox from './ScrollBox'
|
import ScrollBox from './ScrollBox'
|
||||||
@ -64,6 +64,277 @@ DashboardTabLabel.propTypes = {
|
|||||||
onClose: PropTypes.func
|
onClose: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DashboardTabItem = ({
|
||||||
|
tab,
|
||||||
|
tabIndex,
|
||||||
|
isSelected,
|
||||||
|
isDragging,
|
||||||
|
dropPosition,
|
||||||
|
onDragStart,
|
||||||
|
onDragEnd,
|
||||||
|
onClick,
|
||||||
|
onKeyDown,
|
||||||
|
onDragOver,
|
||||||
|
onDrop,
|
||||||
|
onClose
|
||||||
|
}) => {
|
||||||
|
const model = tab.modelName ? getModelByName(tab.modelName) : null
|
||||||
|
const Icon = model?.icon || HomeIcon
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
role='tab'
|
||||||
|
aria-selected={isSelected}
|
||||||
|
tabIndex={isSelected ? 0 : -1}
|
||||||
|
className={classNames(
|
||||||
|
'dashboard-tab-item',
|
||||||
|
'electrobun-webkit-app-region-no-drag',
|
||||||
|
{
|
||||||
|
'dashboard-tab-item-active': isSelected,
|
||||||
|
'dashboard-tab-item-dragging': isDragging,
|
||||||
|
'dashboard-tab-item-drop-before': dropPosition === 'before',
|
||||||
|
'dashboard-tab-item-drop-after': dropPosition === 'after'
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
draggable
|
||||||
|
onDragStart={(event) => onDragStart(event, tab.id)}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
onClick={() => onClick(tab.id)}
|
||||||
|
onKeyDown={(event) => onKeyDown(event, tabIndex)}
|
||||||
|
onDragOver={(event) => onDragOver(event, tab)}
|
||||||
|
onDrop={(event) => onDrop(event, tab)}
|
||||||
|
>
|
||||||
|
<DashboardTabChrome />
|
||||||
|
<span className='dashboard-tab-item-body'>
|
||||||
|
<span className='dashboard-tab-item-icon'>
|
||||||
|
<Icon style={{ fontSize: 14 }} color='secondary' />
|
||||||
|
</span>
|
||||||
|
<DashboardTabLabel tab={tab} onClose={onClose} />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DashboardTabItem.propTypes = {
|
||||||
|
tab: PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
title: PropTypes.string,
|
||||||
|
modelName: PropTypes.string
|
||||||
|
}).isRequired,
|
||||||
|
tabIndex: PropTypes.number.isRequired,
|
||||||
|
isSelected: PropTypes.bool,
|
||||||
|
isDragging: PropTypes.bool,
|
||||||
|
dropPosition: PropTypes.oneOf(['before', 'after']),
|
||||||
|
onDragStart: PropTypes.func.isRequired,
|
||||||
|
onDragEnd: PropTypes.func.isRequired,
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
onKeyDown: PropTypes.func.isRequired,
|
||||||
|
onDragOver: PropTypes.func.isRequired,
|
||||||
|
onDrop: PropTypes.func.isRequired,
|
||||||
|
onClose: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
const STICKY_EDGE_INSET = 20
|
||||||
|
|
||||||
|
const measureTabMetrics = (container) => {
|
||||||
|
const button = container.querySelector('.dashboard-tab-item')
|
||||||
|
const icon = container.querySelector('.dashboard-tab-item-icon')
|
||||||
|
if (!button || !icon) {
|
||||||
|
return {
|
||||||
|
naturalWidth: container.offsetWidth,
|
||||||
|
minWidth: container.offsetWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = window.getComputedStyle(button)
|
||||||
|
const minWidth = Math.ceil(
|
||||||
|
icon.getBoundingClientRect().width +
|
||||||
|
parseFloat(styles.paddingLeft) +
|
||||||
|
parseFloat(styles.paddingRight) +
|
||||||
|
parseFloat(styles.borderLeftWidth) +
|
||||||
|
parseFloat(styles.borderRightWidth)
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
naturalWidth: container.offsetWidth,
|
||||||
|
minWidth: Math.max(minWidth, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideStickyClone = (cloneEl) => {
|
||||||
|
if (!cloneEl) return
|
||||||
|
delete cloneEl.dataset.edge
|
||||||
|
delete cloneEl.dataset.faded
|
||||||
|
cloneEl.style.removeProperty('--tab-sticky-width')
|
||||||
|
cloneEl.style.removeProperty('--tab-label-opacity')
|
||||||
|
cloneEl.style.left = ''
|
||||||
|
cloneEl.style.right = ''
|
||||||
|
cloneEl.setAttribute('inert', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const setOriginalStickyState = (container, sticky) => {
|
||||||
|
if (!container) return
|
||||||
|
if (sticky) {
|
||||||
|
container.dataset.stickyPlaceholder = 'true'
|
||||||
|
container.setAttribute('inert', '')
|
||||||
|
} else {
|
||||||
|
delete container.dataset.stickyPlaceholder
|
||||||
|
container.removeAttribute('inert')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bindActiveTabStickyScroll = (scrollEl, list, cloneEl) => {
|
||||||
|
const state = {
|
||||||
|
activeEl: null,
|
||||||
|
updating: false
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
if (state.updating) return
|
||||||
|
state.updating = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const container = list.querySelector(
|
||||||
|
'.dashboard-tab-item-container-active'
|
||||||
|
)
|
||||||
|
if (state.activeEl && state.activeEl !== container) {
|
||||||
|
setOriginalStickyState(state.activeEl, false)
|
||||||
|
}
|
||||||
|
state.activeEl = container
|
||||||
|
|
||||||
|
if (!container || !cloneEl) {
|
||||||
|
hideStickyClone(cloneEl)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const metrics = measureTabMetrics(container)
|
||||||
|
const naturalWidth = metrics.naturalWidth
|
||||||
|
const minWidth = Math.min(metrics.minWidth, naturalWidth)
|
||||||
|
const maxShrink = Math.max(0, naturalWidth - minWidth)
|
||||||
|
const scrollLeft = scrollEl.scrollLeft
|
||||||
|
const viewportWidth = scrollEl.clientWidth
|
||||||
|
if (viewportWidth <= 0) {
|
||||||
|
setOriginalStickyState(container, false)
|
||||||
|
hideStickyClone(cloneEl)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollRect = scrollEl.getBoundingClientRect()
|
||||||
|
const containerRect = container.getBoundingClientRect()
|
||||||
|
const naturalLeft = scrollLeft + (containerRect.left - scrollRect.left)
|
||||||
|
const overflowLeft = scrollLeft + STICKY_EDGE_INSET - naturalLeft
|
||||||
|
const overflowRight =
|
||||||
|
naturalLeft +
|
||||||
|
naturalWidth -
|
||||||
|
(scrollLeft + viewportWidth - STICKY_EDGE_INSET)
|
||||||
|
|
||||||
|
let edge = null
|
||||||
|
let overflow = 0
|
||||||
|
if (overflowLeft > 0.5 && overflowLeft >= overflowRight) {
|
||||||
|
edge = 'left'
|
||||||
|
overflow = overflowLeft
|
||||||
|
} else if (overflowRight > 0.5) {
|
||||||
|
edge = 'right'
|
||||||
|
overflow = overflowRight
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!edge) {
|
||||||
|
setOriginalStickyState(container, false)
|
||||||
|
hideStickyClone(cloneEl)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const shrink = Math.min(overflow, maxShrink)
|
||||||
|
const visualWidth = Math.max(minWidth, naturalWidth - shrink)
|
||||||
|
const fade = maxShrink === 0 ? 1 : 1 - shrink / maxShrink
|
||||||
|
|
||||||
|
setOriginalStickyState(container, true)
|
||||||
|
cloneEl.removeAttribute('inert')
|
||||||
|
cloneEl.dataset.edge = edge
|
||||||
|
if (fade < 0.25) cloneEl.dataset.faded = 'true'
|
||||||
|
else delete cloneEl.dataset.faded
|
||||||
|
|
||||||
|
const parentRect =
|
||||||
|
cloneEl.offsetParent?.getBoundingClientRect() || scrollRect
|
||||||
|
if (edge === 'left') {
|
||||||
|
cloneEl.style.left = `${scrollRect.left - parentRect.left + STICKY_EDGE_INSET}px`
|
||||||
|
cloneEl.style.right = 'auto'
|
||||||
|
} else {
|
||||||
|
cloneEl.style.left = 'auto'
|
||||||
|
cloneEl.style.right = `${parentRect.right - scrollRect.right + STICKY_EDGE_INSET}px`
|
||||||
|
}
|
||||||
|
cloneEl.style.setProperty('--tab-sticky-width', `${visualWidth}px`)
|
||||||
|
cloneEl.style.setProperty('--tab-label-opacity', String(fade))
|
||||||
|
} finally {
|
||||||
|
state.updating = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let frame = 0
|
||||||
|
const scheduleUpdate = () => {
|
||||||
|
if (frame) return
|
||||||
|
frame = window.requestAnimationFrame(() => {
|
||||||
|
frame = 0
|
||||||
|
update()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
update()
|
||||||
|
scrollEl.addEventListener('scroll', scheduleUpdate, { passive: true })
|
||||||
|
window.addEventListener('resize', scheduleUpdate)
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(scheduleUpdate)
|
||||||
|
observer.observe(scrollEl)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
scrollEl.removeEventListener('scroll', scheduleUpdate)
|
||||||
|
window.removeEventListener('resize', scheduleUpdate)
|
||||||
|
observer.disconnect()
|
||||||
|
if (frame) window.cancelAnimationFrame(frame)
|
||||||
|
setOriginalStickyState(state.activeEl, false)
|
||||||
|
hideStickyClone(cloneEl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const useActiveTabStickyScroll = (activeTabId, tabs) => {
|
||||||
|
const rootRef = useRef(null)
|
||||||
|
const listRef = useRef(null)
|
||||||
|
const cloneRef = useRef(null)
|
||||||
|
const tabLayoutKey = `${activeTabId}:${tabs.map((tab) => `${tab.id}:${tab.title || ''}`).join('|')}`
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const root = rootRef.current
|
||||||
|
const list = listRef.current
|
||||||
|
const cloneEl = cloneRef.current
|
||||||
|
if (!root || !list) return undefined
|
||||||
|
|
||||||
|
let cancelled = false
|
||||||
|
let teardown = () => {}
|
||||||
|
let frame = 0
|
||||||
|
|
||||||
|
const tryBind = () => {
|
||||||
|
if (cancelled) return
|
||||||
|
const scrollEl = root.querySelector('.simplebar-content-wrapper')
|
||||||
|
if (!scrollEl) {
|
||||||
|
frame = window.requestAnimationFrame(tryBind)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
teardown = bindActiveTabStickyScroll(scrollEl, list, cloneEl)
|
||||||
|
}
|
||||||
|
|
||||||
|
tryBind()
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
if (frame) window.cancelAnimationFrame(frame)
|
||||||
|
teardown()
|
||||||
|
}
|
||||||
|
}, [tabLayoutKey])
|
||||||
|
|
||||||
|
return { rootRef, listRef, cloneRef }
|
||||||
|
}
|
||||||
|
|
||||||
const DashboardTabs = () => {
|
const DashboardTabs = () => {
|
||||||
const {
|
const {
|
||||||
tabs,
|
tabs,
|
||||||
@ -76,6 +347,10 @@ const DashboardTabs = () => {
|
|||||||
handleTabDragEnd,
|
handleTabDragEnd,
|
||||||
handleExternalTabDrop
|
handleExternalTabDrop
|
||||||
} = useNavigationTabs()
|
} = useNavigationTabs()
|
||||||
|
const { rootRef, listRef, cloneRef } = useActiveTabStickyScroll(
|
||||||
|
activeTabId,
|
||||||
|
tabs
|
||||||
|
)
|
||||||
const [draggedValue, setDraggedValue] = useState(null)
|
const [draggedValue, setDraggedValue] = useState(null)
|
||||||
const [dropTarget, setDropTarget] = useState(null)
|
const [dropTarget, setDropTarget] = useState(null)
|
||||||
|
|
||||||
@ -232,9 +507,24 @@ const DashboardTabs = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isReordering = draggedValue != null || dropTarget != null
|
const isReordering = draggedValue != null || dropTarget != null
|
||||||
|
const activeTab = tabs.find((tab) => tab.id === activeTabId)
|
||||||
|
const activeTabIndex = activeTab
|
||||||
|
? tabs.findIndex((tab) => tab.id === activeTabId)
|
||||||
|
: 0
|
||||||
|
|
||||||
|
const tabItemProps = {
|
||||||
|
onDragStart: handleItemDragStart,
|
||||||
|
onDragEnd: handleItemDragEnd,
|
||||||
|
onClick: selectTab,
|
||||||
|
onKeyDown: handleKeyDown,
|
||||||
|
onDragOver: handleItemDragOver,
|
||||||
|
onDrop: handleItemDrop,
|
||||||
|
onClose: closeTab
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
|
ref={rootRef}
|
||||||
align='flex-start'
|
align='flex-start'
|
||||||
gap={4}
|
gap={4}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@ -248,6 +538,7 @@ const DashboardTabs = () => {
|
|||||||
<ScrollBox className='dashboard-tabs-wrap' horizontal>
|
<ScrollBox className='dashboard-tabs-wrap' horizontal>
|
||||||
<Flex align='flex-start' gap={8} className='dashboard-tabs-inner'>
|
<Flex align='flex-start' gap={8} className='dashboard-tabs-inner'>
|
||||||
<div
|
<div
|
||||||
|
ref={listRef}
|
||||||
className={classNames('dashboard-tabs-list', {
|
className={classNames('dashboard-tabs-list', {
|
||||||
'dashboard-tabs-reordering': isReordering
|
'dashboard-tabs-reordering': isReordering
|
||||||
})}
|
})}
|
||||||
@ -256,8 +547,6 @@ const DashboardTabs = () => {
|
|||||||
onDrop={handleListDrop}
|
onDrop={handleListDrop}
|
||||||
>
|
>
|
||||||
{tabs.map((tab, index) => {
|
{tabs.map((tab, index) => {
|
||||||
const model = tab.modelName ? getModelByName(tab.modelName) : null
|
|
||||||
const Icon = model?.icon || HomeIcon
|
|
||||||
const isSelected = tab.id === activeTabId
|
const isSelected = tab.id === activeTabId
|
||||||
const isDragging =
|
const isDragging =
|
||||||
draggedValue != null && String(draggedValue) === String(tab.id)
|
draggedValue != null && String(draggedValue) === String(tab.id)
|
||||||
@ -273,37 +562,20 @@ const DashboardTabs = () => {
|
|||||||
'dashboard-tab-item-container-active': isSelected
|
'dashboard-tab-item-container-active': isSelected
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<button
|
<DashboardTabItem
|
||||||
type='button'
|
tab={tab}
|
||||||
role='tab'
|
tabIndex={index}
|
||||||
aria-selected={isSelected}
|
isSelected={isSelected}
|
||||||
tabIndex={isSelected ? 0 : -1}
|
isDragging={isDragging}
|
||||||
className={classNames(
|
dropPosition={
|
||||||
'dashboard-tab-item',
|
isDropTarget
|
||||||
'electrobun-webkit-app-region-no-drag',
|
? dropTarget.insertBefore
|
||||||
{
|
? 'before'
|
||||||
'dashboard-tab-item-active': isSelected,
|
: 'after'
|
||||||
'dashboard-tab-item-dragging': isDragging,
|
: undefined
|
||||||
'dashboard-tab-item-drop-before':
|
}
|
||||||
isDropTarget && dropTarget.insertBefore,
|
{...tabItemProps}
|
||||||
'dashboard-tab-item-drop-after':
|
/>
|
||||||
isDropTarget && !dropTarget.insertBefore
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
draggable
|
|
||||||
onDragStart={(event) => handleItemDragStart(event, tab.id)}
|
|
||||||
onDragEnd={handleItemDragEnd}
|
|
||||||
onClick={() => selectTab(tab.id)}
|
|
||||||
onKeyDown={(event) => handleKeyDown(event, index)}
|
|
||||||
onDragOver={(event) => handleItemDragOver(event, tab)}
|
|
||||||
onDrop={(event) => handleItemDrop(event, tab)}
|
|
||||||
>
|
|
||||||
<DashboardTabChrome />
|
|
||||||
<span className='dashboard-tab-item-icon'>
|
|
||||||
<Icon style={{ fontSize: 14 }} color='secondary' />
|
|
||||||
</span>
|
|
||||||
<DashboardTabLabel tab={tab} onClose={closeTab} />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@ -319,6 +591,23 @@ const DashboardTabs = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
</ScrollBox>
|
</ScrollBox>
|
||||||
|
{activeTab ? (
|
||||||
|
<div
|
||||||
|
ref={cloneRef}
|
||||||
|
className='dashboard-tab-sticky-clone dashboard-tab-item-container dashboard-tab-item-container-active'
|
||||||
|
>
|
||||||
|
<DashboardTabItem
|
||||||
|
tab={activeTab}
|
||||||
|
tabIndex={activeTabIndex}
|
||||||
|
isSelected
|
||||||
|
isDragging={
|
||||||
|
draggedValue != null &&
|
||||||
|
String(draggedValue) === String(activeTab.id)
|
||||||
|
}
|
||||||
|
{...tabItemProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user