Last active
September 8, 2023 10:21
-
-
Save guillermodlpa/87262b0f2f1c8161edad38bdc497ed8f to your computer and use it in GitHub Desktop.
React hook to render a responsive CSS grid centering the elements in the last row
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import useBreakpointValueWithDeviceType from '@/hooks/useBreakpointValueWithUADetection/useBreakpointValueWithDeviceType'; | |
import { BoxProps } from '@chakra-ui/react'; | |
import { useMemo } from 'react'; | |
// Controlling Leftover Grid Items with Pseudo-selectors | |
// @see https://css-irl.info/controlling-leftover-grid-items/ | |
function generateCenteredGridStyles(columnCount: number): BoxProps['sx'] { | |
const itemStyles: Record<string, string | Record<string, string>> = { | |
gridColumn: 'span 2', | |
}; | |
if (columnCount >= 2) { | |
// Dealing with {columnCount} orphan items | |
for (let i = 1; i < columnCount; i++) { | |
itemStyles[ | |
`&:nth-last-of-type(${i}):nth-of-type(${columnCount}n - ${i})` | |
] = { | |
gridColumnEnd: `-${2 * i}`, | |
}; | |
} | |
// Dealing with {columnCount - n} orphan items | |
if (columnCount > 2) { | |
for (let i = 1; i < columnCount; i++) { | |
for (let j = 1; j < columnCount - i; j++) { | |
itemStyles[ | |
`&:nth-last-of-type(${j}):nth-of-type(${columnCount}n - ${i + j})` | |
] = { | |
gridColumnEnd: `-${2 * j + i}`, | |
}; | |
} | |
} | |
} | |
} | |
return itemStyles; | |
} | |
const generateCenteredGridStylesMemo = memoize(generateCenteredGridStyles); | |
// double columns, we span each cell double, so we can position them half-way for the last row | |
// we do this hook to handle responsive number of columns | |
export default function useResponsiveGridWithLastRowCentering( | |
countCols: | |
| number | |
| { base: number; sm?: number; md?: number; lg?: number; xl?: number }, | |
) { | |
const countColsResponsive = | |
typeof countCols === 'number' ? { base: countCols } : countCols; | |
const { gridTemplateColumns, itemStyleBreakpointValues } = useMemo(() => { | |
const gridTemplateColumns = { | |
base: `repeat(${countColsResponsive.base * 2}, 1fr)`, | |
...(countColsResponsive.sm && { | |
sm: `repeat(${countColsResponsive.sm * 2}, 1fr)`, | |
}), | |
...(countColsResponsive.md && { | |
md: `repeat(${countColsResponsive.md * 2}, 1fr)`, | |
}), | |
...(countColsResponsive.lg && { | |
lg: `repeat(${countColsResponsive.lg * 2}, 1fr)`, | |
}), | |
...(countColsResponsive.xl && { | |
xl: `repeat(${countColsResponsive.xl * 2}, 1fr)`, | |
}), | |
}; | |
const itemStyleBreakpointValues = { | |
base: | |
// don't center when it's 2 columns. This is a hardcoded exception in the hook | |
countColsResponsive.base <= 2 | |
? generateCenteredGridStylesMemo(1) | |
: generateCenteredGridStylesMemo(countColsResponsive.base), | |
...(countColsResponsive.sm && { | |
sm: generateCenteredGridStylesMemo(countColsResponsive.sm), | |
}), | |
...(countColsResponsive.md && { | |
md: generateCenteredGridStylesMemo(countColsResponsive.md), | |
}), | |
...(countColsResponsive.lg && { | |
lg: generateCenteredGridStylesMemo(countColsResponsive.lg), | |
}), | |
...(countColsResponsive.xl && { | |
xl: generateCenteredGridStylesMemo(countColsResponsive.xl), | |
}), | |
}; | |
return { gridTemplateColumns, itemStyleBreakpointValues }; | |
}, [ | |
countColsResponsive.base, | |
countColsResponsive.sm, | |
countColsResponsive.md, | |
countColsResponsive.lg, | |
countColsResponsive.xl, | |
]); | |
const itemStyles = useBreakpointValueWithDeviceType( | |
itemStyleBreakpointValues, | |
); | |
return { gridTemplateColumns, itemStyles }; | |
} | |
// These are examples of the resulting sx descriptors. I had these before I made the generic function | |
// const itemStylesFourColumns: BoxProps['sx'] = { | |
// gridColumn: 'span 2', | |
// // Dealing with 3 orphan items | |
// '&:nth-last-of-type(1):nth-of-type(4n - 1)': { | |
// gridColumnEnd: '-2', | |
// }, | |
// '&:nth-last-of-type(2):nth-of-type(4n - 2)': { | |
// gridColumnEnd: '-4', | |
// }, | |
// '&:nth-last-of-type(3):nth-of-type(4n - 3)': { | |
// gridColumnEnd: '-6', | |
// }, | |
// // Dealing with 2 orphan items | |
// '&:nth-last-of-type(1):nth-of-type(4n - 2)': { | |
// gridColumnEnd: '-3', | |
// }, | |
// '&:nth-last-of-type(2):nth-of-type(4n - 3)': { | |
// gridColumnEnd: '-5', | |
// }, | |
// // Dealing with single orphan item | |
// '&:nth-last-of-type(1):nth-of-type(4n - 3)': { | |
// gridColumnEnd: '-4', | |
// }, | |
// }; | |
// const itemStylesThreeColumns: BoxProps['sx'] = { | |
// gridColumn: 'span 2', | |
// // Dealing with 2 orphan items | |
// '&:nth-last-of-type(1):nth-of-type(3n - 1)': { | |
// gridColumnEnd: '-2', | |
// }, | |
// '&:nth-last-of-type(2):nth-of-type(3n - 2)': { | |
// gridColumnEnd: '-4', | |
// }, | |
// // Dealing with single orphan item | |
// '&:nth-last-of-type(1):nth-of-type(3n - 2)': { | |
// gridColumnEnd: '-3', | |
// }, | |
// }; | |
// const itemStylesTwoColumns: BoxProps['sx'] = { | |
// gridColumn: 'span 2', | |
// // Dealing with single orphan item | |
// '&:nth-last-of-type(1):nth-of-type(2n - 1)': { | |
// gridColumnEnd: '-2', | |
// }, | |
// }; | |
// const itemStylesWheNotCentered: BoxProps['sx'] = { | |
// gridColumn: 'span 2', | |
// }; | |
// const itemStylesByNumberOfColumns: Record<number, undefined | BoxProps['sx']> = | |
// { | |
// 2: itemStylesTwoColumns, | |
// 3: itemStylesThreeColumns, | |
// 4: itemStylesFourColumns, | |
// }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment