Unlayered CSS beats Tailwind's @layer utilities regardless of specificity, so base.css (and consumer stylesheets) overrode classes passed to components. Element defaults now live in @layer base and twui-* rules in @layer components. Third-party TinyMCE/Ace overrides stay unlayered. README documents wrapping consumer overrides in layers. Co-Authored-By: Claude Opus 5.5 <[email protected]>
162 lines
5.9 KiB
Markdown
162 lines
5.9 KiB
Markdown
# @moduletrace/twui
|
|
|
|
A modular skeletal React component framework for Tailwind CSS v4.
|
|
|
|
## Install
|
|
|
|
The package is published to the Moduletrace Gitea registry. Add this to your project's `.npmrc`:
|
|
|
|
```ini
|
|
@moduletrace:registry=https://git.tben.me/api/packages/moduletrace/npm/
|
|
```
|
|
|
|
Then install the package with its required peers:
|
|
|
|
```sh
|
|
bun add @moduletrace/twui react react-dom lucide-react tailwindcss
|
|
```
|
|
|
|
### Optional peers
|
|
|
|
Only install these if you use the matching components:
|
|
|
|
| Feature | Packages |
|
|
| ------------------------------------------------------- | ------------------------------------------------ |
|
|
| `editors/AceEditor` | `ace-builds` |
|
|
| `elements/RemoteCodeBlock`, `mdx/markdown/*` | `next-mdx-remote`, `remark-gfm`, `rehype-prism-plus` |
|
|
| `mdx/mdx-components`, `next-js/hooks/useMDXComponents` | `@types/mdx` (dev) |
|
|
| `elements/ai/*` | `react-markdown`, `remark-gfm` (+ `openai` types for the legacy `AIPrompt*` blocks) |
|
|
|
|
`editors/TinyMCE` loads TinyMCE from a URL at runtime. Set it with the `base_url` prop, or with the `NEXT_PUBLIC_TINYMCE_BASE_URL` / `BUNEXT_PUBLIC_TINYMCE_BASE_URL` public env vars.
|
|
|
|
## CSS
|
|
|
|
Import `base.css` once in your main stylesheet. It includes `@import "tailwindcss"`, the default theme tokens, and an `@source` rule so Tailwind generates the classes the components use. Override tokens in your own `@theme inline` block:
|
|
|
|
```css
|
|
@import "@moduletrace/twui/base.css";
|
|
|
|
@theme inline {
|
|
--color-primary: #e2b433;
|
|
--color-primary-hover: #9e7503;
|
|
--color-primary-dark: #e2b433;
|
|
--color-primary-dark-hover: #f7cc59;
|
|
/* ...see base.css for the full list of tokens */
|
|
}
|
|
|
|
/* Element defaults */
|
|
@layer base {
|
|
h1 {
|
|
@apply text-5xl font-bold;
|
|
}
|
|
}
|
|
|
|
/* Templates for twui components */
|
|
@layer components {
|
|
.twui-button-general {
|
|
@apply rounded-full;
|
|
}
|
|
|
|
.twui-section {
|
|
@apply py-20;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Overriding with utility classes
|
|
|
|
Put your own rules inside `@layer base` (element selectors like `h1`, `ul`) or `@layer components` (classes like `.twui-*`). Tailwind's utilities come in the later `utilities` layer, so a class passed to a component (`<Section className="py-4">`) overrides your stylesheet without `!important`.
|
|
|
|
CSS outside any `@layer` beats every layered rule regardless of specificity. A bare `.twui-section { @apply py-44; }` would therefore override `className="py-4"` on every `Section`, which is why these rules belong in a layer.
|
|
|
|
## Usage
|
|
|
|
Import from the root entry:
|
|
|
|
```tsx
|
|
import { Button, Stack, Row, useStatus } from "@moduletrace/twui";
|
|
```
|
|
|
|
Or use deep imports, which mirror the source folder layout:
|
|
|
|
```tsx
|
|
import Button from "@moduletrace/twui/layout/Button";
|
|
import Input from "@moduletrace/twui/form/Input";
|
|
import TinyMCEEditor from "@moduletrace/twui/editors/TinyMCE";
|
|
```
|
|
|
|
Editors, MDX, Next.js helpers, the AI blocks and `RemoteCodeBlock` are **deep-import only**, so the root entry never pulls in optional peers.
|
|
|
|
### AI chat
|
|
|
|
`elements/ai/AIChat` is a complete chat UI. Give it a height and a `send` function. `send` can return a string, an async iterable of chunks, a `ReadableStream`, or a fetch `Response`:
|
|
|
|
```tsx
|
|
import AIChat from "@moduletrace/twui/elements/ai/AIChat";
|
|
|
|
<AIChat
|
|
className="h-[80vh]"
|
|
history={{ storageKey: "my-app-chats" }}
|
|
models={["gpt-5", "claude-opus-5-5"]}
|
|
send={({ messages, model, signal }) =>
|
|
fetch("/api/chat", {
|
|
method: "POST",
|
|
body: JSON.stringify({ messages, model }),
|
|
signal,
|
|
})
|
|
}
|
|
/>;
|
|
```
|
|
|
|
| Feature | Props |
|
|
| --- | --- |
|
|
| Streaming replies, stop, regenerate, retry | built in |
|
|
| Saved conversations (sidebar, search, pin, rename, delete) | `history` (`true`, options, or a `useAIChatHistory()` result), `generateTitle` in the options |
|
|
| Read aloud (text-to-speech) | `speech` (browser voice by default, or `{ speak }` returning audio), `autoSpeak` |
|
|
| Voice input (speech-to-text) | `dictation` (browser recognition by default, or `{ transcribe }`) |
|
|
| Model picker | `models`, `model`, `onModelChange` |
|
|
| Edit messages, thumbs up/down, markdown export | `editable`, `feedback` / `onFeedback`, `exportable` |
|
|
| Empty state, avatars, custom rendering | `suggestions`, `emptyState`, `userImageUrl`, `renderMessageContent`, `renderMessageActions` |
|
|
|
|
To drive the chat from outside, call `useAIChat` (and `useAIChatHistory`) yourself and pass the results as `chat` and `history`. The pieces (`AIChatMessage`, `AIChatComposer`, `AIChatSidebar`, `useAIChatSpeech`, `useAIChatDictation`) are exported from `elements/ai/` for custom layouts.
|
|
|
|
Component modules are marked `"use client"`, so they work in the Next.js App Router.
|
|
|
|
## Development
|
|
|
|
```sh
|
|
bun install
|
|
bun run typecheck
|
|
bun test src
|
|
bun run compile # tsc → dist/, then scripts/post-compile.ts
|
|
```
|
|
|
|
### Playground
|
|
|
|
`dev/` is a local [bunext](https://git.tben.me/Moduletrace/bunext) app that imports the library straight from `src/` (through the committed `dev/twui` symlink and a tsconfig path alias), so component edits hot-reload without running `tsc`. It has its own `node_modules` and is excluded from the published package by the `files` whitelist.
|
|
|
|
```sh
|
|
cd dev
|
|
bun install
|
|
cd ..
|
|
bun run dev # http://localhost:7010
|
|
```
|
|
|
|
Install the library dependencies at the repo root first: the symlinked sources resolve `react`, `tailwind-merge`, `lucide-react` and the optional peers from the root `node_modules`.
|
|
|
|
Other useful scripts:
|
|
|
|
```sh
|
|
bun run dev:types # tsc --watch, for consumers working against dist/
|
|
bun run dev:typecheck # typecheck the playground pages and the sources they import
|
|
```
|
|
|
|
### Publishing
|
|
|
|
```sh
|
|
export GITBEN_NPM_TOKEN=...
|
|
./publish.sh "Commit message"
|
|
```
|
|
|
|
`publish.sh` typechecks, tests, compiles, commits, pushes, and runs `bun publish`. Bump `version` in `package.json` first.
|