Bỏ qua để đến nội dung

Thư viện phần tử giao diện

Mod vẽ giao diện của nó từ các element: text, box, nút bấm, ô nhập, và một vài element tự định dạng nội dung giúp bạn. Các mẫu trong trang này cho thấy code vẽ một element, và hầu hết đều kèm ảnh chụp kết quả trong một pane terminal, để bạn chọn element theo hình dáng của nó.

Để hiểu cách vẽ hoạt động, hãy bắt đầu với Vẽ giao diện bằng mod. Các prop chính và ứng dụng nào vẽ được từng element nằm ở phần tham chiếu element. Các file khai báo type liệt kê mọi prop.

Các mẫu trong trang này là đoạn code (snippet), không phải mod hoàn chỉnh. Mỗi mẫu là code cho một element và những gì lồng bên trong nó.

Để xem một mẫu trong terminal của bạn, hãy tạo mod nhỏ theo các bước dưới đây rồi dán mẫu vào. Mod thêm command /gallery để mở một pane và vẽ mẫu vào đó. Pane là một sidebar bên cạnh transcript khi terminal ở chế độ fullscreen đủ rộng, hoặc một vùng có khung phía trên ô prompt trong các trường hợp còn lại.

Tạo thư mục gallery với hai thư mục con .claude-plugin và hooks. Trang Tạo một mod giải thích các file.

Lưu manifest thành gallery/.claude-plugin/plugin.json:

gallery/.claude-plugin/plugin.json
{
"name": "gallery",
"version": "0.1.0",
"description": "Opens a pane that draws one sample",
"author": { "name": "Your Name" }
}

Khai báo entry point trong gallery/hooks/hooks.json:

gallery/hooks/hooks.json
{
"modules": ["./register.js"]
}

Lưu code thành gallery/hooks/register.js. Nó thêm command /gallery để mở một pane, và vẽ chữ Plain text trong pane đó:

gallery/hooks/register.js
// Đứng thay cho callback của bạn trong các mẫu cần callback
const noop = () => {}
// Mẫu Select lưu lựa chọn ở đây
let picked = 'md'
// Mẫu Raster đóng gói các ô bằng hàm này
const DEFAULT_COLOR = 0x01000000
function cellsOf(rows) {
const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])
return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()
}
export function register(on) {
on('session.start', async ($, e, next) => {
await $.command.register({ name: 'gallery', description: 'Open the sample pane' })
return next(e)
})
on('command.run', { command: 'gallery' }, async ($) => {
await $.ui.open({ id: 'gallery', focus: true, closeOnEscape: true })
return {}
})
on('ui.render', { component: 'Pane' }, async ($, e, next) => {
if (e.requestId !== 'gallery') return next(e)
const { Box, Text, Button, Input, Select, Link, Markdown, Code, Raster, Svg } = $.ui.resolve(e)
// Thay element phía sau return bằng một mẫu
return Text({ children: ['Plain text'] })
})
}

Trong shell, khởi động Claude Code từ thư mục chứa gallery:

Terminal window
claude --plugin-dir ./gallery

Tại prompt của Claude Code, chạy /gallery. Một pane mở ra với dòng Plain text bên trong.

Copy một mẫu trong trang này. Trong register.js, dán nó đè lên Text({ children: ['Plain text'] }), sao cho nó đứng sau return, rồi lưu file. Claude Code reload module mỗi khi bạn lưu, nên chạy lại /gallery để xem mẫu mới.

Các mẫu được nhóm theo thứ bạn muốn đưa lên màn hình:

Ba element đưa chữ lên màn hình: Text cho style do bạn tự đặt, Markdown cho nội dung đã được định dạng sẵn, và Link cho một URL.

Text vẽ một chuỗi với các style bạn cho nó. Mẫu này hiển thị mỗi style một dòng:

Box({
flexDirection: 'column',
children: [
Text({ children: ['Plain text'] }),
Text({ bold: true, children: ['bold'] }),
Text({ italic: true, children: ['italic'] }),
Text({ underline: true, children: ['underline'] }),
Text({ strikethrough: true, children: ['strikethrough'] }),
Text({ dimColor: true, children: ['dimColor'] }),
Text({ inverse: true, children: ['inverse'] }),
Text({ color: 'red', children: ["color: 'red'"] }),
Text({ backgroundColor: 'blue', children: ["backgroundColor: 'blue'"] }),
],
})
Một pane với chín dòng text, mỗi dòng mang tên style của nó: plain, bold, italic, underline, strikethrough, dimColor màu xám, inverse, color màu đỏ và backgroundColor màu xanh dương. Một pane với chín dòng text, mỗi dòng mang tên style của nó: plain, bold, italic, underline, strikethrough, dimColor màu xám, inverse, color màu đỏ và backgroundColor màu xanh dương.

dimColor vẽ text màu xám. backgroundColor chỉ tô nền vừa đúng độ rộng của text.

Markdown định dạng text giống cách câu trả lời của Claude được định dạng. Truyền nội dung qua text, không qua children:

Markdown({
text: '## Release notes\n\nThis build has **two** fixes and one `flag`:\n\n- Faster start\n- Fewer prompts\n\n> Quoted text',
})
Một pane với tiêu đề in đậm Release notes, rồi một câu có một từ in đậm và một từ code có màu, một danh sách hai mục, và một trích dẫn in nghiêng có thanh dọc bên trái. Một pane với tiêu đề in đậm Release notes, rồi một câu có một từ in đậm và một từ code có màu, một danh sách hai mục, và một trích dẫn in nghiêng có thanh dọc bên trái.

Tiêu đề được vẽ in đậm và không có dấu #. Inline code được vẽ có màu và không có dấu backtick. Trích dẫn được vẽ in nghiêng, có thanh dọc bên trái.

Link vẽ một nhãn, theo sau là URL của nó:

Link({ href: 'https://code.claude.com/docs', label: 'Claude Code docs' })
Một pane với một dòng: nhãn Claude Code docs, rồi URL màu xám. Một pane với một dòng: nhãn Claude Code docs, rồi URL màu xám.

Terminal vẽ URL dưới dạng text sau nhãn. Click vào có mở được link hay không tùy thuộc vào terminal của user.

Code vẽ mã nguồn với màu cú pháp của chính Claude Code, hoặc một diff.

Ghi rõ language, hoặc truyền một path để Claude Code tự suy ra ngôn ngữ. Với startLine, các dòng được đánh số bắt đầu từ số đó:

Code({
language: 'javascript',
startLine: 1,
source: "const name = 'mods'\nconsole.log('hello ' + name)",
})
Một pane với hai dòng JavaScript được đánh số, có màu cú pháp. Một pane với hai dòng JavaScript được đánh số, có màu cú pháp.

Màu sắc lấy theo theme của user.

Với format: 'diff', source là một hoặc nhiều hunk của unified diff:

Code({
format: 'diff',
source: '@@ -1,3 +1,3 @@\n # Mods\n-A mod is a plugin.\n+A mod is a plugin that runs code.\n Read on.',
})
Một pane với diff bốn dòng. Dòng bị xóa tô nền đỏ, dòng được thêm tô nền xanh lá, mỗi dòng kèm số dòng. Trong dòng được thêm, cụm từ that runs code được tô đậm hơn. Một pane với diff bốn dòng. Dòng bị xóa tô nền đỏ, dòng được thêm tô nền xanh lá, mỗi dòng kèm số dòng. Trong dòng được thêm, cụm từ that runs code được tô đậm hơn.

Claude Code vẽ số dòng thay cho dòng @@. Khi một dòng bị xóa và một dòng được thêm giống nhau, những từ thay đổi sẽ được tô đậm hơn.

Box bố trí các thứ bên trong nó thành một hàng hoặc một cột, và có thể vẽ viền. Mẫu này đặt một hàng chữ phía trên một box có viền:

Box({
flexDirection: 'column',
gap: 1,
children: [
Box({
flexDirection: 'row',
columnGap: 4,
children: [Text({ children: ['a row'] }), Text({ children: ['of three'] }), Text({ children: ['items'] })],
}),
Box({
borderStyle: 'round',
paddingX: 1,
children: [Text({ children: ["borderStyle: 'round'"] })],
}),
],
})
Một pane với ba cụm từ trên một hàng, cách nhau bốn cột, rồi một dòng trống, rồi một viền bo góc quanh một dòng text. Viền trải hết chiều rộng của pane. Một pane với ba cụm từ trên một hàng, cách nhau bốn cột, rồi một dòng trống, rồi một viền bo góc quanh một dòng text. Viền trải hết chiều rộng của pane.

Viền giãn ra hết chiều rộng của pane.

Button, Input và Select là các control: user di chuyển giữa chúng bằng Tab và dùng control đang có focus. Phần Keyboard focus và hotkey mô tả những phím nào tới được chúng.

Mở pane với focus: true sẽ trao keyboard focus cho pane. Chữ gõ vào chỉ tới được một Input khi nó có focus, nên hãy thêm autoFocus: true cho ô nhập cần nhận chữ ngay khi pane mở.

Một nút chạy onPress. Mẫu này cho thấy dạng mặc định, một nút plain có hotkey, và một nút mờ:

Box({
flexDirection: 'column',
children: [
Button({ key: 'save', label: 'Save', onPress: noop }),
Button({ key: 'next', label: 'Next', hotkey: 'n', plain: true, onPress: noop }),
Button({ key: 'skip', label: 'Skip', dimColor: true, onPress: noop }),
],
})
Một pane với ba nút, mỗi nút một dòng: Save trong ngoặc vuông, n: Next không có ngoặc và chữ n có màu, và Skip trong ngoặc vuông màu xám. Một pane với ba nút, mỗi nút một dòng: Save trong ngoặc vuông, n: Next không có ngoặc và chữ n có màu, và Skip trong ngoặc vuông màu xám.

Nút đang có focus được vẽ đảo màu (inverse video). Ở đây user đã nhấn Tab hai lần:

Ba nút như trên, trong đó nút thứ hai, n: Next, được vẽ đảo màu. Ba nút như trên, trong đó nút thứ hai, n: Next, được vẽ đảo màu.

Input là một ô nhập text một dòng, chạy onSubmit khi user nhấn Enter:

Input({
key: 'title',
label: 'Title',
placeholder: 'Type a title and press Enter',
value: '',
submitLabel: 'save',
onSubmit: noop,
})

Khi không có focus, ô nhập hiển thị nhãn và placeholder:

Một pane với một dòng: nhãn Title, rồi placeholder Type a title and press Enter màu xám. Một pane với một dòng: nhãn Title, rồi placeholder Type a title and press Enter màu xám.

Khi có focus, nhãn chuyển sang in đậm, con trỏ xuất hiện, và submitLabel hiện sau ⏎:

Ô nhập như trên với nhãn in đậm, con trỏ khối nằm ở chữ đầu tiên của placeholder, và ký hiệu return theo sau là chữ save. Ô nhập như trên với nhãn in đậm, con trỏ khối nằm ở chữ đầu tiên của placeholder, và ký hiệu return theo sau là chữ save.

Gõ chữ sẽ thay thế placeholder:

Ô nhập như trên đang chứa các chữ Rel vừa gõ, theo sau là ký hiệu return và chữ save. Ô nhập như trên đang chứa các chữ Rel vừa gõ, theo sau là ký hiệu return và chữ save.

Select cho user chọn một trong nhiều lựa chọn, và chạy onSelect với value của lựa chọn đó:

Select({
key: 'format',
label: 'Format',
value: picked,
options: [
{ value: 'md', label: 'Markdown' },
{ value: 'html', label: 'HTML' },
{ value: 'txt', label: 'Plain text' },
],
onSelect: (value) => {
picked = value
},
})

Khi đóng, nó hiển thị nhãn và lựa chọn hiện tại:

Một pane với một dòng: nhãn Format, lựa chọn hiện tại Markdown, và một mũi tên nhỏ hướng xuống. Một pane với một dòng: nhãn Format, lựa chọn hiện tại Markdown, và một mũi tên nhỏ hướng xuống.

Khi mở, nó liệt kê các lựa chọn và đánh dấu một lựa chọn:

Danh sách chọn đang mở, ba lựa chọn nằm dưới nhãn. Lựa chọn thứ hai, HTML, được vẽ đảo màu. Danh sách chọn đang mở, ba lựa chọn nằm dưới nhãn. Lựa chọn thứ hai, HTML, được vẽ đảo màu.

Sau khi user chọn, danh sách đóng lại:

Danh sách chọn đã đóng, giờ hiển thị HTML là lựa chọn hiện tại. Danh sách chọn đã đóng, giờ hiển thị HTML là lựa chọn hiện tại.

Raster là một lưới các ô ký tự có màu, dùng cho heat map, sparkline hay bàn cờ game. Terminal vẽ được nó. Mẫu này dùng hàm cellsOf trong module khởi đầu, hàm này đóng gói các ô thành chuỗi mà Raster nhận. Phần Vẽ lưới ô màu giải thích chi tiết:

Raster({
key: 'grid',
columns: 3,
rows: 2,
cells: cellsOf([
[['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],
[['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],
]),
})
Một pane với lưới nhỏ các khối màu, hai hàng ba cột: xanh lá, vàng hổ phách và đỏ, rồi xanh lá, xanh lá và vàng hổ phách. Một pane với lưới nhỏ các khối màu, hai hàng ba cột: xanh lá, vàng hổ phách và đỏ, rồi xanh lá, xanh lá và vàng hổ phách.

Raster làm tròn mỗi màu về một bảng màu nhỏ hơn, nên 0x2e7d32 được vẽ thành #337733.

Svg vẽ một tài liệu SVG trong ứng dụng Desktop:

Svg({
alt: 'Three bars of rising height',
width: 120,
height: 60,
source:
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 60"><rect x="10" y="40" width="20" height="20" fill="#2e7d32"/><rect x="50" y="25" width="20" height="35" fill="#f9a825"/><rect x="90" y="5" width="20" height="55" fill="#c62828"/></svg>',
})

Trong terminal, một pane chỉ trả về Svg sẽ mở ra trống trơn. Để vẽ thứ khác ở đó, hãy kiểm tra e.surface và trả về một cây khác.

Còn hai element nữa không có mẫu ở đây. Image vẽ ảnh PNG hoặc pixel thô trong terminal. Client là một vùng được vẽ bởi một file thứ hai của bạn, dùng cho animation và thao tác con trỏ. Phần tham chiếu element liệt kê props của chúng.

Các mẫu trên đều vẽ trong pane. Mod còn có thể vẽ ở những nơi khác, và gọi Claude Code hiển thị thông tin giúp nó:

Bài tiếp theo: Phản ứng với event - Quan sát, viết lại hoặc trả lời tool call, prompt và turn.