import { css, html } from "lit";
import { classMap } from "lit/directives/class-map.js";
import { BaseElement } from "../../core/base-element.ts";
/**
* CTGrid - CSS Grid layout component
*
* @element ct-grid
*
* @attr {string} columns - Number of columns (1-12) or custom template
* @attr {string} rows - Number of rows or custom template
* @attr {string} gap - Gap between items (0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24)
* @attr {string} row-gap - Row gap (overrides gap)
* @attr {string} column-gap - Column gap (overrides gap)
* @attr {string} align - Align items (start, center, end, stretch)
* @attr {string} justify - Justify items (start, center, end, stretch)
* @attr {string} place - Place items shorthand (combines align and justify)
* @attr {string} flow - Grid auto flow (row, column, dense, row-dense, column-dense)
* @attr {string} padding - Padding around the grid
*
* @slot - Content to be laid out in a grid
*
* @example
*
* Item 1
* Item 2
* Item 3
*
*/
export class CTGrid extends BaseElement {
static override styles = css`
:host {
display: block;
container-type: inline-size;
}
.grid {
display: grid;
box-sizing: border-box;
}
/* Gap utilities */
.gap-0 {
gap: 0;
}
.gap-1 {
gap: 0.25rem;
}
.gap-2 {
gap: 0.5rem;
}
.gap-3 {
gap: 0.75rem;
}
.gap-4 {
gap: 1rem;
}
.gap-5 {
gap: 1.25rem;
}
.gap-6 {
gap: 1.5rem;
}
.gap-8 {
gap: 2rem;
}
.gap-10 {
gap: 2.5rem;
}
.gap-12 {
gap: 3rem;
}
.gap-16 {
gap: 4rem;
}
.gap-20 {
gap: 5rem;
}
.gap-24 {
gap: 6rem;
}
/* Row gap utilities */
.row-gap-0 {
row-gap: 0;
}
.row-gap-1 {
row-gap: 0.25rem;
}
.row-gap-2 {
row-gap: 0.5rem;
}
.row-gap-3 {
row-gap: 0.75rem;
}
.row-gap-4 {
row-gap: 1rem;
}
.row-gap-5 {
row-gap: 1.25rem;
}
.row-gap-6 {
row-gap: 1.5rem;
}
.row-gap-8 {
row-gap: 2rem;
}
.row-gap-10 {
row-gap: 2.5rem;
}
.row-gap-12 {
row-gap: 3rem;
}
.row-gap-16 {
row-gap: 4rem;
}
/* Column gap utilities */
.col-gap-0 {
column-gap: 0;
}
.col-gap-1 {
column-gap: 0.25rem;
}
.col-gap-2 {
column-gap: 0.5rem;
}
.col-gap-3 {
column-gap: 0.75rem;
}
.col-gap-4 {
column-gap: 1rem;
}
.col-gap-5 {
column-gap: 1.25rem;
}
.col-gap-6 {
column-gap: 1.5rem;
}
.col-gap-8 {
column-gap: 2rem;
}
.col-gap-10 {
column-gap: 2.5rem;
}
.col-gap-12 {
column-gap: 3rem;
}
.col-gap-16 {
column-gap: 4rem;
}
/* Alignment */
.align-start {
align-items: start;
}
.align-center {
align-items: center;
}
.align-end {
align-items: end;
}
.align-stretch {
align-items: stretch;
}
/* Justification */
.justify-start {
justify-items: start;
}
.justify-center {
justify-items: center;
}
.justify-end {
justify-items: end;
}
.justify-stretch {
justify-items: stretch;
}
/* Place items */
.place-start {
place-items: start;
}
.place-center {
place-items: center;
}
.place-end {
place-items: end;
}
.place-stretch {
place-items: stretch;
}
/* Grid flow */
.flow-row {
grid-auto-flow: row;
}
.flow-column {
grid-auto-flow: column;
}
.flow-dense {
grid-auto-flow: dense;
}
.flow-row-dense {
grid-auto-flow: row dense;
}
.flow-column-dense {
grid-auto-flow: column dense;
}
/* Padding utilities */
.p-0 {
padding: 0;
}
.p-1 {
padding: 0.25rem;
}
.p-2 {
padding: 0.5rem;
}
.p-3 {
padding: 0.75rem;
}
.p-4 {
padding: 1rem;
}
.p-5 {
padding: 1.25rem;
}
.p-6 {
padding: 1.5rem;
}
.p-8 {
padding: 2rem;
}
.p-10 {
padding: 2.5rem;
}
.p-12 {
padding: 3rem;
}
.p-16 {
padding: 4rem;
}
.p-20 {
padding: 5rem;
}
.p-24 {
padding: 6rem;
}
/* Responsive grid columns */
@container (min-width: 640px) {
.grid-sm-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.grid-sm-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid-sm-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid-sm-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid-sm-6 {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
}
@container (min-width: 768px) {
.grid-md-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.grid-md-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid-md-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid-md-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid-md-6 {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
}
@container (min-width: 1024px) {
.grid-lg-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.grid-lg-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid-lg-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid-lg-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid-lg-6 {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
}
`;
static override properties = {
columns: { type: String },
rows: { type: String },
gap: { type: String },
rowGap: { type: String, attribute: "row-gap" },
columnGap: { type: String, attribute: "column-gap" },
align: { type: String },
justify: { type: String },
place: { type: String },
flow: { type: String },
padding: { type: String },
};
declare columns: string;
declare rows: string;
declare gap: string;
declare rowGap: string;
declare columnGap: string;
declare align: string;
declare justify: string;
declare place: string;
declare flow: string;
declare padding: string;
constructor() {
super();
this.columns = "1";
this.rows = "";
this.gap = "0";
this.rowGap = "";
this.columnGap = "";
this.align = "stretch";
this.justify = "stretch";
this.place = "";
this.flow = "row";
this.padding = "0";
}
private getGridTemplateColumns(): string {
// Check if it's a number (1-12)
const num = parseInt(this.columns);
if (!isNaN(num) && num >= 1 && num <= 12) {
return `repeat(${num}, minmax(0, 1fr))`;
}
// Otherwise, use as custom template
return this.columns;
}
private getGridTemplateRows(): string {
if (!this.rows) return "";
// Check if it's a number
const num = parseInt(this.rows);
if (!isNaN(num) && num >= 1) {
return `repeat(${num}, minmax(0, 1fr))`;
}
// Otherwise, use as custom template
return this.rows;
}
override render() {
const classes: Record = {
grid: true,
[`p-${this.padding}`]: true,
[`flow-${this.flow}`]: true,
};
// Add place or align/justify classes
if (this.place) {
classes[`place-${this.place}`] = true;
} else {
if (this.align) classes[`align-${this.align}`] = true;
if (this.justify) classes[`justify-${this.justify}`] = true;
}
// Add gap classes
if (this.rowGap || this.columnGap) {
if (this.rowGap) classes[`row-gap-${this.rowGap}`] = true;
if (this.columnGap) classes[`col-gap-${this.columnGap}`] = true;
} else {
classes[`gap-${this.gap}`] = true;
}
const gridStyle = {
"grid-template-columns": this.getGridTemplateColumns(),
...(this.rows && { "grid-template-rows": this.getGridTemplateRows() }),
};
return html`
`;
}
}
globalThis.customElements.define("ct-grid", CTGrid);