feat(web): Use a better grid system

This commit is contained in:
Wanjohi
2025-07-22 17:14:10 +03:00
parent 7ebf0644f1
commit 7156980063
2 changed files with 154 additions and 146 deletions

View File

@@ -1,5 +1,24 @@
--- ---
import { Stack, Grid } from "../solidjs/index.ts"; import { Stack, Grid } from "../solidjs/index.ts";
// Define which areas are occupied by content at different breakpoints
const occupiedAreas = {
xs: [
{ startRow: 2, endRow: 6, startCol: 1, endCol: 9 }, // Hero content spans rows 2-6, cols 1-9
],
sm: [
{ startRow: 2, endRow: 5, startCol: 2, endCol: 8 }, // Hero content spans rows 2-5, cols 2-8
],
smd: [
{ startRow: 2, endRow: 4, startCol: 2, endCol: 12 }, // Hero content spans rows 2-4, cols 2-12
],
md: [
{ startRow: 2, endRow: 4, startCol: 2, endCol: 12 }, // Same as smd
],
lg: [
{ startRow: 2, endRow: 6, startCol: 2, endCol: 12 }, // Same as smd
],
};
--- ---
<Stack <Stack
@@ -10,30 +29,13 @@ import { Stack, Grid } from "../solidjs/index.ts";
> >
<div class="flex justify-center items-center flex-col"> <div class="flex justify-center items-center flex-col">
<Grid.Root <Grid.Root
style={{ "border-bottom": "none" }} xsGridRows={6}
smGridRows={5}
mdGridRows={6}
xsGridColumns={8}
smGridColumns={8} smGridColumns={8}
mdGridColumns={10} mdGridColumns={12}
lgGridColumns={12} occupiedAreas={occupiedAreas}
>
<Grid.Cross crossRow={1} crossColumn={0} />
</Grid.Root>
<Grid.Root
class="before:!border-t-0"
style={{ "border-bottom": "none" }}
smGridColumns={1}
mdGridColumns={1}
lgGridColumns={3}
heroHeading={true}
/> />
<Grid.Root
style={{ "border-bottom": "none" }}
class="before:!border-t-0"
smGridColumns={8}
gridRows={5}
mdGridColumns={10}
lgGridColumns={12}
>
<Grid.Cross crossRow={-1} crossColumn={-1} />
</Grid.Root>
</div> </div>
</Stack> </Stack>

View File

@@ -1,46 +1,68 @@
import { splitProps, For } from "solid-js"; import { type ParentProps, splitProps, For, type JSX } from "solid-js";
import type { ParentProps, JSX } from 'solid-js';
import styles from "./index.module.css"; import styles from "./index.module.css";
export interface GridCell {
startRow: number;
endRow: number;
startCol: number;
endCol: number;
}
export interface GridOccupiedAreas {
xs?: GridCell[];
sm?: GridCell[];
smd?: GridCell[];
md?: GridCell[];
lg?: GridCell[];
}
export interface GridRootProps { export interface GridRootProps {
gridRows?: number; xsGridRows?: number;
smGridRows?: number;
mdGridRows?: number;
xsGridColumns?: number;
smGridColumns?: number; smGridColumns?: number;
mdGridColumns?: number; mdGridColumns?: number;
lgGridColumns?: number;
smHeight?: string; smHeight?: string;
heroHeading?: boolean; occupiedAreas?: GridOccupiedAreas;
class?: string; class?: string;
style?: JSX.CSSProperties; style?: JSX.CSSProperties;
} }
const defaultProps: GridRootProps = { const defaultProps: GridRootProps = {
gridRows: 1, xsGridRows: 6,
lgGridColumns: 1, smGridRows: 5,
mdGridColumns: 1, mdGridRows: 4,
heroHeading: false, xsGridColumns: 8,
smGridColumns: 8,
mdGridColumns: 12,
smHeight: "calc(var(--width) / var(--grid-cols) * var(--grid-rows))", smHeight: "calc(var(--width) / var(--grid-cols) * var(--grid-rows))",
smGridColumns: 1, occupiedAreas: {},
} }
export function Root(props: ParentProps<GridRootProps>) { export function Root(props: ParentProps<GridRootProps>) {
const [local, others] = splitProps({ ...defaultProps, ...props }, [ const [local, others] = splitProps({ ...defaultProps, ...props }, [
"gridRows", "xsGridRows",
"smGridRows",
"mdGridRows",
"xsGridColumns",
"smGridColumns", "smGridColumns",
"mdGridColumns", "mdGridColumns",
"lgGridColumns",
"smHeight", "smHeight",
"heroHeading", "occupiedAreas",
"class", "class",
"style", "style",
"children" "children"
]); ]);
const styleVars: Record<string, string> = { const styleVars: Record<string, string> = {
...(local.smHeight && { "--sm-height": local.smHeight }), ...(local.xsGridRows && { "--xs-grid-rows": local.xsGridRows.toString() }),
...(local.smGridRows && { "--sm-grid-rows": local.smGridRows.toString() }),
...(local.mdGridRows && { "--md-grid-rows": local.mdGridRows.toString() }),
...(local.xsGridColumns && { "--xs-grid-cols": local.xsGridColumns.toString() }),
...(local.smGridColumns && { "--sm-grid-cols": local.smGridColumns.toString() }), ...(local.smGridColumns && { "--sm-grid-cols": local.smGridColumns.toString() }),
...(local.mdGridColumns && { "--md-grid-cols": local.mdGridColumns.toString() }), ...(local.mdGridColumns && { "--md-grid-cols": local.mdGridColumns.toString() }),
...(local.lgGridColumns && { "--lg-grid-cols": local.lgGridColumns.toString() }), ...(local.smHeight && { "--sm-height": local.smHeight }),
...(local.gridRows && { "--grid-rows": local.gridRows.toString() }),
} }
const styleStr = Object.entries({ ...styleVars, ...local.style }) const styleStr = Object.entries({ ...styleVars, ...local.style })
@@ -48,128 +70,112 @@ export function Root(props: ParentProps<GridRootProps>) {
.map(([k, v]) => `${k}: ${v}`) .map(([k, v]) => `${k}: ${v}`)
.join("; "); .join("; ");
// Helper function to check if a cell is occupied
const isCellOccupied = (breakpoint: keyof GridOccupiedAreas, col: number, row: number): boolean => {
const areas = local.occupiedAreas?.[breakpoint] || [];
return areas.some(area =>
col >= area.startCol && col < area.endCol &&
row >= area.startRow && row < area.endRow
);
};
// Helper function to determine border styles for a cell
const getCellBorderStyle = (
breakpoint: keyof GridOccupiedAreas,
col: number,
row: number,
maxCols: number,
maxRows: number
) => {
const isOccupied = isCellOccupied(breakpoint, col, row);
const isLastCol = col === maxCols;
const isLastRow = row === maxRows;
// Check if the cell to the right is also occupied (to remove internal borders)
const rightCellOccupied = !isLastCol && isCellOccupied(breakpoint, col + 1, row);
// Check if the cell below is also occupied (to remove internal borders)
const bottomCellOccupied = !isLastRow && isCellOccupied(breakpoint, col, row + 1);
return {
// Remove right border only if both current and right cell are occupied
...(isOccupied && rightCellOccupied && { "border-right": "none" }),
// Remove bottom border only if both current and bottom cell are occupied
...(isOccupied && bottomCellOccupied && { "border-bottom": "none" }),
};
};
// Render grid guides for a specific breakpoint
const renderGridGuides = (
breakpoint: keyof GridOccupiedAreas,
className: string,
cols: number,
rows: number
) => (
<div class={styles.grid_gridGuides} aria-hidden="true" data-grid-guides="true">
<For each={Array.from({ length: rows })}>
{(_, rowIndex) => (
<For each={Array.from({ length: cols })}>
{(_, colIndex) => {
const row = rowIndex() + 1;
const col = colIndex() + 1;
const borderStyle = getCellBorderStyle(breakpoint, col, row, cols, rows);
return ( return (
<div <div
class={`${styles.grid_system} ${styles.grid_grid} ${local.heroHeading ? styles.grid_headingGrid : ""} ${local.class || ""}`} aria-hidden="true"
class={`${styles.grid_gridGuide} ${className}`}
style={{
"--x": col,
"--y": row,
...borderStyle
}}
/>
);
}}
</For>
)}
</For>
</div>
);
return (
<section
class={`${styles.grid_system} ${styles.grid_grid} ${local.class || ""}`}
data-grid=""
style={styleStr} style={styleStr}
{...others} {...others}
> >
{local.children} {local.children}
<div class={styles.grid_gridGuides}>
<For each={Array.from({ length: local.gridRows || 1 })}> {/* XS Grid Guides */}
{(_, rowIndex) => ( {renderGridGuides("xs", styles.grid_xsGuide, local.xsGridColumns || 8, local.xsGridRows || 6)}
<For each={Array.from({ length: local.smGridColumns || 0 })}>
{(_, colIndex) => ( {/* SM Grid Guides */}
<div {renderGridGuides("sm", styles.grid_smGuide, local.smGridColumns || 8, local.smGridRows || 5)}
class={`${styles.grid_gridGuide} ${styles.grid_xsGuide}`}
style={{ {/* SMD Grid Guides */}
"--x": colIndex() + 1, {renderGridGuides("smd", styles.grid_smdGuide, local.mdGridColumns || 12, local.mdGridRows || 4)}
"--y": rowIndex() + 1,
...(((local.gridRows || 1) > 1 && rowIndex() < (local.gridRows || 1) - 1) && { {/* MD Grid Guides */}
"border-bottom-width": "var(--guide-width)" {renderGridGuides("md", styles.grid_mdGuide, local.mdGridColumns || 12, local.mdGridRows || 4)}
})
}} {/* LG Grid Guides */}
/> {renderGridGuides("lg", styles.grid_lgGuide, local.mdGridColumns || 12, local.mdGridRows || 4)}
)} </section>
</For>
)}
</For>
</div>
<div class={styles.grid_gridGuides}>
<For each={Array.from({ length: local.gridRows || 1 })}>
{(_, rowIndex) => (
<For each={Array.from({ length: local.smGridColumns || 0 })}>
{(_, colIndex) => (
<div
class={`${styles.grid_gridGuide} ${styles.grid_smGuide}`}
style={{
"--x": colIndex() + 1,
"--y": rowIndex() + 1,
...(((local.gridRows || 1) > 1 && rowIndex() < (local.gridRows || 1) - 1) && {
"border-bottom-width": "var(--guide-width)"
})
}}
/>
)}
</For>
)}
</For>
</div>
<div class={styles.grid_gridGuides}>
<For each={Array.from({ length: local.gridRows || 1 })}>
{(_, rowIndex) => (
<For each={Array.from({ length: local.mdGridColumns || 0 })}>
{(_, colIndex) => (
<div
class={`${styles.grid_gridGuide} ${styles.grid_smdGuide}`}
style={{
"--x": colIndex() + 1,
"--y": rowIndex() + 1,
...(((local.gridRows || 1) > 1 && rowIndex() < (local.gridRows || 1) - 1) && {
"border-bottom-width": "var(--guide-width)"
})
}}
/>
)}
</For>
)}
</For>
</div>
<div class={styles.grid_gridGuides}>
<For each={Array.from({ length: local.gridRows || 1 })}>
{(_, rowIndex) => (
<For each={Array.from({ length: local.mdGridColumns || 1 })}>
{(_, colIndex) => (
<div
class={`${styles.grid_gridGuide} ${styles.grid_mdGuide}`}
style={{
"--x": colIndex() + 1,
"--y": rowIndex() + 1,
...(((local.gridRows || 1) > 1 && rowIndex() < (local.gridRows || 1) - 1) && {
"border-bottom-width": "var(--guide-width)"
})
}}
/>
)}
</For>
)}
</For>
</div>
<div class={styles.grid_gridGuides}>
<For each={Array.from({ length: local.gridRows || 1 })}>
{(_, rowIndex) => (
<For each={Array.from({ length: local.lgGridColumns || 0 })}>
{(_, colIndex) => (
<div
class={`${styles.grid_gridGuide} ${styles.grid_lgGuide}`}
style={{
"--x": colIndex() + 1,
"--y": rowIndex() + 1,
...(((local.gridRows || 1) > 1 && rowIndex() < (local.gridRows || 1) - 1) && {
"border-bottom-width": "var(--guide-width)"
})
}}
/>
)}
</For>
)}
</For>
</div>
</div>
); );
} }
interface GridCrossProps { interface GridCrossProps {
crossRow: number; row: number;
crossColumn: number; column: number;
} }
export function Cross(props: GridCrossProps) { export function Cross(props: GridCrossProps) {
return ( return (
<div style={{ <div style={{
"--cross-row": props.crossRow, "--cross-row": props.row,
"--cross-column": props.crossColumn, "--cross-column": props.column,
}} class={styles.grid_cross}> }} class={styles.grid_cross}>
<div <div
style={{ style={{