Skip to content

Commit

Permalink
fix: prevent layout issues and 'zero-sized element' errors in react-v…
Browse files Browse the repository at this point in the history
…irtuoso

- Remove grid gaps from virtualized containers as react-virtuoso doesn't support this and can result
in odd flickering / scroll issues.
- Also ensure that table headers on smaller breakpoints are never set to `display: none`, which can
yield 'zero-sized element' errors.
  • Loading branch information
robinpyon committed May 4, 2022
1 parent 501e452 commit c0233c9
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 144 deletions.
2 changes: 0 additions & 2 deletions src/components/AssetGridVirtualized/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ const ItemContainer = styled.div`

const ListContainer = styled.div`
display: grid;
grid-column-gap: 5px;
grid-template-columns: repeat(auto-fill, ${CARD_WIDTH}px);
grid-row-gap: 5px;
justify-content: center;
margin: 0 auto;
`
Expand Down
258 changes: 131 additions & 127 deletions src/components/CardAsset/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,17 @@ type Props = {
selected: boolean
}

const CardWrapper = styled(Flex)`
height: 100%;
overflow: hidden;
padding: 2px;
position: relative;
width: 100%;
`

const CardContainer = styled(Flex)<{picked?: boolean; updating?: boolean}>`
/* background: ${hues.gray[950].hex}; */
border: 1px solid transparent;
height: 100%;
overflow: hidden;
pointer-events: ${props => (props.updating ? 'none' : 'auto')};
position: relative;
transition: all 300ms;
Expand Down Expand Up @@ -100,16 +106,14 @@ const CardAsset = (props: Props) => {
value: asset._id
}
])
} else {
if (shiftPressed.current) {
if (picked) {
dispatch(assetsActions.pick({assetId: asset._id, picked: !picked}))
} else {
dispatch(assetsActions.pickRange({startId: lastPicked || asset._id, endId: asset._id}))
}
} else if (shiftPressed.current) {
if (picked) {
dispatch(assetsActions.pick({assetId: asset._id, picked: !picked}))
} else {
dispatch(dialogActions.showAssetEdit({assetId: asset._id}))
dispatch(assetsActions.pickRange({startId: lastPicked || asset._id, endId: asset._id}))
}
} else {
dispatch(dialogActions.showAssetEdit({assetId: asset._id}))
}
}

Expand All @@ -118,144 +122,144 @@ const CardAsset = (props: Props) => {

if (onSelect) {
dispatch(dialogActions.showAssetEdit({assetId: asset._id}))
} else if (shiftPressed.current && !picked) {
dispatch(assetsActions.pickRange({startId: lastPicked || asset._id, endId: asset._id}))
} else {
if (shiftPressed.current && !picked) {
dispatch(assetsActions.pickRange({startId: lastPicked || asset._id, endId: asset._id}))
} else {
dispatch(assetsActions.pick({assetId: asset._id, picked: !picked}))
}
dispatch(assetsActions.pick({assetId: asset._id, picked: !picked}))
}
}

const opacityContainer = updating ? 0.5 : 1
const opacityPreview = selected || updating ? 0.25 : 1

return (
<CardContainer direction="column" picked={picked} updating={item.updating}>
{/* Image */}
<Box
flex={1}
style={{
cursor: selected ? 'default' : 'pointer',
position: 'relative'
}}
>
<div onClick={handleAssetClick} style={{height: '100%', opacity: opacityPreview}}>
{/* File icon */}
{isFileAsset(asset) && <FileIcon extension={asset.extension} width="80px" />}
<CardWrapper>
<CardContainer direction="column" picked={picked} updating={item.updating}>
{/* Image */}
<Box
flex={1}
style={{
cursor: selected ? 'default' : 'pointer',
position: 'relative'
}}
>
<div onClick={handleAssetClick} style={{height: '100%', opacity: opacityPreview}}>
{/* File icon */}
{isFileAsset(asset) && <FileIcon extension={asset.extension} width="80px" />}

{/* Image */}
{isImageAsset(asset) && (
<Image
draggable={false}
showCheckerboard={!isOpaque}
src={imageDprUrl(asset, {height: 250, width: 250})}
style={{
draggable: false,
transition: 'opacity 1000ms'
}}
/>
)}
</div>

{/* Selected check icon */}
{selected && !updating && (
<Flex
align="center"
justify="center"
style={{
height: '100%',
left: 0,
opacity: opacityContainer,
position: 'absolute',
top: 0,
width: '100%'
}}
>
<Text size={2}>
<CheckmarkCircleIcon />
</Text>
</Flex>
)}

{/* Spinner */}
{updating && (
<Flex
align="center"
justify="center"
style={{
height: '100%',
left: 0,
position: 'absolute',
top: 0,
width: '100%'
}}
>
<Spinner />
</Flex>
)}
</Box>

{/* Image */}
{isImageAsset(asset) && (
<Image
draggable={false}
showCheckerboard={!isOpaque}
src={imageDprUrl(asset, {height: 250, width: 250})}
{/* Footer */}
<ContextActionContainer
align="center"
onClick={handleContextActionClick}
paddingX={1}
style={{opacity: opacityContainer}}
>
{onSelect ? (
<EditIcon
style={{
flexShrink: 0,
opacity: 0.5
}}
/>
) : (
<Checkbox
checked={picked}
readOnly
style={{
draggable: false,
transition: 'opacity 1000ms'
flexShrink: 0,
pointerEvents: 'none',
transform: 'scale(0.8)'
}}
/>
)}
</div>

{/* Selected check icon */}
{selected && !updating && (
<Flex
align="center"
justify="center"
style={{
height: '100%',
left: 0,
opacity: opacityContainer,
position: 'absolute',
top: 0,
width: '100%'
}}
>
<Text size={2}>
<CheckmarkCircleIcon />
<Box marginLeft={2}>
<Text muted size={0} textOverflow="ellipsis">
{asset.originalFilename}
</Text>
</Flex>
)}
</Box>
</ContextActionContainer>

{/* Spinner */}
{updating && (
<Flex
align="center"
justify="center"
{/* TODO: DRY */}
{/* Error button */}
{error && (
<Box
padding={3}
style={{
height: '100%',
left: 0,
position: 'absolute',
top: 0,
width: '100%'
right: 0,
top: 0
}}
>
<Spinner />
</Flex>
)}
</Box>

{/* Footer */}
<ContextActionContainer
align="center"
onClick={handleContextActionClick}
paddingX={1}
style={{opacity: opacityContainer}}
>
{onSelect ? (
<EditIcon
style={{
flexShrink: 0,
opacity: 0.5
}}
/>
) : (
<Checkbox
checked={picked}
readOnly
style={{
flexShrink: 0,
pointerEvents: 'none',
transform: 'scale(0.8)'
}}
/>
<Tooltip
content={
<Container padding={2} width={0}>
<Text size={1}>{error}</Text>
</Container>
}
placement="left"
portal
>
<Text size={1}>
<StyledWarningOutlineIcon color="critical" />
</Text>
</Tooltip>
</Box>
)}

<Box marginLeft={2}>
<Text muted size={0} textOverflow="ellipsis">
{asset.originalFilename}
</Text>
</Box>
</ContextActionContainer>

{/* TODO: DRY */}
{/* Error button */}
{error && (
<Box
padding={3}
style={{
position: 'absolute',
right: 0,
top: 0
}}
>
<Tooltip
content={
<Container padding={2} width={0}>
<Text size={1}>{error}</Text>
</Container>
}
placement="left"
portal
>
<Text size={1}>
<StyledWarningOutlineIcon color="critical" />
</Text>
</Tooltip>
</Box>
)}
</CardContainer>
</CardContainer>
</CardWrapper>
)
}

Expand Down
8 changes: 6 additions & 2 deletions src/components/TableHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,24 @@ const TableHeader: FC = () => {
}
}

// Note that even though we hide the table header on smaller breakpoints, we never set it to
// `display: none`, as doing so causes issues with react-virtuoso.
// Instead, we give it 0 height and hide it with `visibility: hidden`.
return (
<LegacyBox
sx={{
alignItems: 'center',
bg: black.hex,
borderBottom: `1px solid ${hues.gray?.[900].hex}`,
display: ['none', null, null, 'grid'],
display: 'grid',
gridColumnGap: [0, null, null, 3],
gridTemplateColumns: 'tableLarge',
height: `${PANEL_HEIGHT}px`,
height: [0, null, null, `${PANEL_HEIGHT}px`],
letterSpacing: '0.025em',
position: 'sticky',
textTransform: 'uppercase',
top: 0,
visibility: ['hidden', null, null, 'visible'],
width: '100%',
zIndex: 1 // force stacking context
}}
Expand Down
22 changes: 9 additions & 13 deletions src/components/TableRowAsset/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,10 @@ const TableRowAsset = (props: Props) => {

if (onSelect) {
dispatch(dialogActions.showAssetEdit({assetId: asset._id}))
} else if (shiftPressed.current && !picked) {
dispatch(assetsActions.pickRange({startId: lastPicked || asset._id, endId: asset._id}))
} else {
if (shiftPressed.current && !picked) {
dispatch(assetsActions.pickRange({startId: lastPicked || asset._id, endId: asset._id}))
} else {
dispatch(assetsActions.pick({assetId: asset._id, picked: !picked}))
}
dispatch(assetsActions.pick({assetId: asset._id, picked: !picked}))
}
}

Expand All @@ -107,16 +105,14 @@ const TableRowAsset = (props: Props) => {
value: asset._id
}
])
} else {
if (shiftPressed.current) {
if (picked) {
dispatch(assetsActions.pick({assetId: asset._id, picked: !picked}))
} else {
dispatch(assetsActions.pickRange({startId: lastPicked || asset._id, endId: asset._id}))
}
} else if (shiftPressed.current) {
if (picked) {
dispatch(assetsActions.pick({assetId: asset._id, picked: !picked}))
} else {
dispatch(dialogActions.showAssetEdit({assetId: asset._id}))
dispatch(assetsActions.pickRange({startId: lastPicked || asset._id, endId: asset._id}))
}
} else {
dispatch(dialogActions.showAssetEdit({assetId: asset._id}))
}
}

Expand Down

0 comments on commit c0233c9

Please sign in to comment.