@damarkuncoro/layout-engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +45 -0
  2. package/dist/core/index.d.ts +3 -0
  3. package/dist/core/index.js +3 -0
  4. package/dist/core/render.d.ts +5 -0
  5. package/dist/core/render.js +41 -0
  6. package/dist/core/responsiveSystem.d.ts +11 -0
  7. package/dist/core/responsiveSystem.js +41 -0
  8. package/dist/core/spacingSystem.d.ts +2 -0
  9. package/dist/core/spacingSystem.js +17 -0
  10. package/dist/core/styleResolver.d.ts +5 -0
  11. package/dist/core/styleResolver.js +10 -0
  12. package/dist/index.d.ts +12 -0
  13. package/dist/index.js +12 -0
  14. package/dist/patterns/DashboardLayout.d.ts +8 -0
  15. package/dist/patterns/DashboardLayout.js +11 -0
  16. package/dist/primitives/Box.d.ts +15 -0
  17. package/dist/primitives/Box.js +23 -0
  18. package/dist/primitives/Flex.d.ts +20 -0
  19. package/dist/primitives/Flex.js +27 -0
  20. package/dist/primitives/Grid.d.ts +15 -0
  21. package/dist/primitives/Grid.js +27 -0
  22. package/dist/primitives/Stack.d.ts +13 -0
  23. package/dist/primitives/Stack.js +21 -0
  24. package/dist/primitives/index.d.ts +4 -0
  25. package/dist/primitives/index.js +4 -0
  26. package/dist/structures/SidebarLayout.d.ts +13 -0
  27. package/dist/structures/SidebarLayout.js +16 -0
  28. package/dist/system/contracts.d.ts +17 -0
  29. package/dist/system/contracts.js +1 -0
  30. package/dist/system/index.d.ts +2 -0
  31. package/dist/system/index.js +2 -0
  32. package/dist/system/types.d.ts +38 -0
  33. package/dist/system/types.js +1 -0
  34. package/dist/tests/basic.test.d.ts +1 -0
  35. package/dist/tests/basic.test.js +38 -0
  36. package/dist/tests/structures.test.d.ts +1 -0
  37. package/dist/tests/structures.test.js +25 -0
  38. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # layout-engine
2
+
3
+ Headless layout engine berfokus pada primitives (Box, Flex, Stack, Grid) dan structures (SidebarLayout) dengan sistem responsive sederhana.
4
+
5
+ ## Instalasi
6
+
7
+ ```bash
8
+ npm install @damarkuncoro/layout-engine
9
+ ```
10
+
11
+ ## Fitur
12
+ - Headless nodes → mudah dirender ke HTML string atau adaptor UI.
13
+ - Primitives: Box, Flex, Stack, Grid.
14
+ - Structures: SidebarLayout.
15
+ - Sistem responsive berbasis breakpoints (sm, md, lg, xl, 2xl).
16
+
17
+ ## Penggunaan Dasar
18
+
19
+ Render headless ke HTML:
20
+
21
+ ```js
22
+ import { Box, Flex, Stack, Grid, SidebarLayout, renderToString } from "layout-engine"
23
+
24
+ const page = SidebarLayout({
25
+ sidebar: Box({ children: "Sidebar" }),
26
+ children: Stack({ gap: 16, children: [Box({ children: "Card 1" }), Box({ children: "Card 2" })] }),
27
+ sidebarWidth: { base: 120, md: 200, xl: 280 },
28
+ viewportWidth: 1024
29
+ })
30
+
31
+ console.log(renderToString(page))
32
+ ```
33
+
34
+ Responsive utility:
35
+
36
+ ```js
37
+ import { resolveResponsive } from "layout-engine"
38
+ const gap = resolveResponsive({ base: 8, md: 16, xl: 24 }, 768) // 16
39
+ ```
40
+
41
+ ## TypeScript
42
+ - ESM-only, deklarasi tipe tersedia pada `dist/index.d.ts`.
43
+
44
+ ## Lisensi
45
+ MIT
@@ -0,0 +1,3 @@
1
+ export * from "./styleResolver.js";
2
+ export * from "./spacingSystem.js";
3
+ export * from "./responsiveSystem.js";
@@ -0,0 +1,3 @@
1
+ export * from "./styleResolver.js";
2
+ export * from "./spacingSystem.js";
3
+ export * from "./responsiveSystem.js";
@@ -0,0 +1,5 @@
1
+ import type { HeadlessNode } from "../system/types.js";
2
+ /**
3
+ * Merender HeadlessNode menjadi string HTML dengan inline style.
4
+ */
5
+ export declare const renderToString: (node: HeadlessNode) => string;
@@ -0,0 +1,41 @@
1
+ const styleToString = (style) => {
2
+ if (!style)
3
+ return "";
4
+ const entries = Object.entries(style)
5
+ .filter(([_, v]) => v !== undefined && v !== null && v !== "")
6
+ .map(([k, v]) => {
7
+ const prop = k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
8
+ return `${prop}:${String(v)}`;
9
+ });
10
+ return entries.join(";");
11
+ };
12
+ const escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
13
+ const renderChild = (child) => {
14
+ if (child === null || child === undefined || child === false)
15
+ return "";
16
+ if (Array.isArray(child))
17
+ return child.map(renderChild).join("");
18
+ if (typeof child === "object" && child.type && child.props) {
19
+ return renderToString(child);
20
+ }
21
+ return escapeHtml(String(child));
22
+ };
23
+ /**
24
+ * Merender HeadlessNode menjadi string HTML dengan inline style.
25
+ */
26
+ export const renderToString = (node) => {
27
+ const { type, props } = node;
28
+ const { children, style, className, ...rest } = props || {};
29
+ const styleStr = styleToString(style);
30
+ const attr = (className ? ` class="${className}"` : "") +
31
+ (styleStr ? ` style="${styleStr}"` : "") +
32
+ Object.entries(rest)
33
+ .map(([k, v]) => {
34
+ if (v === undefined || v === null || typeof v === "object")
35
+ return "";
36
+ return ` ${k}="${String(v)}"`;
37
+ })
38
+ .join("");
39
+ const inner = renderChild(children);
40
+ return `<${type}${attr}>${inner}</${type}>`;
41
+ };
@@ -0,0 +1,11 @@
1
+ export type BreakpointKey = "sm" | "md" | "lg" | "xl" | "2xl";
2
+ export declare const breakpoints: Record<BreakpointKey, number>;
3
+ export type ResponsiveValue<T> = T | {
4
+ base?: T;
5
+ sm?: T;
6
+ md?: T;
7
+ lg?: T;
8
+ xl?: T;
9
+ "2xl"?: T;
10
+ } | T[];
11
+ export declare const resolveResponsive: <T>(value: ResponsiveValue<T> | undefined, width: number) => T | undefined;
@@ -0,0 +1,41 @@
1
+ export const breakpoints = {
2
+ sm: 640,
3
+ md: 768,
4
+ lg: 1024,
5
+ xl: 1280,
6
+ "2xl": 1536
7
+ };
8
+ export const resolveResponsive = (value, width) => {
9
+ if (value === undefined)
10
+ return undefined;
11
+ if (!Array.isArray(value) && typeof value !== "object")
12
+ return value;
13
+ const order = [
14
+ { min: 0 },
15
+ { key: "sm", min: breakpoints.sm },
16
+ { key: "md", min: breakpoints.md },
17
+ { key: "lg", min: breakpoints.lg },
18
+ { key: "xl", min: breakpoints.xl },
19
+ { key: "2xl", min: breakpoints["2xl"] }
20
+ ];
21
+ let resolved = undefined;
22
+ if (Array.isArray(value)) {
23
+ for (let i = 0; i < order.length; i++) {
24
+ if (width >= order[i].min && i < value.length) {
25
+ resolved = value[i];
26
+ }
27
+ }
28
+ return resolved;
29
+ }
30
+ const obj = value;
31
+ if (obj.base !== undefined)
32
+ resolved = obj.base;
33
+ for (const it of order) {
34
+ if (!it.key)
35
+ continue;
36
+ if (width >= it.min && obj[it.key] !== undefined) {
37
+ resolved = obj[it.key];
38
+ }
39
+ }
40
+ return resolved;
41
+ };
@@ -0,0 +1,2 @@
1
+ export declare const spacingScale: Record<number, number>;
2
+ export declare const space: (i: number) => string;
@@ -0,0 +1,17 @@
1
+ export const spacingScale = {
2
+ 0: 0,
3
+ 1: 4,
4
+ 2: 8,
5
+ 3: 12,
6
+ 4: 16,
7
+ 5: 24,
8
+ 6: 32,
9
+ 7: 40,
10
+ 8: 48,
11
+ 9: 64
12
+ };
13
+ export const space = (i) => {
14
+ const v = spacingScale[i];
15
+ const n = typeof v === "number" ? v : i;
16
+ return `${n}px`;
17
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Mengubah nilai panjang CSS: angka → px, string tetap.
3
+ * Contoh: 16 -> "16px", "2rem" -> "2rem", undefined -> undefined
4
+ */
5
+ export declare const normalizeUnit: (value?: number | string) => string | undefined;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Mengubah nilai panjang CSS: angka → px, string tetap.
3
+ * Contoh: 16 -> "16px", "2rem" -> "2rem", undefined -> undefined
4
+ */
5
+ export const normalizeUnit = (value) => {
6
+ if (typeof value === "number") {
7
+ return `${value}px`;
8
+ }
9
+ return value;
10
+ };
@@ -0,0 +1,12 @@
1
+ export * from "./core/styleResolver.js";
2
+ export * from "./core/spacingSystem.js";
3
+ export { resolveResponsive, breakpoints, type BreakpointKey } from "./core/responsiveSystem.js";
4
+ export * from "./core/render.js";
5
+ export * from "./system/types.js";
6
+ export * from "./system/contracts.js";
7
+ export { Box, type LayoutProps } from "./primitives/Box.js";
8
+ export { Flex, type FlexProps } from "./primitives/Flex.js";
9
+ export { Stack, type StackProps } from "./primitives/Stack.js";
10
+ export { Grid, type GridProps } from "./primitives/Grid.js";
11
+ export { SidebarLayout, type ResponsiveSidebarLayoutProps } from "./structures/SidebarLayout.js";
12
+ export * from "./patterns/DashboardLayout.js";
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export * from "./core/styleResolver.js";
2
+ export * from "./core/spacingSystem.js";
3
+ export { resolveResponsive, breakpoints } from "./core/responsiveSystem.js";
4
+ export * from "./core/render.js";
5
+ export * from "./system/types.js";
6
+ export * from "./system/contracts.js";
7
+ export { Box } from "./primitives/Box.js";
8
+ export { Flex } from "./primitives/Flex.js";
9
+ export { Stack } from "./primitives/Stack.js";
10
+ export { Grid } from "./primitives/Grid.js";
11
+ export { SidebarLayout } from "./structures/SidebarLayout.js";
12
+ export * from "./patterns/DashboardLayout.js";
@@ -0,0 +1,8 @@
1
+ import type { DashboardLayoutContract } from "../system/contracts.js";
2
+ export declare function DashboardLayout({ header, sidebar, children }: DashboardLayoutContract): {
3
+ type: string;
4
+ props: {
5
+ children: any;
6
+ style: Record<string, any>;
7
+ };
8
+ };
@@ -0,0 +1,11 @@
1
+ import { Flex } from "../primitives/Flex.js";
2
+ import { SidebarLayout } from "../structures/SidebarLayout.js";
3
+ export function DashboardLayout({ header, sidebar, children }) {
4
+ return Flex({
5
+ direction: "column",
6
+ children: [
7
+ header,
8
+ SidebarLayout({ sidebar, children })
9
+ ]
10
+ });
11
+ }
@@ -0,0 +1,15 @@
1
+ import type { LayoutProps } from "../system/types.js";
2
+ export { type LayoutProps };
3
+ /**
4
+ * Komponen blok dasar yang memetakan LayoutProps → inline style.
5
+ * Gunakan untuk membangun primitive lainnya.
6
+ */
7
+ export declare function Box({ children, padding, margin, width, height, display, style, ...rest }: LayoutProps & {
8
+ children?: any;
9
+ }): {
10
+ type: string;
11
+ props: {
12
+ children: any;
13
+ style: Record<string, any>;
14
+ };
15
+ };
@@ -0,0 +1,23 @@
1
+ import { normalizeUnit } from "../core/styleResolver.js";
2
+ /**
3
+ * Komponen blok dasar yang memetakan LayoutProps → inline style.
4
+ * Gunakan untuk membangun primitive lainnya.
5
+ */
6
+ export function Box({ children, padding, margin, width, height, display, style, ...rest }) {
7
+ const resolved = {
8
+ padding: normalizeUnit(padding),
9
+ margin: normalizeUnit(margin),
10
+ width: normalizeUnit(width),
11
+ height: normalizeUnit(height),
12
+ display,
13
+ ...style
14
+ };
15
+ return {
16
+ type: "div",
17
+ props: {
18
+ style: resolved,
19
+ ...rest,
20
+ children
21
+ }
22
+ };
23
+ }
@@ -0,0 +1,20 @@
1
+ import type { LayoutProps, CSSLength } from "../system/types.js";
2
+ export interface FlexProps extends LayoutProps {
3
+ justify?: string;
4
+ align?: string;
5
+ gap?: CSSLength;
6
+ direction?: "row" | "row-reverse" | "column" | "column-reverse";
7
+ }
8
+ /**
9
+ * Kontainer fleksibel berbasis style inline (tanpa dependency React).
10
+ * Menghasilkan struktur objek yang merepresentasikan node 'div' dengan style flex.
11
+ */
12
+ export declare function Flex({ children, justify, align, gap, direction, padding, margin, width, height, display, style, ...rest }: FlexProps & {
13
+ children?: any;
14
+ }): {
15
+ type: string;
16
+ props: {
17
+ children: any;
18
+ style: Record<string, any>;
19
+ };
20
+ };
@@ -0,0 +1,27 @@
1
+ import { normalizeUnit } from "../core/styleResolver.js";
2
+ /**
3
+ * Kontainer fleksibel berbasis style inline (tanpa dependency React).
4
+ * Menghasilkan struktur objek yang merepresentasikan node 'div' dengan style flex.
5
+ */
6
+ export function Flex({ children, justify, align, gap, direction = "row", padding, margin, width, height, display, style, ...rest }) {
7
+ const resolved = {
8
+ padding: normalizeUnit(padding),
9
+ margin: normalizeUnit(margin),
10
+ width: normalizeUnit(width),
11
+ height: normalizeUnit(height),
12
+ display: display !== null && display !== void 0 ? display : "flex",
13
+ justifyContent: justify,
14
+ alignItems: align,
15
+ gap: normalizeUnit(gap),
16
+ flexDirection: direction,
17
+ ...style
18
+ };
19
+ return {
20
+ type: "div",
21
+ props: {
22
+ style: resolved,
23
+ ...rest,
24
+ children
25
+ }
26
+ };
27
+ }
@@ -0,0 +1,15 @@
1
+ import type { CSSLength, LayoutProps } from "../system/types.js";
2
+ export interface GridProps extends LayoutProps {
3
+ columns?: number | string;
4
+ rows?: number | string;
5
+ gap?: CSSLength;
6
+ }
7
+ export declare function Grid({ children, columns, rows, gap, padding, margin, width, height, display, style, ...rest }: GridProps & {
8
+ children?: any;
9
+ }): {
10
+ type: string;
11
+ props: {
12
+ children: any;
13
+ style: Record<string, any>;
14
+ };
15
+ };
@@ -0,0 +1,27 @@
1
+ import { normalizeUnit } from "../core/styleResolver.js";
2
+ const toTemplate = (v) => {
3
+ if (typeof v === "number")
4
+ return `repeat(${v}, minmax(0, 1fr))`;
5
+ return v;
6
+ };
7
+ export function Grid({ children, columns, rows, gap, padding, margin, width, height, display, style, ...rest }) {
8
+ const resolved = {
9
+ padding: normalizeUnit(padding),
10
+ margin: normalizeUnit(margin),
11
+ width: normalizeUnit(width),
12
+ height: normalizeUnit(height),
13
+ display: display !== null && display !== void 0 ? display : "grid",
14
+ gridTemplateColumns: toTemplate(columns),
15
+ gridTemplateRows: toTemplate(rows),
16
+ gap: normalizeUnit(gap),
17
+ ...style
18
+ };
19
+ return {
20
+ type: "div",
21
+ props: {
22
+ style: resolved,
23
+ ...rest,
24
+ children
25
+ }
26
+ };
27
+ }
@@ -0,0 +1,13 @@
1
+ import type { CSSLength, LayoutProps } from "../system/types.js";
2
+ export interface StackProps extends LayoutProps {
3
+ gap?: CSSLength;
4
+ }
5
+ export declare function Stack({ children, gap, padding, margin, width, height, display, style, ...rest }: StackProps & {
6
+ children?: any;
7
+ }): {
8
+ type: string;
9
+ props: {
10
+ children: any;
11
+ style: Record<string, any>;
12
+ };
13
+ };
@@ -0,0 +1,21 @@
1
+ import { normalizeUnit } from "../core/styleResolver.js";
2
+ export function Stack({ children, gap = 8, padding, margin, width, height, display, style, ...rest }) {
3
+ const resolved = {
4
+ padding: normalizeUnit(padding),
5
+ margin: normalizeUnit(margin),
6
+ width: normalizeUnit(width),
7
+ height: normalizeUnit(height),
8
+ display: display !== null && display !== void 0 ? display : "flex",
9
+ flexDirection: "column",
10
+ gap: normalizeUnit(gap),
11
+ ...style
12
+ };
13
+ return {
14
+ type: "div",
15
+ props: {
16
+ style: resolved,
17
+ ...rest,
18
+ children
19
+ }
20
+ };
21
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./Box";
2
+ export * from "./Flex";
3
+ export * from "./Grid";
4
+ export * from "./Stack";
@@ -0,0 +1,4 @@
1
+ export * from "./Box";
2
+ export * from "./Flex";
3
+ export * from "./Grid";
4
+ export * from "./Stack";
@@ -0,0 +1,13 @@
1
+ import type { SidebarLayoutContract } from "../system/contracts.js";
2
+ import type { CSSLength, ResponsiveValue } from "../system/types.js";
3
+ export interface ResponsiveSidebarLayoutProps extends Omit<SidebarLayoutContract, "sidebarWidth"> {
4
+ sidebarWidth?: ResponsiveValue<CSSLength>;
5
+ viewportWidth?: number;
6
+ }
7
+ export declare function SidebarLayout({ sidebar, children, sidebarWidth, viewportWidth }: ResponsiveSidebarLayoutProps): {
8
+ type: string;
9
+ props: {
10
+ children: any;
11
+ style: Record<string, any>;
12
+ };
13
+ };
@@ -0,0 +1,16 @@
1
+ import { Flex } from "../primitives/Flex.js";
2
+ import { Box } from "../primitives/Box.js";
3
+ import { resolveResponsive } from "../core/responsiveSystem.js";
4
+ import { normalizeUnit } from "../core/styleResolver.js";
5
+ export function SidebarLayout({ sidebar, children, sidebarWidth = 240, viewportWidth = 1024 }) {
6
+ var _a;
7
+ // Resolve responsive sidebarWidth based on viewport
8
+ const resolvedWidth = normalizeUnit((_a = resolveResponsive(sidebarWidth, viewportWidth)) !== null && _a !== void 0 ? _a : 240);
9
+ return Flex({
10
+ gap: 16,
11
+ children: [
12
+ Box({ width: resolvedWidth, children: sidebar }),
13
+ Box({ style: { flex: 1 }, children })
14
+ ]
15
+ });
16
+ }
@@ -0,0 +1,17 @@
1
+ import type { CSSLength, HeadlessNode, ResponsiveValue } from "./types.js";
2
+ export type NodeLike = HeadlessNode | string | number | boolean | null | undefined;
3
+ export interface SidebarLayoutContract {
4
+ sidebar: NodeLike;
5
+ children: NodeLike;
6
+ sidebarWidth?: CSSLength | ResponsiveValue<CSSLength>;
7
+ viewportWidth?: number;
8
+ }
9
+ export interface HeaderLayoutContract {
10
+ header: NodeLike;
11
+ children: NodeLike;
12
+ }
13
+ export interface DashboardLayoutContract {
14
+ header: NodeLike;
15
+ sidebar: NodeLike;
16
+ children: NodeLike;
17
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export * from "./contracts.js";
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export * from "./contracts.js";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Tipe panjang CSS. Angka akan dinormalisasi ke 'px' oleh resolver.
3
+ */
4
+ export type CSSLength = number | string;
5
+ /**
6
+ * Responsive value that adapts based on viewport width.
7
+ */
8
+ export type ResponsiveValue<T> = T | {
9
+ base?: T;
10
+ sm?: T;
11
+ md?: T;
12
+ lg?: T;
13
+ xl?: T;
14
+ "2xl"?: T;
15
+ } | T[];
16
+ /**
17
+ * Props dasar untuk layout primitives.
18
+ * Hanya properti yang dipetakan ke style inline.
19
+ */
20
+ export interface LayoutProps {
21
+ padding?: CSSLength;
22
+ margin?: CSSLength;
23
+ width?: CSSLength;
24
+ height?: CSSLength;
25
+ display?: string;
26
+ style?: Record<string, any>;
27
+ }
28
+ /**
29
+ * Representasi node headless untuk renderer.
30
+ */
31
+ export interface HeadlessNode {
32
+ type: string;
33
+ props: {
34
+ style?: Record<string, any>;
35
+ children?: any;
36
+ [key: string]: any;
37
+ };
38
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ const assert = (cond, msg) => {
2
+ if (!cond) {
3
+ throw new Error(`Test failed: ${msg}`);
4
+ }
5
+ };
6
+ const run = async () => {
7
+ const api = await import(new URL("../index.js", import.meta.url).href);
8
+ const { normalizeUnit, space, resolveResponsive, Flex, Box, renderToString } = api;
9
+ // normalizeUnit
10
+ assert(normalizeUnit(16) === "16px", "normalizeUnit number to px");
11
+ assert(normalizeUnit("2rem") === "2rem", "normalizeUnit keeps string");
12
+ // spacing tokens
13
+ assert(space(2) === "8px", "spacing index 2 is 8px");
14
+ // responsive resolver (object form)
15
+ assert(resolveResponsive({ base: 8, md: 16, xl: 24 }, 800) === 16, "resolveResponsive picks md at width 800");
16
+ assert(resolveResponsive({ base: 8, md: 16, xl: 24 }, 1300) === 24, "resolveResponsive picks xl at width 1300");
17
+ // primitives mapping + render
18
+ const node = Flex({
19
+ direction: "row",
20
+ gap: 16,
21
+ children: [
22
+ Box({ width: 240, children: "Sidebar" }),
23
+ Box({ style: { flex: 1 }, children: "Content" })
24
+ ]
25
+ });
26
+ const html = renderToString(node);
27
+ assert(html.includes("display:flex"), "Flex renders display flex");
28
+ assert(html.includes("flex-direction:row"), "Flex renders direction");
29
+ assert(html.includes("gap:16px"), "Flex renders gap 16px");
30
+ assert(html.includes("width:240px"), "Box width 240px");
31
+ assert(html.includes("flex:1"), "Box flex:1 in style");
32
+ console.log("All tests passed");
33
+ };
34
+ run().catch((e) => {
35
+ console.error(e);
36
+ process.exit(1);
37
+ });
38
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ const assert = (cond, msg) => {
2
+ if (!cond) {
3
+ throw new Error(`Test failed: ${msg}`);
4
+ }
5
+ };
6
+ const run = async () => {
7
+ const api = await import(new URL("../index.js", import.meta.url).href);
8
+ const { SidebarLayout, DashboardLayout, renderToString, Box } = api;
9
+ const sidebar = Box({ children: "SIDE" });
10
+ const content = Box({ children: "MAIN" });
11
+ const header = Box({ children: "HEAD" });
12
+ const node1 = SidebarLayout({ sidebar, children: content, sidebarWidth: 200 });
13
+ const html1 = renderToString(node1);
14
+ assert(html1.includes("width:200px"), "Sidebar width applied");
15
+ assert(html1.indexOf("SIDE") < html1.indexOf("MAIN"), "Sidebar before content");
16
+ const node2 = DashboardLayout({ header, sidebar, children: content });
17
+ const html2 = renderToString(node2);
18
+ assert(html2.indexOf("HEAD") < html2.indexOf("SIDE"), "Header before sidebar/content");
19
+ console.log("Structures tests passed");
20
+ };
21
+ run().catch((e) => {
22
+ console.error(e);
23
+ process.exit(1);
24
+ });
25
+ export {};
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@damarkuncoro/layout-engine",
3
+ "version": "0.1.0",
4
+ "description": "Minimal layout engine primitives and resolvers",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "module": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "sideEffects": false,
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "keywords": [
15
+ "layout",
16
+ "ui",
17
+ "headless",
18
+ "responsive",
19
+ "primitives"
20
+ ],
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ }
29
+ },
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "typecheck": "tsc -p tsconfig.json --noEmit",
33
+ "test": "npm run build && node dist/tests/basic.test.js && node dist/tests/structures.test.js"
34
+ },
35
+ "peerDependencies": {},
36
+ "devDependencies": {
37
+ "typescript": "^5.4.0"
38
+ }
39
+ }