diff --git a/README.md b/README.md index d39bfdc..15b8f58 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,53 @@ A dependency-free TypeScript terminal UI runtime. Screens are plain data; JSX is one way to write them. ```bash -npm install @textui/kit @textui/widgets +npm install @textui/kit +``` + +```tsx +import { Box, Text, render, useInput, useState } from '@textui/kit'; + +function Counter() { + const [count, setCount] = useState(0); + useInput((e) => { + if (e.name !== '+') return false; + setCount((c) => c + 1); + return true; + }); + + return ( + + Count: {count} + + to increment, ctrl+c to quit + + ); +} + +const { waitUntilExit } = render(); +await waitUntilExit(); +``` + +> Status: pre-1.0. The surface is still moving. + +## Installing + +One package is enough to have something on screen. The rest are additive, and +nothing pulls in a third-party dependency. + +```bash +npm install @textui/kit # pnpm add @textui/kit +``` + +`@textui/kit` is the runtime, a terminal to put it on, and `render`. `Box`, +`Text`, the hooks and `render` all come from here, and the example above needs +nothing else. + +**For the component catalog** - `Panel`, `Table`, `Row`, `Column`, charts, +overlays, forms - add `@textui/widgets`. Its components are resolved by name at +mount time, so they have to be registered before they can render: + +```bash +npm install @textui/kit @textui/widgets # pnpm add @textui/kit @textui/widgets ``` ```tsx @@ -14,7 +60,32 @@ const { waitUntilExit } = render(, { onBoot: registerBuiltins }); await waitUntilExit(); ``` -> Status: pre-1.0. The surface is still moving. +Forget `onBoot` and the components are missing registrations rather than +missing imports - the screen renders, and says so where they should have been. + +**For a project set up for you**, and for components copied into your source +rather than imported, use the CLI. It needs no install: + +```bash +npx @textui/cli init # pnpm dlx @textui/cli init +npx @textui/cli doctor # what this terminal can actually do +``` + +`doctor` is the one to run first when something renders wrong - it reports the +unicode level, colour depth and keyboard protocol actually detected. + +**Working against the pieces directly** - `@textui/core` and `@textui/terminal` +- is the same thing with a longer name. `@textui/kit` re-exports both and adds +`render`; there is nothing in them it hides. Take them separately when you want +the runtime without a terminal attached, which is what the test harness and the +static renderer do: + +```bash +npm install @textui/core @textui/terminal # pnpm add @textui/core @textui/terminal +``` + +Node >= 22, and TypeScript wants `"jsx": "react-jsx"` with +`"jsxImportSource": "@textui/kit"` - see [getting started](docs/getting-started.md). ## The one idea diff --git a/docs/getting-started.md b/docs/getting-started.md index 6213649..701614a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -10,12 +10,99 @@ type Service = { name: string; status: string; cpu: string }; # Getting started ```bash -pnpm add @textui/core @textui/terminal +npm install @textui/kit # pnpm add @textui/kit ``` -Node ≥ 22. There are no other dependencies. +Node ≥ 22. There are no other dependencies, and this one package is enough to +put something on screen. -## The smallest thing that runs +## Hello, in full + +`@textui/kit` is the runtime, a terminal to put it on, and `render`. Everything +this example uses comes from it. + +```tsx +import { Box, Text, render, useInput, useState } from '@textui/kit'; + +function Counter() { + const [count, setCount] = useState(0); + useInput((e) => { + if (e.name !== '+') return false; + setCount((c) => c + 1); + return true; + }); + + return ( + + Count: {count} + + to increment, ctrl+c to quit + + ); +} + +const { waitUntilExit } = render(); +await waitUntilExit(); +``` + +`render` mounts and keeps running - it returns a handle rather than a promise, +so the application is up before the next line. `waitUntilExit()` resolves when +it stops, however it stopped. + +For JSX, point the compiler at the runtime: + +```jsonc +// tsconfig.json +{ + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "@textui/kit" + } +} +``` + +## Adding the catalog + +`Box` and `Text` are host primitives and always available. `Panel`, `Table`, +`Row`, `Column`, the charts, forms and overlays live in `@textui/widgets`, and +are resolved **by name at mount time** - so they have to be registered before +they can render: + +```bash +npm install @textui/widgets # pnpm add @textui/widgets +``` + +```tsx +import { Text, render } from '@textui/kit'; +import { Panel, registerBuiltins } from '@textui/widgets'; + +function Dashboard() { + return ( + + nothing to report + + ); +} + +const { waitUntilExit } = render(, { onBoot: registerBuiltins }); +await waitUntilExit(); +``` + +Forget `onBoot` and nothing throws at compile time: a missing registration is a +runtime miss, rendered visibly where the component should have been. + +## The pieces underneath + +`@textui/kit` re-exports `@textui/core` and [`@textui/terminal`](terminal) +and adds `render`; it hides nothing. Take them +separately when you want the runtime without a terminal attached - which is +what the test harness and the static renderer do - or when you want to own the +wiring: + +```bash +npm install @textui/core @textui/terminal # pnpm add @textui/core @textui/terminal +``` + +Wired by hand, that same hello is: ```tsx import { createApp, WRITER_KEY } from '@textui/core'; @@ -38,19 +125,11 @@ await app.start(); ## JSX -Point the compiler at the runtime: - -```jsonc -// tsconfig.json -{ - "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "@textui/core" - } -} -``` +Working against the pieces directly, `jsxImportSource` is `@textui/core` +rather than `@textui/kit` - the runtime is where the factory lives, and the kit +re-exports it. -Then `` produces `{ component: 'Row', gap: 1 }`. Lowercase names are host primitives - `box`, `text`, `canvas`, `spacer` - and capitalised names are components you import, which is what gives them prop types. +`` produces `{ component: 'Row', gap: 1 }`. Lowercase names are host primitives - `box`, `text`, `canvas`, `spacer` - and capitalised names are components you import, which is what gives them prop types. ```tsx import { useStoreValue } from '@textui/core'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fd2152..5a0db73 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -304,6 +304,9 @@ importers: '@textui/documents': specifier: workspace:* version: link:../../../packages/documents + '@textui/kit': + specifier: workspace:* + version: link:../../../packages/facade '@textui/terminal': specifier: workspace:* version: link:../../../packages/terminal diff --git a/scripts/docs/snippets/package.json b/scripts/docs/snippets/package.json index 283211a..5b01888 100644 --- a/scripts/docs/snippets/package.json +++ b/scripts/docs/snippets/package.json @@ -11,6 +11,7 @@ "dependencies": { "@textui/cli": "workspace:*", "@textui/core": "workspace:*", + "@textui/kit": "workspace:*", "@textui/documents": "workspace:*", "@textui/terminal": "workspace:*", "@textui/textide": "workspace:*",