import { theme, cursor, sizePx } from '@planview/pv-utilities' import styled, { css } from 'styled-components' import { dashedBorder } from '../utils/dashed-border' import { BORDERS } from '../constants' export const GridBody = styled.tbody` display: block; position: relative; width: 100%; contain: layout style; ` const RowGrabberStyles = css` content: ''; ${cursor.grab}; position: absolute; inset: 0; right: auto; width: 10px; z-index: 8; /* Sticky cells have a z-index of 7 */ background-image: url('data:image/svg+xml;utf-8,'); background-size: 16px 16px; background-repeat: no-repeat; background-position: -4px center; ` export const GridRowLayout = styled.tr<{ $excluded: boolean $selected: boolean $draggable: boolean $dragging: boolean $hovering: boolean $selectable: boolean $hasBorder: boolean $loading: boolean $noBorderOnLast: boolean $focusVisible: boolean $spreadsheetMode: boolean }>` --grid-row-background-color: ${theme.backgroundNeutral0}; --grid-row-border-color: ${theme.borderLight}; --grid-row-border-width: ${(props) => (props.$hasBorder ? '1px' : '0')}; ${(props) => props.$hasBorder ? css` &:last-child { ${props.$noBorderOnLast ? '--grid-row-border-color: transparent' : null} } border-bottom: solid 1px var(--grid-row-border-color); ` : '--grid-row-border-color: transparent'}; display: flex; flex-direction: row; position: absolute; left: 0; top: 0; width: 100%; min-width: 100%; ${(props) => props.$selectable ? cursor.pointer : props.$draggable && !props.$spreadsheetMode ? cursor.grab : ''}; ${(props) => props.$loading && css` pointer-events: none; `} background-color: var(--grid-row-background-color); &:focus-visible { outline: none; } &::before { content: ''; position: absolute; z-index: 10; inset: 0 0 ${(props) => (props.$hasBorder ? '-1px' : '0')} 0; border: solid 1px transparent; display: none; pointer-events: none; } ${(props) => props.$selected ? css` --grid-row-background-color: ${theme.backgroundNeutral0Active}; --grid-row-border-color: ${theme.borderActive}; &::before { border-color: var(--grid-row-border-color); display: block; } &[aria-selected] + &[aria-selected]::before { border-top-color: transparent; } ` : null} ${(props) => props.$hovering ? css` --grid-row-background-color: ${theme.backgroundNeutral0Hover}; ` : null} ${(props) => props.$hovering && props.$selected ? css` --grid-row-background-color: ${theme.backgroundNeutral0ActiveHover}; --grid-row-border-color: ${theme.borderActive}; ` : null} ${(props) => props.$excluded || props.$dragging ? css` & > * { opacity: 0.2; } ` : null} ${(props) => props.$spreadsheetMode && css` user-select: none; `} ${(props) => props.$draggable && css` user-select: none; &:hover::after { ${RowGrabberStyles} } &:active::after { ${cursor.grabbing}; } ${!props.$spreadsheetMode && css` &:active { ${cursor.grabbing}; } `} `} ${(props) => props.$focusVisible && !props.$dragging ? css` &::after { ${RowGrabberStyles}; outline: none; } &::before { --grid-row-border-color: ${theme.borderFocus}; border-color: var(--grid-row-border-color); border-width: 2px; display: block; } ` : null} ` const copyPasteStyles = css<{ $editable?: boolean }>` [data-pvds-grid-range-copied='true'] &::before { transition: none; background-color: ${theme.backgroundTranslucentActive}; } ${(props) => props.$editable && css` [data-pvds-grid-range-pasted='true'] &::before { transition: none; background-image: linear-gradient( to right, rgb(from ${theme.success500} r g b / 15%), rgb(from ${theme.success500} r g b / 15%) ); background-color: rgb(from ${theme.success500} r g b / 15%); border-color: ${theme.success500}; } `} [data-pvds-grid-range-pasted='error'] &::before { transition: none; background-image: linear-gradient( to right, rgb(from ${theme.backgroundError} r g b / 15%), rgb(from ${theme.backgroundError} r g b / 15%) ); background-color: rgb(from ${theme.backgroundError} r g b / 15%); border-color: ${theme.borderError}; } ` export const GridCellLayout = styled.td<{ $sticky?: boolean $inRange?: boolean $inRangeBorders?: number $editable?: boolean }>` height: 100%; display: block; position: relative; outline: none; isolation: isolate; background-color: var(--grid-row-background-color); box-sizing: content-box; ${(props) => props.$sticky && css` position: sticky; top: 0; z-index: 7; border-bottom: solid var(--grid-row-border-width) var(--grid-row-border-color); `} ${(props) => props.$inRange && css` &::before { content: ''; position: absolute; pointer-events: none; z-index: 100; background-color: transparent; background-image: linear-gradient( to right, ${theme.backgroundTranslucentActive}, ${theme.backgroundTranslucentActive} ); inset: 0 0 -1px; ${((props.$inRangeBorders ?? 0) & BORDERS.TOP) !== 0 ? `border-top: 1px solid ${theme.borderActive}` : null}; ${((props.$inRangeBorders ?? 0) & BORDERS.RIGHT) !== 0 ? `border-right: 1px solid ${theme.borderActive}` : null}; ${((props.$inRangeBorders ?? 0) & BORDERS.BOTTOM) !== 0 ? `border-bottom: 1px solid ${theme.borderActive}` : null}; ${((props.$inRangeBorders ?? 0) & BORDERS.LEFT) !== 0 ? `border-left: 1px solid ${theme.borderActive}` : null}; transition: background-color 0.2s ease-in-out; } ${copyPasteStyles} `} ` export const GridCellFocusLayout = styled.div<{ $editable?: boolean $editing?: boolean $focusVisible?: boolean $leftBorder?: boolean $rightBorder?: boolean }>` height: 100%; width: 100%; display: block; contain: size layout; ${(props) => props.$leftBorder ? `border-left: 1px solid ${theme.borderLight}` : null}; ${(props) => props.$rightBorder ? `border-right: 1px solid ${theme.borderLight}` : null}; ${(props) => props.$editable && !props.$editing && css` cursor: text; &:hover::after { content: ''; position: absolute; pointer-events: none; z-index: 100; inset: 0; background-clip: border-box; ${dashedBorder(theme.gray900)}; } `} ${(props) => props.$focusVisible && css` &::after, &:hover::after { content: ''; position: absolute; pointer-events: none; z-index: 100; background-clip: border-box; ${props.$editable && !props.$editing ? css` ${dashedBorder(theme.borderFocus, { width: 2 })}; ` : css` border: solid 2px ${theme.borderFocus}; `} inset: 0; } `}; ` export const GridEmptyContainer = styled.div` position: absolute; inset: ${sizePx.small} 0 0 0; ` export const LiveRegion = styled.div` clip: rect(0 0 0 0); clip-path: inset(50%); height: 1px; overflow: hidden; position: absolute; white-space: nowrap; width: 1px; top: 0; left: 0; `