UNPKG

layoutz

Version:

Friendly, expressive print-layout DSL for JavaScript/TypeScript

577 lines (498 loc) โ€ข 11.9 kB
<p align="center"> <img src="pix/layoutz-ts.png" width="700"> </p> # <img src="../pix/layoutz.png" width="60"> layoutz **Simple, beautiful CLI output ๐Ÿชถ** Build declarative and composable sections, trees, tables, dashboards for your JavaScript applications. ## Features - Zero dependencies - pure ts file - Effortless composition of elements - Rich text formatting: alignment, wrapping, justification, underlines, padding, truncation - Lists, trees, tables, charts, banners... ## Installation Install via npm: ```bash npm install layoutz ``` All you need: ```typescript import * as L from 'layoutz'; ``` ## Quickstart Beautiful, compositional text layouts: ```typescript import * as L from 'layoutz'; const demo = L.layout( L.underline("ห†")("Test Dashboard").center(), L.row( L.statusCard("API", "LIVE").border(L.Border.Double), L.statusCard("DB", "99.9%"), L.statusCard("Cache", "READY").border(L.Border.Thick) ), "", L.box("Services")( L.ul("Production", "Staging", L.ul("test-api", L.ul("more nest"))), "", L.inlineBar("Health", 0.94) ).border(L.Border.Round) ); console.log(demo.render()); ``` ``` Test Dashboard ห†ห†ห†ห†ห†ห†ห†ห†ห†ห†ห†ห†ห†ห† โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•— โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ โ•‘ API โ•‘ โ”‚ DB โ”‚ โ”ƒ Cache โ”ƒ โ•‘ LIVE โ•‘ โ”‚ 99.9% โ”‚ โ”ƒ READY โ”ƒ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ• โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”—โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”› โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€Servicesโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ โ€ข Production โ”‚ โ”‚ โ€ข Staging โ”‚ โ”‚ โ—ฆ test-api โ”‚ โ”‚ โ–ช more nest โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ Health [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ”€โ”€] 94% โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ``` ## Core concepts - Every piece of content is an `Element` - Elements are **immutable** and **composable** - you build complex layouts by combining simple elements - A `layout` arranges elements **vertically** with consistent spacing: ```typescript layout(elem1, elem2, elem3) // Joins with "\n" ``` Call `.render()` on an element to get a string The power comes from **uniform composition** - since everything is an `Element`, everything can be combined with everything else. ## API Style TypeScript `layoutz` uses a **functional** approach with builder methods: ```typescript // Compose elements functionally const element = box("Title")( ul("Item 1", "Item 2"), inlineBar("Progress", 0.75) ).border(Border.Double); // Chain transformations const formatted = "Hello World" .center(20) .underline() .pad(2); ``` ## Elements All components that implement the Element interface you can use in your layouts... ### Text TypeScript `layoutz` automatically converts strings to `Text` elements: ```typescript "Simple text" // <- automatically converted to Text element text("Simple text") // <- explicit Text constructor ``` ### Line Break Add extra line-break "\n" with empty string: ```typescript layout("Line 1", "", "Line 2") ``` ### Section: `section` ```typescript section("Config")(kv("env", "prod")) section("Status", "-")(kv("health", "ok")) section("Report", "#", 5)(kv("items", "42")) ``` ``` === Config === env : prod --- Status --- health : ok ##### Report ##### items : 42 ``` ### Layout (vertical): `layout` ```typescript layout("First", "Second", "Third") ``` ``` First Second Third ``` ### Row (horizontal): `row` ```typescript row("Left", "Middle", "Right") ``` ``` Left Middle Right ``` ### Columns: `columns` ```typescript columns( layout("Tasks", ul("Setup", "Code", ul("more stuff"))), layout("Status", "foo", "bar", "baz") ) ``` ``` Tasks Status โ€ข Setup foo โ€ข Code bar โ—ฆ more stuff baz ``` ### Horizontal rule: `hr` ```typescript hr() hr().width(10).char("~") ``` ``` โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ~~~~~~~~~~ ``` ### Key-value pairs: `kv` ```typescript kv("name", "Alice", "role", "admin") // or with tuples: kv(["name", "Alice"], ["role", "admin"]) ``` ``` name : Alice role : admin ``` ### Table: `table` Tables automatically normalize row lengths - truncating long rows and padding short ones: ```typescript table()( ["Name", "Age", "City"], // headers [ ["Alice", "30", "New York"], ["Bob", "25"], // Short row - auto-padded ["Charlie", "35", "London", "Extra"] // Long row - auto-truncated ] ) ``` ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Name โ”‚ Age โ”‚ City โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ Alice โ”‚ 30 โ”‚ New Yorkโ”‚ โ”‚ Bob โ”‚ 25 โ”‚ โ”‚ โ”‚ Charlie โ”‚ 35 โ”‚ London โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ### Ordered Lists: `ol` Automatically numbered lists ```typescript ol("First step", "Second step", "Third step") ``` ``` 1. First step 2. Second step 3. Third step ``` Hierarchical nested numbering ```typescript ol( "Setup", ol("Install tools", "Configure IDE"), "Development", ol("Write code", ol("Unit tests", "Integration tests")), "Deploy" ) ``` ``` 1. Setup a. Install tools b. Configure IDE 2. Development a. Write code i. Unit tests ii. Integration tests 3. Deploy ``` ### Unordered Lists: `ul` Clean unordered lists with custom bullets ```typescript ul("Feature A", "Feature B", "Feature C") ul("โ†’")("Item 1", "Item 2") ``` ``` โ€ข Feature A โ€ข Feature B โ€ข Feature C โ†’ Item 1 โ†’ Item 2 ``` Nested lists with auto-styling ```typescript ul( "Backend", ul("API", "Database"), "Frontend", ul("Components", ul("Header", ul("Footer"))) ) ``` ``` โ€ข Backend โ—ฆ API โ—ฆ Database โ€ข Frontend โ—ฆ Components โ–ช Header โ€ข Footer ``` ### Underline: `underline` Add underlines to any element ```typescript underline()("Important Title") underline("=")("Custom") ``` ``` Important Title โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Custom โ•โ•โ•โ•โ•โ• ``` ### Box: `box` With title: ```typescript box("Summary")(kv("total", "42")) ``` ``` โ”Œโ”€โ”€Summaryโ”€โ”€โ”€โ” โ”‚ total : 42 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` Without title: ```typescript box()(kv("total", "42")) ``` ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ total : 42 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ### Status card: `statusCard` ```typescript statusCard("CPU", "45%") ``` ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ CPU โ”‚ โ”‚ 45% โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ### Progress bar: `inlineBar` ```typescript inlineBar("Download", 0.75) ``` ``` Download [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ”€โ”€โ”€โ”€โ”€] 75% ``` ### Tree: `tree` ```typescript tree("Project")( tree("src")( tree("main")(tree("App.ts")), tree("test")(tree("App.spec.ts")) ) ) ``` ``` Project โ””โ”€โ”€ src/ โ”œโ”€โ”€ main/ โ”‚ โ””โ”€โ”€ App.ts โ””โ”€โ”€ test/ โ””โ”€โ”€ App.spec.ts ``` ### Banner: `banner` ```typescript banner("System Dashboard").border(Border.Double) ``` ``` โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— โ•‘ System Dashboard โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• ``` ### Chart: `chart` ```typescript chart( ["Web", 10], ["Mobile", 20], ["API", 15] ) ``` ``` Web โ”‚โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ 10.0 Mobile โ”‚โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ 20.0 API โ”‚โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ 15.0 ``` ### Padding: `pad` Add uniform padding around any element ```typescript pad(2)("content") ``` ``` content ``` ### Truncation: `truncate` Truncate long text with ellipsis ```typescript truncate(15)("This is a very long text that will be cut off") truncate(20, "โ€ฆ")("Custom ellipsis example text here") ``` ``` This is a ve... Custom ellipsis exโ€ฆ ``` ### Empty Element: `empty` Useful for conditional rendering ```typescript layout( "Always shown", hasError ? margin.error()("Something failed!") : empty(), "Also always shown" ) ``` ### Vertical Rule: `vr` Vertical separators to complement horizontal rules ```typescript vr(3) // 3-line vertical separator vr(5, "โ”ƒ") // Custom character ``` ``` โ”‚ โ”‚ โ”‚ ``` ### Margin: `margin` Use `margin` for nice & colourful "compiler-style" margin strings: ```typescript layout( layout( "Ooops", "", row("const result: number = ", underline("^")("getUserName()")), "Expected number, found string" ).marginError(), "", layout( "Unused variable detected", row("const ", underline("~")("temp"), " = calculateTotal(items)") ).marginWarn(), "Clean code, cleaner layouts with layoutz", layout( "Pro tip", "", row("const ", underline("~")("beauty"), " = renderCode(perfectly)").margin("[layoutz ~>]") ).marginInfo() ) ``` ## Text Formatting & Layout ### Alignment: `center`/`leftAlign`/`rightAlign` Align text within a specified width ```typescript center("TITLE", 20) leftAlign("Left side", 20) rightAlign("Right side", 20) ``` ``` TITLE Left side Right side ``` Works with multiline text: ```typescript center("Line 1\nLine 2", 15) ``` ``` Line 1 Line 2 ``` ### Text Wrapping: `wrap` Wrap long text at word boundaries ```typescript wrap("This is a very long line that should be wrapped at word boundaries", 20) ``` ``` This is a very long line that should be wrapped at word boundaries ``` ### Text Justification: `justify`/`justifyAll` Distribute spaces to fit exact width ```typescript justify("All the lines\nmaybe the last", 20) justifyAll("All the lines\nmaybe the last", 20) ``` ``` All the lines maybe the last All the lines maybe the last ``` ### Border Styles Elements like `box`, `table`, and `banner` support different `Border` options: **Single** (default): ```typescript box("Title")("").border(Border.Single) // default style is Border.Single, so same as: box("Title")("") ``` ``` โ”Œโ”€Titleโ”€โ” โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` **Double**: ```typescript banner("Welcome").border(Border.Double) ``` ``` โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— โ•‘ Welcome โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ• ``` **Thick**: ```typescript table().border(Border.Thick)(headers, rows) ``` ``` โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”“ โ”ƒ Name โ”ƒ Status โ”ƒ โ”ฃโ”โ”โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ โ”ƒ Alice โ”ƒ Online โ”ƒ โ”—โ”โ”โ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”โ”โ”โ”› ``` **Round**: ```typescript box("Info")("").border(Border.Round) ``` ``` โ•ญโ”€Infoโ”€โ•ฎ โ”‚ โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ``` **Custom**: ```typescript box("Hello hello")("World!").border( Border.Custom("+", "=", "|") ) ``` ``` +==Hello hello==+ | World! | +===============+ ``` ## Browser Usage Import via CDN or bundle with your favorite bundler: ```html <!-- Via CDN --> <script type="module"> import { layout, box, ul } from 'https://unpkg.com/layoutz@latest/dist/index.esm.js'; const demo = layout( box("Browser Demo")( ul("Works in browser", "No dependencies", "ES modules") ) ); console.log(demo.render()); </script> ``` ## Inspiration - Original Scala [layoutz](https://github.com/mattlianje/layoutz) - [ScalaTags](https://github.com/com-lihaoyi/scalatags) by Li Haoyi