Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,6 @@ Animation that keeps running, such as a terminal demo or a drawing that plays a

## Pictures of other programs

`browser` frames a picture of a web page and uses only the site's tokens. `terminal` shares its outer edge (`border`), corners and 2.5rem title bar, so a guide can show a terminal step and a browser step one after the other. `terminal` and `claude-code` draw another program's screen, so they keep that program's look instead of the page's: their own dark colors in both themes (`terminal-*` for the window and shell text, `claude-*` for Claude Code's screen, named and valued after Claude Code's dark theme), 1.25rem rows and two-cell East Asian characters. Use them only to show what a member will see in a terminal, such as a demo or an install guide, never as page chrome. Change what they say through props, not how they look; a color they need is a `terminal-*` or `claude-*` token, never a literal; check a new screen against a real `claude` run with an empty `CLAUDE_CONFIG_DIR`. Everything on a terminal screen is a character, rules (`─`), bars (`│`) and the Claude logo (`▐▛███▛█`) included: never draw them with borders or SVG. `terminal` fills block and box-drawing characters to the whole cell, the way a terminal does, so rows touch and the text still copies.
`browser` frames a picture of a web page and uses only the site's tokens. `terminal` shares its outer edge (`border`), corners and 2.5rem title bar, so a guide can show a terminal step and a browser step one after the other. `terminal` and `claude-code` draw another program's screen, so they keep that program's look instead of the page's: their own dark colors in both themes (`terminal-*` for the window and shell text, `claude-*` for Claude Code's screen, named and valued after Claude Code's dark theme), 1.25rem rows and two-cell East Asian characters. Use them only to show what a member will see in a terminal, such as a demo or an install guide, never as page chrome. Change what they say through props, not how they look; a color they need is a `terminal-*` or `claude-*` token, never a literal; check a new screen against a real `claude` run with an empty `CLAUDE_CONFIG_DIR`. Everything on a terminal screen is a character, rules (`─`), bars (`│`), round boxes (`╭╮╰╯`) and the Claude logo (`▐▛███▛█`) included: never draw them with borders or SVG. `terminal` fills block and box-drawing characters to the whole cell, the way a terminal does, so rows touch and the text still copies.

A service that signs other programs in, such as an MCP server, asks the member on a `consent` page in the spotlight layout: the title names the service, the client is named as the server received it, and the two buttons are 取消 (outline) and 允許 (primary). A service that renders HTML outside React copies the block's markup and these tokens into its own page, so every WinLab consent page looks the same.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ npx shadcn@latest add @winlab/button
| `focus` | `registry:block` | The one loud thing on a page, under its header: a label, a title with a highlight and a row of facts |
| `avatar-stack` | `registry:block` | Several people in the space of one; the avatars spread apart when pointed at |
| `countdown` | `registry:block` | Time left until a moment, to the minute, with rolling digits |
| `claude-code` | `registry:block` | Claude Code's screen in a `terminal` for demos and install guides: welcome, prompts, MCP calls, permission questions, the `/plugins` and `/mcp` menus; content through props; colors are `claude-*` tokens from Claude Code's dark theme |
| `claude-code` | `registry:block` | Claude Code's screen in a `terminal` for demos and install guides: welcome, prompts, MCP calls, permission questions, the `/plugins` menus (marketplaces, the plugin list with its search box, plugin details) and `/mcp`; content through props; colors are `claude-*` tokens from Claude Code's dark theme |
| `typing` | `registry:lib` | Timed keystrokes for typing animations: a steady pace (100ms a key, about 120 WPM) with jitter; Chinese typed as zhuyin that turns into the character on its tone key; `InputBox` shows the zhuyin with `composing` |
| `expand-row` | `registry:block` | A list row that opens in place to show its details |
| `badge` | `registry:ui` | Status label; variants `default`, `muted`, `outline`, `destructive`, `success`, `warning` |
Expand Down
26 changes: 26 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,32 @@
@utility cell-br {
--cell-br: currentColor;
}
@utility cell-arc {
background-image: radial-gradient(
ellipse 50% 50% at var(--cell-arc-at),
transparent calc(100% - 1.5px),
currentColor calc(100% - 1px),
currentColor 100%,
transparent calc(100% + 0.5px)
);
}

@utility cell-arc-tl {
--cell-arc-at: 100% 100%;
}

@utility cell-arc-tr {
--cell-arc-at: 0% 100%;
}

@utility cell-arc-bl {
--cell-arc-at: 100% 0%;
}

@utility cell-arc-br {
--cell-arc-at: 0% 0%;
}

@utility cell-hline {
background-image: linear-gradient(currentColor 0 0);
background-size: 100% 1px;
Expand Down
15 changes: 15 additions & 0 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,21 @@
"background-image": "linear-gradient(currentColor 0 0)",
"background-size": "1px 100%",
"background-position": "center"
},
"@utility cell-arc": {
"background-image": "radial-gradient(ellipse 50% 50% at var(--cell-arc-at), transparent calc(100% - 1.5px), currentColor calc(100% - 1px), currentColor 100%, transparent calc(100% + 0.5px))"
},
"@utility cell-arc-tl": {
"--cell-arc-at": "100% 100%"
},
"@utility cell-arc-tr": {
"--cell-arc-at": "0% 100%"
},
"@utility cell-arc-bl": {
"--cell-arc-at": "100% 0%"
},
"@utility cell-arc-br": {
"--cell-arc-at": "0% 0%"
}
}
},
Expand Down
99 changes: 99 additions & 0 deletions registry/winlab/blocks/claude-code/claude-code.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,102 @@ function Marketplace({
)
}

// A search field in a menu, such as the one above /plugins > Discover: a
// round box drawn with box characters, as wide as the screen.
function SearchBox({ placeholder = "Search…" }: { placeholder?: string }) {
const edge = (left: string, right: string) => (
<Line indent={2} className="flex whitespace-nowrap">
<T tone="inactive">{left}</T>
<T tone="inactive" className="min-w-0 flex-1 overflow-hidden">
{"─".repeat(240)}
</T>
<T tone="inactive">{right}</T>
</Line>
)
return (
<div className="pb-5">
{edge("╭", "╮")}
<Line indent={2} className="flex">
<T tone="inactive">│</T>
<T tone="inactive" className="min-w-0 flex-1">
{" ⌕ "}
{placeholder}
</T>
<T tone="inactive">│</T>
</Line>
{edge("╰", "╯")}
</div>
)
}

type PluginItem = {
name: string
description: string
/** The version the marketplace lists, such as "v0002". */
version: string
installed?: boolean
}

// Claude Code cuts a description to the cells it has and ends it with "…".
function cut(text: string, width: number) {
return text.length > width ? `${text.slice(0, width - 1)}…` : text
}

// The plugin list of /plugins > Discover: a count, then one row per plugin
// with its description cut to `width` cells and its version. `shown` rows
// are drawn; the rest sit under "more below".
function PluginList({
items,
choice,
shown = items.length,
width = 60,
}: {
items: PluginItem[]
choice: number
shown?: number
width?: number
}) {
return (
<>
<Line indent={2} className="pb-5">
<T bold>Install Plugins</T>{" "}
<T tone="inactive">
({choice + 1}/{items.length})
</T>
</Line>
{items.slice(0, shown).map((item, index) => {
const on = index === choice
return (
<div key={item.name} className="pb-5">
<Line indent={2}>
{on ? <T tone="permission">❯ </T> : " "}
{item.installed ? <T tone="success">✔ </T> : "○ "}
<T
tone={
item.installed ? "success" : on ? "permission" : undefined
}
>
{item.name}
</T>
{item.installed && <T tone="inactive"> (installed)</T>}
</Line>
<Line indent={4}>
<T tone="inactive">
{cut(item.description, width)} · {item.version}
</T>
</Line>
</div>
)
})}
{shown < items.length && (
<Line indent={2}>
<T tone="inactive">↓ more below</T>
</Line>
)}
</>
)
}

// A key: value row in a detail screen.
function Field({
name,
Expand Down Expand Up @@ -784,15 +880,18 @@ export {
Menu,
Notice,
Output,
PluginList,
PluginTabs,
Prompt,
Reply,
Rule,
Screen,
SearchBox,
Spinner,
ToolUse,
ClaudeText,
type ClaudeTone,
type PluginItem,
doneVerb,
spinnerVerb,
Welcome,
Expand Down
11 changes: 10 additions & 1 deletion registry/winlab/ui/terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,15 @@ const QUADRANTS: Record<string, string> = {
"▚": "1001",
"▞": "0110",
}
const DRAWN = /([─│▘▝▖▗▌▐▀▄█▛▜▙▟▚▞])/
const DRAWN = /([─│╭╮╰╯▘▝▖▗▌▐▀▄█▛▜▙▟▚▞])/
// A round corner is a quarter of an ellipse centred on the cell corner its
// two lines point away from.
const ARCS: Record<string, string> = {
"╭": "cell-arc-tl",
"╮": "cell-arc-tr",
"╰": "cell-arc-bl",
"╯": "cell-arc-br",
}

// One drawn cell: the character stays in the text for copying and screen
// readers; its glyph is hidden and the cell's background draws the shape in
Expand All @@ -80,6 +88,7 @@ function DrawnCell({ char }: { char: string }) {
"cell-drawn",
char === "─" && "cell-hline",
char === "│" && "cell-vline",
ARCS[char] && ["cell-arc", ARCS[char]],
quarters && [
"cell-quad",
...QUARTERS.filter((_, index) => quarters[index] === "1"),
Expand Down
Loading