@qilin-agent/client-ui-primitives 0.0.0-stage → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.i18n.yaml +61 -0
- package/README.md +187 -2
- package/README.zh.md +188 -0
- package/lib/Button.module.css +79 -0
- package/lib/Checkbox.module.css +28 -0
- package/lib/CommandText.module.css +12 -0
- package/lib/ConnectionIndicator.module.css +123 -0
- package/lib/DiffBlock.module.css +109 -0
- package/lib/DisclosureRow.module.css +84 -0
- package/lib/FileTypeIcon.module.css +50 -0
- package/lib/HoverCard.module.css +70 -0
- package/lib/ImageLightbox.module.css +48 -0
- package/lib/ImagePreview.module.css +24 -0
- package/lib/Input.module.css +38 -0
- package/lib/JsonTree.module.css +345 -0
- package/lib/Menu.module.css +274 -0
- package/lib/MenuGroup.module.css +37 -0
- package/lib/MenuSurface.module.css +49 -0
- package/lib/Modal.module.css +103 -0
- package/lib/OnboardingSurface.module.css +29 -0
- package/lib/PathLabel.module.css +28 -0
- package/lib/Pill.module.css +27 -0
- package/lib/ReadBlock.module.css +111 -0
- package/lib/RiskConfirmation.module.css +73 -0
- package/lib/SearchBlock.module.css +112 -0
- package/lib/SegmentedControl.module.css +76 -0
- package/lib/ShortcutKeys.module.css +33 -0
- package/lib/StateDot.module.css +73 -0
- package/lib/Switch.module.css +50 -0
- package/lib/Tag.module.css +57 -0
- package/lib/TerminalBlock.module.css +205 -0
- package/lib/TextShimmer.module.css +88 -0
- package/lib/Toast.module.css +76 -0
- package/lib/Tooltip.module.css +52 -0
- package/lib/WebBlock.module.css +141 -0
- package/lib/index.js +10647 -0
- package/lib/markdown/CodeBlock.module.css +130 -0
- package/lib/markdown/JsonBlock.module.css +32 -0
- package/lib/markdown/MarkdownText.module.css +485 -0
- package/lib/markdown/mermaid.module.css +49 -0
- package/lib/settings-form/SettingsForm.module.css +53 -0
- package/lib/settings-form/fields.module.css +145 -0
- package/lib/types/Button.d.ts +18 -0
- package/lib/types/Checkbox.d.ts +20 -0
- package/lib/types/CodeFileIcon.d.ts +12 -0
- package/lib/types/CommandText.d.ts +16 -0
- package/lib/types/ConnectionIndicator.d.ts +26 -0
- package/lib/types/DiffBlock.d.ts +52 -0
- package/lib/types/DisclosureRow.d.ts +36 -0
- package/lib/types/FileTypeIcon.d.ts +43 -0
- package/lib/types/FoldToggle.d.ts +20 -0
- package/lib/types/HoverCard.d.ts +32 -0
- package/lib/types/ImageLightbox.d.ts +27 -0
- package/lib/types/ImagePreview.d.ts +12 -0
- package/lib/types/Input.d.ts +12 -0
- package/lib/types/JsonTree.d.ts +59 -0
- package/lib/types/LinkIcon.d.ts +49 -0
- package/lib/types/Menu.d.ts +133 -0
- package/lib/types/MenuGroup.d.ts +22 -0
- package/lib/types/MenuSurface.d.ts +16 -0
- package/lib/types/Modal.d.ts +44 -0
- package/lib/types/OnboardingSurface.d.ts +11 -0
- package/lib/types/PathLabel.d.ts +13 -0
- package/lib/types/Pill.d.ts +13 -0
- package/lib/types/QilinSeal.d.ts +18 -0
- package/lib/types/ReadBlock.d.ts +47 -0
- package/lib/types/ReferenceIcon.d.ts +16 -0
- package/lib/types/RiskConfirmation.d.ts +20 -0
- package/lib/types/SearchBlock.d.ts +72 -0
- package/lib/types/SegmentedControl.d.ts +35 -0
- package/lib/types/ShortcutKeys.d.ts +11 -0
- package/lib/types/SiteGlyph.d.ts +25 -0
- package/lib/types/StateDot.d.ts +18 -0
- package/lib/types/Switch.d.ts +20 -0
- package/lib/types/Tag.d.ts +32 -0
- package/lib/types/TerminalBlock.d.ts +68 -0
- package/lib/types/TextShimmer.d.ts +22 -0
- package/lib/types/Toast.d.ts +36 -0
- package/lib/types/Tooltip.d.ts +45 -0
- package/lib/types/WebBlock.d.ts +61 -0
- package/lib/types/ansi.d.ts +17 -0
- package/lib/types/clipboard.d.ts +9 -0
- package/lib/types/code-file-icon-artwork.d.ts +54 -0
- package/lib/types/code-file-types.d.ts +30 -0
- package/lib/types/darwin-desktop.d.ts +18 -0
- package/lib/types/file-size.d.ts +17 -0
- package/lib/types/focus.d.ts +9 -0
- package/lib/types/head-tail-cap.d.ts +23 -0
- package/lib/types/icons/index.d.ts +211 -0
- package/lib/types/icons/props.d.ts +9 -0
- package/lib/types/index.d.ts +92 -0
- package/lib/types/input-modality.d.ts +19 -0
- package/lib/types/keyboard-composition.d.ts +11 -0
- package/lib/types/markdown/CodeBlock.d.ts +30 -0
- package/lib/types/markdown/JsonBlock.d.ts +8 -0
- package/lib/types/markdown/MarkdownDelegate.d.ts +46 -0
- package/lib/types/markdown/MarkdownText.d.ts +54 -0
- package/lib/types/markdown/cjkFriendlyStrong.d.ts +9 -0
- package/lib/types/markdown/file-link.d.ts +13 -0
- package/lib/types/markdown/highlight.d.ts +142 -0
- package/lib/types/markdown/incremental.d.ts +80 -0
- package/lib/types/markdown/katex.d.ts +27 -0
- package/lib/types/markdown/local-image-syntax.d.ts +10 -0
- package/lib/types/markdown/mathCompatibility.d.ts +11 -0
- package/lib/types/markdown/mermaid.d.ts +39 -0
- package/lib/types/markdown/parse.d.ts +25 -0
- package/lib/types/markdown/plain-text.d.ts +22 -0
- package/lib/types/markdown/render.d.ts +158 -0
- package/lib/types/markdown/useViewportHighlighting.d.ts +11 -0
- package/lib/types/overlay-top-margin.d.ts +8 -0
- package/lib/types/pointer-grace.d.ts +22 -0
- package/lib/types/rank-by-name.d.ts +22 -0
- package/lib/types/relative-time.d.ts +23 -0
- package/lib/types/seal-geometry.d.ts +27 -0
- package/lib/types/seal-glyphs.d.ts +9 -0
- package/lib/types/settings-form/SettingsForm.d.ts +46 -0
- package/lib/types/settings-form/fields.d.ts +70 -0
- package/lib/types/settings-form/form-model.d.ts +214 -0
- package/lib/types/use-copy-feedback.d.ts +14 -0
- package/lib/types/useAnchoredMaxHeight.d.ts +11 -0
- package/lib/types/useAnchoredPosition.d.ts +34 -0
- package/lib/types/useDismissOnOutsidePointer.d.ts +11 -0
- package/lib/types/useModalLayer.d.ts +27 -0
- package/lib/types/user-text.d.ts +37 -0
- package/lib/user-text.module.css +43 -0
- package/package.json +63 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kkutysllb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/client/ui-primitives/README.md
|
|
5
|
+
/:
|
|
6
|
+
en: b6701ad9280c43fb
|
|
7
|
+
zh: bc1ed7854daac92e
|
|
8
|
+
/qilin-agent-client-ui-primitives:
|
|
9
|
+
en: 77c8b257195e8d1c
|
|
10
|
+
zh: e14d896c8f1374c2
|
|
11
|
+
/qilin-agent-client-ui-primitives/summary:
|
|
12
|
+
en: cb45feeda688bec7
|
|
13
|
+
zh: bf5480fcaafc640e
|
|
14
|
+
/qilin-agent-client-ui-primitives/table-of-contents:
|
|
15
|
+
en: d152484eb41ac6b4
|
|
16
|
+
zh: 09388d293f9be9cb
|
|
17
|
+
/qilin-agent-client-ui-primitives/use-this-package:
|
|
18
|
+
en: ca3db95551971b07
|
|
19
|
+
zh: 083ba0e187ce544a
|
|
20
|
+
/qilin-agent-client-ui-primitives/use-this-package/component-catalog:
|
|
21
|
+
en: 070f2305cb51f06d
|
|
22
|
+
zh: a5cbfdded55ec641
|
|
23
|
+
/qilin-agent-client-ui-primitives/use-this-package/controls-and-icons:
|
|
24
|
+
en: 219d402939f9c314
|
|
25
|
+
zh: 3460e90d2e1c46df
|
|
26
|
+
/qilin-agent-client-ui-primitives/use-this-package/rendering-agent-output:
|
|
27
|
+
en: 7aac8d4af442ee84
|
|
28
|
+
zh: 484000023a699d59
|
|
29
|
+
/qilin-agent-client-ui-primitives/use-this-package/localizing-copy:
|
|
30
|
+
en: 4aef65dea11de083
|
|
31
|
+
zh: abc22cd7c65cdf38
|
|
32
|
+
/qilin-agent-client-ui-primitives/understand-the-implementation:
|
|
33
|
+
en: ff71d06c5d21ac81
|
|
34
|
+
zh: f5739d0e28e73552
|
|
35
|
+
/qilin-agent-client-ui-primitives/understand-the-implementation/source-map:
|
|
36
|
+
en: 5b8a88c0706a7c1c
|
|
37
|
+
zh: a3dcbfabffe4e5ec
|
|
38
|
+
/qilin-agent-client-ui-primitives/understand-the-implementation/input-modality:
|
|
39
|
+
en: 44dfc06332ca3b5b
|
|
40
|
+
zh: 94df10cde37c18e6
|
|
41
|
+
/qilin-agent-client-ui-primitives/understand-the-implementation/streaming-markdown:
|
|
42
|
+
en: 8bbfa14c27f3bfd7
|
|
43
|
+
zh: 88f7548ac3a114e1
|
|
44
|
+
/qilin-agent-client-ui-primitives/understand-the-implementation/geometry-and-overflow:
|
|
45
|
+
en: 64cddabf7d398df4
|
|
46
|
+
zh: 0aad27f39486db06
|
|
47
|
+
/qilin-agent-client-ui-primitives/further-exploration:
|
|
48
|
+
en: 1ef5e5fdbe3d2436
|
|
49
|
+
zh: af5cb7ace2ab6e64
|
|
50
|
+
/qilin-agent-client-ui-primitives/model-experience:
|
|
51
|
+
en: 3120df665299c634
|
|
52
|
+
zh: 1d9f365d091d0d2a
|
|
53
|
+
/qilin-agent-client-ui-primitives/model-experience/kv-cache-effect:
|
|
54
|
+
en: ca75c51c89c2c9b0
|
|
55
|
+
zh: de54f3467ef3b9be
|
|
56
|
+
/qilin-agent-client-ui-primitives/known-limitations-and-deferred-work:
|
|
57
|
+
en: de2ce8b129329ea0
|
|
58
|
+
zh: 53465e4a8255748a
|
|
59
|
+
/qilin-agent-client-ui-primitives/known-limitations-and-deferred-work/dev-note:
|
|
60
|
+
en: e67f633dc6fe7773
|
|
61
|
+
zh: 3bb0c20c93f16f98
|
package/README.md
CHANGED
|
@@ -1,3 +1,188 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "Shared React UI atoms for the qilin web client: controls, icons, markdown and math rendering, and the terminal/read/diff/search/web output cards (zero Kylin)."
|
|
3
|
+
kind: "package-library"
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# @qilin-agent/client-ui-primitives
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
Use `qilin-client-ui-primitives` to build web-client controls and render agent output with shared React UI. It includes standard controls, icons, anchored overlays, and renderers for Markdown with TeX, terminal output, file reads, diffs, search, web retrieval, and JSON. The renderers handle untrusted model output by keeping raw HTML literal unless the owner supplies its own sanitizing renderer, restricting links, and parsing ANSI escape sequences. The components import no Kylin runtime; callers supply localized labels, and theme-facing colors use `--qilin-*` design tokens.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
This package is a Web-shell build input. Its static ESM retains third-party imports and styles for Vite; independent consumers supply its development dependencies ([dependency rules](../AGENTS.md#dependency-declaration)).
|
|
29
|
+
|
|
30
|
+
Compose feature UI from these atoms whenever the web client needs a standard control or an agent-output renderer. They render through React only and take `--qilin-*` design tokens from the theme, so they fit any plugin without importing the theme or the slot system.
|
|
31
|
+
|
|
32
|
+
<a id="component-catalog"></a>
|
|
33
|
+
### Component catalog
|
|
34
|
+
|
|
35
|
+
Check this table before writing a control in a feature package. A plugin cannot import another plugin's component, so this package is the only place a control can be shared: reuse what fits, and lift a deliberate visual difference into a prop rather than starting a second copy.
|
|
36
|
+
|
|
37
|
+
| Export | What it is |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `Button` | Clickable action; `variant` selects `primary`, `ghost`, `outline`, or `toolbar`. |
|
|
40
|
+
| `Switch` | Two-state toggle, 36×20. `label` is required, so the control cannot ship unnamed. |
|
|
41
|
+
| `SegmentedControl` | Tablist of two or more equal-width segments with one sliding indicator, for switching a card or panel between a few modes; the owner holds the selection and `label` names the list. `id` seeds each tab's id (`<id>-<value>`) and the panel it controls (`<id>-<value>-panel`), which the owner renders and points back at the tab with `aria-labelledby`; a segment may be `disabled` with a `title`, and `disabled` on the control locks every segment while the shown panel has work in flight. |
|
|
42
|
+
| `Checkbox` | Labeled native checkbox with controlled state, keyboard interaction, and disabled styling; the caller supplies localized `label` text. |
|
|
43
|
+
| `Input` | Single-line text entry for search boxes and inline forms. |
|
|
44
|
+
| `Menu` | Dropdown of items, separators, and group labels, with nested submenus. While open, ↑/↓ (with Home and End) walk the list, Tab settles the focused row, and Escape or Shift+Tab close back to the anchor; selecting a row also returns the keyboard to the anchor unless the owner moved it itself. Only a keyboard on the anchor or inside the list is intercepted, and `autoFocus` decides solely whether opening focuses the first row. |
|
|
45
|
+
| `Pill` | Selectable capsule button for view switchers and filters; takes `active` and `onClick`. |
|
|
46
|
+
| `Tag` | Read-only capsule badge; `tone` selects one of eight palettes. |
|
|
47
|
+
| `PathLabel` | Single-line file path with subdued directories, a primary filename, and the full path on hover. Fitting paths align left; clipped paths preserve their suffix with a left-edge fade that updates on path and size changes. |
|
|
48
|
+
| `StateDot` | Status mark: `done`, `warning`, `ongoing`, `error`, or `idle`. `aria-hidden`, so the render site owns the name. |
|
|
49
|
+
| `ConnectionIndicator` | Inline connection-recovery control across outage, retry, and recovered states. |
|
|
50
|
+
| `DisclosureRow` | 24px compact disclosure that lays title and content side by side. |
|
|
51
|
+
| `CommandText` | One command line, gaining a Tab stop, a group role, and its accessible name only while it overflows horizontally. |
|
|
52
|
+
| `Modal` | Centered dialog over a page mask. |
|
|
53
|
+
| `RiskConfirmation` | Sensitive action gated behind an explicit checkbox. |
|
|
54
|
+
| `OnboardingSurface` | First-run stage that holds the application root inert. |
|
|
55
|
+
| `Tooltip` | Hover text on a cloned anchor, placed right, bottom, or top. |
|
|
56
|
+
| `HoverCard` | Hover preview the pointer can rest on and select from; optional copy button. |
|
|
57
|
+
| `ImageLightbox` | Shared image modal with focus restoration and Escape dismissal. |
|
|
58
|
+
| `Toast` | Transient top-center banner held for the owner's `holdMs`. |
|
|
59
|
+
| `JsonTree`, `JsonBlock` | Read-only JSON inspection. |
|
|
60
|
+
| `MarkdownText`, `MarkdownDelegateProvider`, `CodeBlock` | Untrusted GFM with TeX math, Mermaid diagrams, owner-delegated HTTP(S) navigation, and highlighted code. `CodeBlock` accepts opt-in `lineNumbers`; copied source excludes the gutter, and `contentRef` exposes its stable source wrapper to an owner that uses it as a scrollport. Set `showHeader={false}` when the owner supplies its own language and copy toolbar. |
|
|
61
|
+
| `TerminalBlock`, `ReadBlock`, `DiffBlock`, `SearchBlock`, `WebBlock` | The agent-output card matching each tool-result intent. |
|
|
62
|
+
| `SettingsForm`, `SettingsValueField`, `SettingsSecretField` | Staged configuration form over `SettingsFormModel`: fields report what the reader typed, and its save is the single point where a draft becomes a document mutation. |
|
|
63
|
+
| `icons/*`, `QilinSeal`, `ReferenceIcon`, `LinkIcon` | Glyphs and brand marks. Use `LinkIcon` for 14px clickable-link categories and known-site marks. |
|
|
64
|
+
| `FileTypeIcon`, `classifyFileType`, `fileExtension` | A category-colored 28px file or folder glyph and the shared case-insensitive filename mapping behind it. Code and configuration files use detailed full-color technology glyphs; use `LinkIcon` for link-leading glyphs and image previews for image content. |
|
|
65
|
+
|
|
66
|
+
Four pairs are easy to confuse:
|
|
67
|
+
|
|
68
|
+
- **`Tag` against `Pill`.** Reach for `Tag` for a read-only badge at the 11px capsule size, and for `Pill` when the capsule is selectable (`active` and `onClick`, as view switchers and filters use) or when it must sit on a 24px text line — `TerminalBlock` renders its exit status as a static `Pill` for exactly that reason. Size decides as much as interactivity here; the two are not interchangeable.
|
|
69
|
+
- **`Pill` against `SegmentedControl`.** A row of `Pill`s is a set of independent chips — each one toggles on its own, and several may be active. `SegmentedControl` is one choice among a few mutually exclusive modes, drawn as a tablist with one indicator, and it comes with the tab keyboard pattern (arrow keys walk the segments, only the selected one is in the tab sequence).
|
|
70
|
+
- **`DisclosureRow` against a card.** The row lays its title and content side by side at a fixed 24px. A card that stacks a name over a description is a different layout, and belongs in the feature package — `ui-settings-plugins`' `PluginCard` is the precedent and records why.
|
|
71
|
+
- **`FoldToggle` against the exported surface.** It is package-internal and not exported; the output cards use it for their head-tail fold.
|
|
72
|
+
|
|
73
|
+
Writing your own component in your own package is fine when the need is genuinely specific. What is not fine is copying a control that already exists here — and once a second package needs the same control, it belongs in this package ([decision](../../../.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.md)).
|
|
74
|
+
|
|
75
|
+
### Controls and icons
|
|
76
|
+
|
|
77
|
+
The catalog above lists what each export is for; this section covers the behavior that props alone do not show. The `ic_ds_*` icon set fills the inline-icon slots, and the `QilinSeal` stamp is the brand mark. `FileTypeIcon` renders the traditional 28px Excel, folder, HTML, image, Markdown, generic, PDF, PPT, video, and Word glyphs, and uses the imported square technology artwork for the established 48 code and configuration categories. The import replaces artwork only: archive-only categories do not extend `CodeFileType`. `classifyFileType` applies exact filename, prefix, suffix, optional project-context, and extension rules in that order; React names win over TypeScript/JavaScript, Angular suffixes win over their base extension, and a Dart file becomes Flutter only when the supplied project files contain a `pubspec.yaml` with `flutter:`. Markdown and SVG remain traditional Markdown and image files. Office mappings include XLSM/Numbers as Excel, KEY as slides, and RTF/ODT/Pages as documents. `fileExtension` exposes the same basename and final-dot parsing for adjacent metadata labels. Traditional glyphs use a solid category-colored sheet with a white mark and translucent white corner; the generic file uses a grey sheet and darker grey corner. Callers may override the sheet color through `--qilin-file-type-icon-color`. The full-color technology artwork is the deliberate exception and retains its embedded palette. All glyphs are decorative and carry no label. `LinkIcon` remains the smaller leading glyph for clickable artifact links — globe, folder, code, image, document, or plain paper, and for a `url` link the mark of a well-known site named by its `href` — the developer sites a transcript usually cites (GitHub, GitLab, npm, PyPI, Stack Overflow, MDN, Wikipedia, Hacker News, YouTube, X, Bilibili, Zhihu, Juejin, CSDN) and mainstream search, video, social, shopping, and reference sites (Google, Baidu, DuckDuckGo, TikTok, Netflix, Spotify, Facebook, Instagram, Reddit, Telegram, WhatsApp, WeChat, QQ, Weibo, Taobao, AliExpress, eBay, Quora, V2EX, Apple) — while `classifyLinkPath` folds the shared file types into that existing six-category vocabulary. `ConnectionIndicator` renders a warning-colored disconnected action whose permanent retry glyph marks the retry action beside the owner-supplied outage label, a connecting spinner beside a label whose one-to-three dots advance every 500ms independently of retry timing, or a success-colored recovered status. Clicking either warning state requests an immediate reconnect; no hover interaction changes the copy. The pill fades in on appearance, fades out for 150ms before unmounting, and sizes to its current label. Its owner supplies visibility, the recovery hold, localized labels, and the immediate-reconnect callback; the primitive uses no native title tooltip. `useAnchoredPosition` and `useAnchoredMaxHeight` keep floating panels and bottom-anchored overlays clamped to the viewport and following their anchor. `HoverCard` keeps its portaled preview reachable across the anchor gap and can expose a copy button through the `copyText` prop. `Toast` holds for the window its owner names through `holdMs`, because how long a banner has to stay depends on how much there is to read; the same value drives its unmount timer and the stylesheet's fade delay, so the two cannot disagree. `rankByName` is the `/` menu's shared candidate ranker for the command and skill sources: the query must be a case-insensitive ordered subsequence of the name; prefix hits rank first, then alignment score, then source order. `Menu.autoFocus` focuses its first enabled item, supports Arrow Up/Down and Home/End navigation, and focuses the first button in the anchor on Escape; it is opt-in for action menus.
|
|
78
|
+
|
|
79
|
+
### Rendering agent output
|
|
80
|
+
|
|
81
|
+
The nearest `MarkdownDelegateProvider` supplies optional `openExternalLink` and `openFile` navigation callbacks. Nested providers replace the enclosing capabilities, and callback changes reach already-rendered links without rebuilding Markdown. Its `openFile` makes local Markdown links clickable after settlement. Absolute and workspace-relative paths support percent escapes and `#L24` / `#L24-L30` fragments; ranges open at their first line. Literal `?` and `#` in filenames must be percent-encoded. The tooltip uses the decoded path and supplies the accessible name when the label is empty. The callback receives the decoded path and optional line, while the renderer preserves the label and displays a file icon. Without a callback, local links remain text. URL schemes, queries, unsupported fragments, and malformed destinations never reach the file opener.
|
|
82
|
+
|
|
83
|
+
`MarkdownText` renders untrusted GFM and TeX math, blocks unsafe links and images, and can turn resolved file mentions into explicit controls. A surrounding `MarkdownDelegateProvider` receives sanitized HTTP(S) URLs from ordinary clicks; modified clicks and links outside a provider retain native external-anchor behavior. When the owner passes a `pathImages` vocabulary, image destinations that are local media paths rewrite to displayable URLs on settled renders only (the same streaming gate as file mentions); without a vocabulary, local destinations remain inert alt text. A load or decode failure replaces the image with its authored alt text, or the original destination when alt is empty; `fileImages` additionally supplies a localized failure prefix. Changing the image source permits a fresh load. While a reply streams, it freezes completed blocks, advances a top-level open fence by completed lines, and highlights that fence from saved Shiki grammar state. Completed token lines enter fixed-size React groups, so later chunks reconcile only the growing group; an unchanged fence retains that DOM when the final full parse resolves cross-document syntax. `TerminalBlock`, `ReadBlock`, `DiffBlock`, `SearchBlock`, and `WebBlock` render the matching tool-result intent with copy controls, overflow handling, and ANSI processing where applicable. `JsonTree` and `JsonBlock` inspect JSON values read-only, while `projectUserText` projects sent user text into inline plain runs and reference chips for the message bubble and queue rows. When supplied with `UserTextReferences`, file and skill references become keyboard-accessible preview buttons using the same hover and focus styling as prose file links; the first pointer click can open a preview, while subsequent clicks and existing text selections retain native selection handling. Keyboard activation opens previews even when text is selected. A ```` ```mermaid ```` fence renders through the lazily imported library in strict security mode; the sanitized SVG keeps geometry, text, gradients, and classes while scripts, event handlers, `foreignObject`, and `javascript:` URLs are stripped, and a click or Enter/Space opens the enlarged diagram in a modal whose overlay click dismisses it. A rejected diagram shows the first error lines above the fenced source with its copy control.
|
|
84
|
+
|
|
85
|
+
`MarkdownText` defaults to `variant="body"`. Use `variant="compact"` for secondary content: its 13px text and 20px line height follow the content-size setting, all heading levels use the same size with weight 600, and paragraphs and lists use tighter spacing. Text, links, and code keep the tertiary color; dotted underlines distinguish links. Code headers scroll with their blocks. Tables and math stay enabled at the surrounding text size and scroll horizontally within the available width. Both variants share the parser and streaming cache.
|
|
86
|
+
|
|
87
|
+
`DiffBlock` compares the old and new content by line. It shows actual additions and deletions with up to three neutral context lines on each side, separates distant changes with `⋯`, and excludes shared context from both summary and footer totals. Search stops beyond 256 line additions/deletions per fragment; those fragments display and count the complete old and new contents as a coarse replacement, including shared lines. Copy includes the full displayed diff with its prefixes. A final newline is treated as a terminator; differences only in the presence of a final newline are not displayed.
|
|
88
|
+
|
|
89
|
+
`JsonTree` clamps collapsed strings to `collapsedStringLines` (three by default). Expanded strings show raw text, retain sibling commas, and fit within the window and outer scrolling containers. Resize and ancestor-scroll events update that limit. Row copy feedback updates independently of JSON value rendering; pending clipboard writes cannot update a different row or an unmounted tree.
|
|
90
|
+
|
|
91
|
+
`ImageLightbox` is the shared original-image modal with focus restoration and Escape dismissal. The internal thumbnail renderer receives loading/failure labels from its owner. `HoverCard.inline` keeps a file link in the text flow and uses the shared menu material, keyboard-visible focus, and placement above or below the anchor without covering it. Escape closes an open thumbnail even when focus is elsewhere; subsequent Escape presses reach the owner. `MarkdownDelegateProvider.fileImages` supplies a decoded-path resolver and complete image labels: settled image links gain hover previews, and standalone images gain lightbox activation. Image-only anchors retain their single navigation target. The parser recovers only complete, unescaped, standalone local image references with bare spaces and an unambiguous supported image suffix; code and ambiguous destinations remain literal.
|
|
92
|
+
|
|
93
|
+
### Localizing copy
|
|
94
|
+
|
|
95
|
+
The atoms cannot read the application locale, so every piece of user-facing copy arrives through required label props. `HoverCard`, `TerminalBlock`, `JsonTree`, `CodeBlock`, `MarkdownText`, `JsonBlock`, `ConnectionIndicator`, `Modal`, `DiffBlock`, `ReadBlock`, `SearchBlock`, and `WebBlock` accept complete localized labels. The package owns no language fallback; omission fails typechecking, and each feature maps its typed `t` seat into the primitive's label interface.
|
|
96
|
+
|
|
97
|
+
-----
|
|
98
|
+
|
|
99
|
+
<a id="understand-the-implementation"></a>
|
|
100
|
+
## Understand the implementation
|
|
101
|
+
|
|
102
|
+
<details>
|
|
103
|
+
<summary>Implementation internals — click to expand</summary>
|
|
104
|
+
|
|
105
|
+
The package enforces one separation: presentational React atoms with zero Kylin and zero slot knowledge, styled only through `--qilin-*` tokens, while every feature-specific concern (locale, session data, composition) stays in the composing plugin.
|
|
106
|
+
|
|
107
|
+
### Source map
|
|
108
|
+
|
|
109
|
+
| File | Role |
|
|
110
|
+
|---|---|
|
|
111
|
+
| [`src/index.ts`](src/index.ts) | Public atom exports |
|
|
112
|
+
| [`src/markdown/`](src/markdown/) | Markdown and math pipeline: micromark parsing, KaTeX typesetting, incremental streaming renderer, `CodeBlock`/`JsonBlock` |
|
|
113
|
+
| [`src/TerminalBlock.tsx`](src/TerminalBlock.tsx) | ANSI escape parsing (`anser`) and terminal card rendering |
|
|
114
|
+
| [`src/CommandText.tsx`](src/CommandText.tsx) | One command line's horizontal scrollport and its overflow-only accessible name |
|
|
115
|
+
| [`src/settings-form/`](src/settings-form/) | Staged configuration form, its field controls, and the draft/write model behind them |
|
|
116
|
+
| [`src/ReadBlock.tsx`](src/ReadBlock.tsx) / [`src/DiffBlock.tsx`](src/DiffBlock.tsx) | Read and diff cards |
|
|
117
|
+
| [`src/SearchBlock.tsx`](src/SearchBlock.tsx) / [`src/WebBlock.tsx`](src/WebBlock.tsx) | Search and web-retrieval cards |
|
|
118
|
+
| [`src/icons/`](src/icons/) | `ic_ds_*` glyph components and brand marks |
|
|
119
|
+
| [`src/useAnchoredPosition.ts`](src/useAnchoredPosition.ts) / [`src/useAnchoredMaxHeight.ts`](src/useAnchoredMaxHeight.ts) | Floating-panel and overlay geometry hooks |
|
|
120
|
+
| [`src/input-modality.ts`](src/input-modality.ts) | Document-wide input modality published on `<html>` |
|
|
121
|
+
|
|
122
|
+
<a id="input-modality"></a>
|
|
123
|
+
### Input modality
|
|
124
|
+
|
|
125
|
+
[`input-modality.ts`](src/input-modality.ts) tracks input for tooltips and publishes `data-input-modality` on `<html>` for the [theme's focus styles](../ui-theme/README.md#understand-the-implementation). `pointerModality()` is true after pointer input and false after any key, including IME composition keys; `Tooltip` uses it to decide whether focus may show a bubble. The published attribute stays `pointer` until a non-composing navigation key (Tab, arrows, Home/End, PageUp/PageDown), or focus on a different control after a non-composing key. Refocusing the same control does not restore keyboard modality. Pointer input, an IME composition key, and window blur each clear the pending key; focus changes without a pending key leave the modality unchanged. The listeners live for the document lifetime; Node imports install none. The focus-change rule observes events exposed to window; components own additional navigation inside shadow roots that do not expose those events.
|
|
126
|
+
|
|
127
|
+
### Streaming markdown
|
|
128
|
+
|
|
129
|
+
While a reply streams, `MarkdownText` parses incrementally: all but the trailing two blocks freeze as cached React elements and only the source tail re-parses per chunk, so per-chunk work tracks the tail instead of the whole reply. A final unclosed top-level fence keeps its parsed code node and sends only the last completed line plus the current partial line through the same GFM grammar; a closing fence or ambiguous parse returns to the ordinary tail path. Highlighting likewise resumes from saved Shiki grammar state and publishes only newly completed lines plus the mutable tail. `CodeBlock` seals completed lines into fixed-size React groups, reuses earlier groups, and retains the whole highlighted tree across settlement when code and language are unchanged. The settled full parse still resolves references that crossed the freeze boundary.
|
|
130
|
+
|
|
131
|
+
### Geometry and overflow
|
|
132
|
+
|
|
133
|
+
The output cards share one geometry model: `white-space: pre` with horizontal scrolling so column-aligned content keeps its alignment, and a head-plus-tail slice behind an expand button past `maxLines` (default 16) so a long body never stretches the card. `TerminalBlock` uses `CommandText` to keep each authored command line selectable and horizontally scrollable; the prompt and status stay fixed. Only horizontally overflowing text enters the Tab order as a named group, using the owner's localized `commandLine(line)` label. `TerminalBlock` parses ANSI into React spans with a per-line column buffer for cursor movement, honoring erase-in-line, tab stops, and character width.
|
|
134
|
+
|
|
135
|
+
</details>
|
|
136
|
+
|
|
137
|
+
-----
|
|
138
|
+
|
|
139
|
+
<a id="further-exploration"></a>
|
|
140
|
+
## Further Exploration
|
|
141
|
+
|
|
142
|
+
These pages place the atoms in the client stack and the design system.
|
|
143
|
+
|
|
144
|
+
- [ui-renderer](../ui-renderer/README.md) — the React renderer that mounts the assembled application and binds slot data.
|
|
145
|
+
- [ui-tool](../ui-tool/README.md) — the tool-call presentation layer that composes these output cards.
|
|
146
|
+
- [ui-conversation](../ui-conversation/README.md) — the chat surface that renders markdown replies and tool cards.
|
|
147
|
+
- [ui-theme](../ui-theme/README.md) — the `--qilin-*` token system these atoms style through.
|
|
148
|
+
- [Web styling](../../../docs/web-styling.md) — the authoritative styling rules for web client components.
|
|
149
|
+
|
|
150
|
+
-----
|
|
151
|
+
|
|
152
|
+
<a id="model-experience"></a>
|
|
153
|
+
## Model Experience
|
|
154
|
+
|
|
155
|
+
None, as the package is a browser-side UI plugin layer that registers nothing model-facing.
|
|
156
|
+
|
|
157
|
+
#### KV Cache effect
|
|
158
|
+
|
|
159
|
+
None; this package neither assembles nor sends a provider request.
|
|
160
|
+
|
|
161
|
+
## Known Limitations and Deferred Work
|
|
162
|
+
|
|
163
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
These limits define how the atoms behave at the edges; they are current package constraints, not a component roadmap.
|
|
167
|
+
|
|
168
|
+
- **Diff search is bounded, input processing is linear** — the edit-distance limit trades precise alignment for a coarse replacement on heavily changed fragments. Normalization, fallback rows, and copied output still scale with input size; the height cap limits visible rows, not those allocations.
|
|
169
|
+
- **Known-site marks are a fixed list** — only the named hosts resolve to their own mark, and every other external host keeps the globe; recognizing an arbitrary site would require fetching its icon over the network.
|
|
170
|
+
- **Streaming defers cross-boundary reference resolution** — a reference-style link or footnote whose definition sits on the other side of the incremental freeze boundary renders as literal text while the reply streams; the settled full parse at finalize resolves it.
|
|
171
|
+
- **Overlong lines stay uncolored** — a source line of 1,000 or more UTF-16 units renders as one plain run in every highlighted surface, because TextMate scanning cost grows superlinearly with line length and a single generated line can otherwise block the page for seconds. The grammar state skips that line, so a comment or template literal that opens or closes on it mis-colors the following line.
|
|
172
|
+
|
|
173
|
+
- **A long highlighted fence retains its complete token DOM** — streaming avoids re-parsing, re-tokenizing, and reconciling the completed prefix, but it does not discard old colors or virtualize token spans. Final DOM cardinality therefore still follows the fence's token count; nested/container fences and a pathological single long line remain on the general tail path.
|
|
174
|
+
- **Glyph-level icons are redrawn approximations** — the fish logo and the sparkle mark come from font glyphs whose vector geometry is not exportable from the local design data; hand-authored recreations stand in until an exact export path exists.
|
|
175
|
+
- **`Pill` and `Input` have no design source** — both atoms are self-defined; the sidebar search field and view-tab strip that resemble them are consumer-owned compositions, not these atoms.
|
|
176
|
+
- **No `Active` `StateDot` variant** — the supported states are done, warning, ongoing, error, and idle.
|
|
177
|
+
- **User-facing copy is required at the render site** — the atoms are zero-Kylin and cannot reach `ctx.locale`; each feature must supply complete localized labels through the primitive's typed props ([decision](../../../.agents/notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.md)).
|
|
178
|
+
- **`TerminalBlock` is not a terminal emulator** — it renders settled or still-running command output, not an interactive session: SGR colors, carriage return, backspace, erase-in-line, tab stops, and character width are honored; absolute cursor positioning, screen clearing, and alternate-screen sequences are stripped.
|
|
179
|
+
|
|
180
|
+
<a id="dev-note"></a>
|
|
181
|
+
### Dev Note
|
|
182
|
+
|
|
183
|
+
<details>
|
|
184
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
185
|
+
|
|
186
|
+
None.
|
|
187
|
+
|
|
188
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "qilin Web 客户端共享的 React UI 原子组件:控件、图标、Markdown 与数学公式渲染,以及终端/读取/差异/搜索/网页输出卡片(零 Kylin)。"
|
|
3
|
+
kind: "package-library"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @qilin-agent/client-ui-primitives
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
使用 `qilin-client-ui-primitives`,通过共享 React UI 构建 Web 客户端控件并渲染 agent 输出。它提供标准控件、图标、锚定浮层,以及用于带 TeX 公式的 Markdown、终端输出、文件读取、差异、搜索、网页检索和 JSON 的渲染器。这些渲染器会把原始 HTML 保持为字面文本——除非持有方自己提供一个消毒渲染器——并限制链接、解析 ANSI 转义序列,以处理不受信任的模型输出。组件不 import Kylin 运行时;调用方提供本地化 label,主题相关颜色使用 `--qilin-*` 设计 token。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
本包是 Web 壳的构建输入。静态 ESM 为 Vite 保留第三方导入和样式;独立消费方自行提供开发依赖([依赖规则](../AGENTS.md#dependency-declaration))。
|
|
29
|
+
|
|
30
|
+
只要 Web 客户端需要标准控件或 agent 输出渲染器,就用这些原子组件拼装功能 UI。它们只经 React 渲染,并从主题取得 `--qilin-*` 设计 token,因此无需导入主题或 slot 系统即可适配任意插件。
|
|
31
|
+
|
|
32
|
+
<a id="component-catalog"></a>
|
|
33
|
+
### 组件目录
|
|
34
|
+
|
|
35
|
+
在功能包里写控件之前,先查这张表。插件无法导入另一个插件的组件,因此本包是控件唯一可以共享的地方:合适的就复用,有意的视觉差异提升成 prop,而不是另起一份拷贝。
|
|
36
|
+
|
|
37
|
+
| 导出 | 是什么 |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `Button` | 可点击操作;`variant` 选择 `primary`、`ghost`、`outline` 或 `toolbar`。 |
|
|
40
|
+
| `Switch` | 36×20 的双态开关。`label` 必填,控件不可能在没有名称的情况下发布。 |
|
|
41
|
+
| `SegmentedControl` | 由两个及以上等宽分段构成的 tablist,用一个滑动指示块在若干模式间切换卡片或面板;选中值由拥有者持有,`label` 为列表命名。`id` 派生出每个页签的 id(`<id>-<value>`)与它所控制的面板(`<id>-<value>-panel`),面板由拥有者渲染并用 `aria-labelledby` 指回页签;单个分段可 `disabled` 并带 `title`,控件级 `disabled` 会在所示面板有在途写入或请求时锁住全部分段。 |
|
|
42
|
+
| `Checkbox` | 带标签的原生复选框,支持受控状态、键盘交互和禁用样式;调用方提供本地化的 `label` 文本。 |
|
|
43
|
+
| `Input` | 单行文本输入,用于搜索框与行内表单。 |
|
|
44
|
+
| `Menu` | 由条目、分隔线与分组标题构成的下拉菜单,支持嵌套子菜单。打开期间 `↑`/`↓`(以及 Home、End)在列表中走位,Tab 选定聚焦行,Escape 或 Shift+Tab 关闭并把焦点还给锚点;选定一行同样把键盘还给锚点——除非拥有者自己移动了焦点。只拦截位于锚点或列表内的键盘,`autoFocus` 仅决定打开时是否聚焦首行。 |
|
|
45
|
+
| `Pill` | 可选中的胶囊按钮,用于视图切换与筛选器;接受 `active` 与 `onClick`。 |
|
|
46
|
+
| `Tag` | 只读胶囊徽章;`tone` 选择八种配色之一。 |
|
|
47
|
+
| `PathLabel` | 单行文件路径:目录弱化、文件名为主要信息、悬停时显示完整路径。放得下时左对齐;被裁剪时保留尾部并在左缘渐隐,路径或尺寸变化都会重新判定。 |
|
|
48
|
+
| `StateDot` | 状态标记:`done`、`warning`、`ongoing`、`error` 或 `idle`。它是 `aria-hidden` 的,名称由渲染点提供。 |
|
|
49
|
+
| `ConnectionIndicator` | 行内连接恢复控件,覆盖断线、重试与已恢复三种状态。 |
|
|
50
|
+
| `DisclosureRow` | 24px 紧凑折叠行,标题与内容左右排列。 |
|
|
51
|
+
| `CommandText` | 一行命令文本:仅在横向溢出时才获得 Tab 停靠、group 角色与可访问名。 |
|
|
52
|
+
| `Modal` | 页面遮罩之上的居中对话框。 |
|
|
53
|
+
| `RiskConfirmation` | 以显式复选框把关的敏感操作确认。 |
|
|
54
|
+
| `OnboardingSurface` | 首次运行的引导舞台,期间保持应用根节点 inert。 |
|
|
55
|
+
| `Tooltip` | 克隆锚点上的悬停文本,可置于右、下、上三个方向。 |
|
|
56
|
+
| `HoverCard` | 指针可停留、可选中的悬停预览;可选带复制按钮。 |
|
|
57
|
+
| `ImageLightbox` | 共享图片浮层,支持焦点恢复与 Esc 关闭。 |
|
|
58
|
+
| `Toast` | 顶部居中的瞬时横幅,保持时长由所有者的 `holdMs` 决定。 |
|
|
59
|
+
| `JsonTree`、`JsonBlock` | 只读 JSON 查看。 |
|
|
60
|
+
| `MarkdownText`、`MarkdownDelegateProvider`、`CodeBlock` | 不可信 GFM 与 TeX 数学、owner 委托的 HTTP(S) 导航,以及高亮代码。`CodeBlock` 可通过 `lineNumbers` 开启行号;复制的源码不含行号栏,`contentRef` 则向需要把稳定源码包装节点用作滚动区的 owner 提供该节点。调用方提供自己的语言与复制工具栏时,设置 `showHeader={false}`。 |
|
|
61
|
+
| `TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock`、`WebBlock` | 与各类工具结果意图对应的 agent 输出卡片。 |
|
|
62
|
+
| `SettingsForm`、`SettingsValueField`、`SettingsSecretField` | 基于 `SettingsFormModel` 的分阶段配置表单:字段如实报告读者输入,保存是草稿变成文档变更的唯一入口。 |
|
|
63
|
+
| `icons/*`、`QilinSeal`、`ReferenceIcon`、`LinkIcon` | 字形与品牌标识。`LinkIcon` 用于 14px 的可点击链接分类及已知站点标记。 |
|
|
64
|
+
| `FileTypeIcon`、`classifyFileType`、`fileExtension` | 按类别着色的 28px 文件或文件夹图形,以及它背后共享的不区分大小写文件名映射。代码与配置文件使用细分的全彩技术图形;链接前置图形使用 `LinkIcon`,图片内容使用图片预览。 |
|
|
65
|
+
|
|
66
|
+
有四组容易混淆:
|
|
67
|
+
|
|
68
|
+
- **`Tag` 与 `Pill`。** 11px 胶囊尺寸的只读徽章用 `Tag`;胶囊可选中(`active` 与 `onClick`,视图切换与筛选器就是这样用的),或者必须落在 24px 文本行上时用 `Pill`——`TerminalBlock` 把退出状态渲染成静态 `Pill` 正是后一种情况。这里尺寸和是否可交互同样是判据,两者不可互换。
|
|
69
|
+
- **`Pill` 与 `SegmentedControl`。** 一排 `Pill` 是一组彼此独立的筹码——每个自己切换,可以同时有多个处于激活态。`SegmentedControl` 是若干互斥模式中的一个选择,画成带单个指示块的 tablist,并自带页签键盘模式(方向键在分段间走位,只有选中的那个位于 Tab 序列中)。
|
|
70
|
+
- **`DisclosureRow` 与卡片。** 该行以固定 24px 把标题与内容左右排列。把名称叠在描述之上的卡片是另一种布局,属于功能包——`ui-settings-plugins` 的 `PluginCard` 是先例,并记录了原因。
|
|
71
|
+
- **`FoldToggle` 与对外导出面。** 它是包内组件,未导出;输出卡片用它做头尾折叠。
|
|
72
|
+
|
|
73
|
+
需求确实特殊时,在自己的包里写自己的组件没有问题。不可以的是复制这里已有的控件——而当第二个包需要同一个控件时,它就该住进本包([决定](../../../.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.zh.md))。
|
|
74
|
+
|
|
75
|
+
### 控件与图标
|
|
76
|
+
|
|
77
|
+
上面的目录说明每个导出的用途;本节讲 props 本身看不出来的行为。`ic_ds_*` 图标集填充行内图标 slot,`QilinSeal` 印章是品牌标记。`FileTypeIcon` 渲染传统的 28px Excel、folder、HTML、image、Markdown、generic、PDF、PPT、video 与 Word 图形,并为现有 48 个代码和配置类别使用导入的方形技术图形。该导入只替换图形:资源包中额外的类别不会扩展 `CodeFileType`。`classifyFileType` 按完整文件名、前缀、后缀、可选项目上下文、扩展名的顺序匹配;React 文件名优先于 TypeScript/JavaScript,Angular 后缀优先于基础扩展名,只有传入的项目文件包含带 `flutter:` 的 `pubspec.yaml` 时 Dart 文件才使用 Flutter。Markdown 与 SVG 仍分别使用传统 Markdown 与图片图形。办公文件映射包含 XLSM/Numbers 的表格图标、KEY 的幻灯片图标,以及 RTF/ODT/Pages 的文档图标。`fileExtension` 为相邻元数据 label 暴露同一套 basename 与最终点号解析。传统图形使用实色分类底板、白色标记和半透明白色折角;通用文件使用灰色底板与较深灰色折角。调用方可通过 `--qilin-file-type-icon-color` 覆盖底板颜色。全彩技术图形是明确例外,会保留其内嵌调色板。所有图形都是装饰性的,不自带 label。`LinkIcon` 仍是可点击产物链接较小的前置图形——地球、文件夹、代码、图片、文档或纸张,`url` 链接的 `href` 指向已知站点时则改用该站点自己的标记——转写内容常引用的开发者站点(GitHub、GitLab、npm、PyPI、Stack Overflow、MDN、Wikipedia、Hacker News、YouTube、X、Bilibili、知乎、掘金、CSDN),以及主流搜索、视频、社交、购物与参考资料站点(Google、百度、DuckDuckGo、TikTok、Netflix、Spotify、Facebook、Instagram、Reddit、Telegram、WhatsApp、微信、QQ、微博、淘宝、速卖通、eBay、Quora、V2EX、Apple)——`classifyLinkPath` 把共享文件类型折叠进原有六类词汇。`ConnectionIndicator` 可渲染警告色的断联操作(常驻重试图形指明重试动作,断联文案由持有方提供)、spinner 加一至三个点以独立于 retry 时序的 500ms 节奏推进的连接中状态,或成功色的恢复状态。点击任一警告状态都会请求立即重连;没有任何悬停交互会改变文案。药丸出现时淡入、卸载前淡出 150ms,宽度随当前 label 自适应。它的持有方提供可见性、恢复驻留时间、本地化 label 与立即重连回调;该原语不使用原生 title tooltip。`useAnchoredPosition` 与 `useAnchoredMaxHeight` 让浮动面板与底部锚定浮层始终钳制在视口内并跟随锚点。`HoverCard` 通过指针离开宽限期让采用 portal 的预览在跨过锚点间隙时仍可触及,并可通过 `copyText` prop 提供复制按钮。`Toast` 的停留时长由使用方通过 `holdMs` 指定,因为横幅该留多久取决于有多少内容要读;同一个值同时驱动它的卸载定时器与样式表的淡出延迟,两者不可能再错位。`rankByName` 是 `/` 菜单命令源与 skill(技能)源共享的候选排序器:查询必须是名字的不区分大小写的有序子序列;前缀命中排最前,其次按对齐分数,再按来源顺序。 `Menu.autoFocus` 聚焦首个启用项,支持上下方向键与 Home/End 导航,并在 Escape 时聚焦 anchor 内的第一个按钮;操作菜单可显式启用。
|
|
78
|
+
|
|
79
|
+
### 渲染 agent 输出
|
|
80
|
+
|
|
81
|
+
最近的 `MarkdownDelegateProvider` 提供可选的 `openExternalLink` 和 `openFile` 导航回调。嵌套 Provider 替换外层能力,回调变化无需重新构建 Markdown 即可到达已渲染链接。其 `openFile` 使本地 Markdown 链接在落定后可点击。绝对路径和工作区相对路径支持百分号转义以及 `#L24` / `#L24-L30` 片段;范围定位到起始行。文件名中的字面 `?` 和 `#` 必须百分号编码。悬停提示使用解码后的路径,并在标签为空时提供可访问名称。回调接收解码后的路径和可选行号,渲染器保留标签并显示文件图标。不传回调时,本地链接仍为文本。URL 协议、查询串、不支持的片段及格式错误的目标不会传给文件打开器。
|
|
82
|
+
|
|
83
|
+
`MarkdownText` 渲染不可信的 GFM 与 TeX 公式、阻止不安全的链接与图片,并可把已解析的文件提及转换为显式控件。外层 `MarkdownDelegateProvider` 会接收普通点击产生的已净化 HTTP(S) URL;带修饰键的点击和 Provider 外的链接保留原生外部 anchor 行为。当 owner 传入 `pathImages` 词表时,本地媒体路径的图片目标只在落定渲染阶段重写为可展示 URL(与 file mentions 相同的流式门);不传词表时本地目标保持惰性 alt 文本。加载或解码失败后,图片替换为作者的 alt 文本;alt 为空时显示原始目标路径;`fileImages` 还提供本地化的失败提示前缀。图片源变化后可重新加载。回复流式输出时,它冻结已完成的块、按已完成行推进顶层未闭合 fence,并从保存的 Shiki grammar state 为该 fence 增量高亮。已完成的 token 行进入固定大小的 React 分组,后续分片只 reconcile 正在增长的分组;最终全量解析解决跨文档语法时,未变化的 fence 会保留该 DOM。`TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock` 与 `WebBlock` 把对应的工具结果意图渲染为带复制控件、溢出处理及适用时 ANSI 处理的卡片。`JsonTree` 与 `JsonBlock` 以只读方式检查 JSON 值;`projectUserText` 把已发送的用户文本投影为行内普通文本段与引用 chip,供消息气泡和排队行使用。 传入 `UserTextReferences` 时,文件和 skill 引用成为支持键盘操作的预览按钮,复用正文文件链接的悬停和聚焦样式;第一次指针点击可以打开预览,后续点击和已有选区保留原生选择行为。键盘激活在存在选区时仍可打开预览。```` ```mermaid ```` 围栏经懒加载的库以严格安全模式渲染;净化后的 SVG 保留几何、文本、渐变与类名,脚本、事件处理器、`foreignObject` 和 `javascript:` URL 一律剥离,点击或 Enter/Space 在模态中放大显示图示,点击遮罩关闭。渲染被拒时,围栏源码及其复制控件上方显示前几行错误。
|
|
84
|
+
|
|
85
|
+
`MarkdownText` 默认为 `variant="body"`。次级内容使用 `variant="compact"`:其 13px 字号与 20px 行高跟随内容字号设置,各级标题保持同一字号并使用 600 字重,段落与列表采用更紧凑的间距。正文、链接和代码均保持 tertiary 颜色,以点状下划线区分链接。代码标题栏随代码块滚动。表格和公式仍然启用,使用周围文字的字号,并在可用宽度内横向滚动。两个变体共享解析器与流式缓存。
|
|
86
|
+
|
|
87
|
+
`DiffBlock` 按行比较新旧内容。它显示实际增删行及两侧最多三行中性上下文,用 `⋯` 分隔远距离改动,摘要和底部统计都不计入共享上下文。若一个片段需要超过 256 次行新增或删除,则停止精确比较;该片段按完整新旧内容显示和统计为粗粒度替换,包含共享行。复制包含完整显示 diff 及其前缀。末尾换行视为行终止符;仅末尾换行不同不会显示为改动。
|
|
88
|
+
|
|
89
|
+
`JsonTree` 把折叠字符串限制为 `collapsedStringLines` 行(默认三行)。展开后显示原始文本、保留同级逗号,并限制在窗口与外层滚动容器内;尺寸变化和祖先滚动事件会更新此限制。行复制反馈独立于 JSON 值渲染更新;尚未完成的剪贴板写入不会更新另一行或已卸载的树。
|
|
90
|
+
|
|
91
|
+
`ImageLightbox` 是共享原图浮层,支持焦点恢复与 Esc 关闭。包内缩略图渲染器由调用方提供加载及失败文案。`HoverCard.inline` 使文件链接保持在文本流内,并使用共享菜单材质、键盘可见焦点,并在锚点上方或下方定位而不遮挡锚点。即使焦点位于其他位置,Esc 也会关闭已打开的缩略图;后续 Esc 按键继续传给 owner。`MarkdownDelegateProvider.fileImages` 提供已解码路径解析器与完整图片文案:消息落定后的图片链接支持悬停预览,独立图片支持点击放大。仅包含图片的链接保留单一导航目标。解析器仅恢复完整、未转义、独立成段且带明确图片扩展名的含裸空格本地图片引用;代码与有歧义的目标保持原文。
|
|
92
|
+
|
|
93
|
+
### 本地化文案
|
|
94
|
+
|
|
95
|
+
这些原子组件无法读取应用 locale,因此每段面向用户的文案都必须通过 label prop 提供。`HoverCard`、`TerminalBlock`、`JsonTree`、`CodeBlock`、`MarkdownText`、`JsonBlock`、`ConnectionIndicator`、`Modal`、`DiffBlock`、`ReadBlock`、`SearchBlock` 与 `WebBlock` 接收完整的本地化 label。本包不拥有语言回退;遗漏会导致类型检查失败,各功能会把带类型的 `t` 席位映射到 primitive 的 label 接口。
|
|
96
|
+
|
|
97
|
+
-----
|
|
98
|
+
|
|
99
|
+
<a id="understand-the-implementation"></a>
|
|
100
|
+
## 理解实现
|
|
101
|
+
|
|
102
|
+
<details>
|
|
103
|
+
<summary>实现细节——点击展开</summary>
|
|
104
|
+
|
|
105
|
+
本包只做一件事:提供零 cordis、零 slot 知识、仅经 `--qilin-*` token 设置样式的纯 React 原子组件,而所有功能专属的关注点(locale、会话数据、组合)都留在拼装它们的插件中。
|
|
106
|
+
|
|
107
|
+
### 源码地图
|
|
108
|
+
|
|
109
|
+
| 文件 | 职责 |
|
|
110
|
+
|---|---|
|
|
111
|
+
| [`src/index.ts`](src/index.ts) | 原子组件公开导出 |
|
|
112
|
+
| [`src/markdown/`](src/markdown/) | Markdown 与数学公式流水线:micromark 解析、KaTeX 排版、增量流式渲染器、`CodeBlock`/`JsonBlock` |
|
|
113
|
+
| [`src/TerminalBlock.tsx`](src/TerminalBlock.tsx) | ANSI 转义解析(`anser`)与终端卡片渲染 |
|
|
114
|
+
| [`src/CommandText.tsx`](src/CommandText.tsx) | 单行命令的横向滚动区,以及仅在溢出时出现的可访问名 |
|
|
115
|
+
| [`src/settings-form/`](src/settings-form/) | 分阶段配置表单、其字段控件,以及背后的草稿/写入模型 |
|
|
116
|
+
| [`src/ReadBlock.tsx`](src/ReadBlock.tsx) / [`src/DiffBlock.tsx`](src/DiffBlock.tsx) | 读取与差异卡片 |
|
|
117
|
+
| [`src/SearchBlock.tsx`](src/SearchBlock.tsx) / [`src/WebBlock.tsx`](src/WebBlock.tsx) | 搜索与网页检索卡片 |
|
|
118
|
+
| [`src/icons/`](src/icons/) | `ic_ds_*` 字形组件与品牌标记 |
|
|
119
|
+
| [`src/useAnchoredPosition.ts`](src/useAnchoredPosition.ts) / [`src/useAnchoredMaxHeight.ts`](src/useAnchoredMaxHeight.ts) | 浮动面板与浮层几何钩子 |
|
|
120
|
+
| [`src/input-modality.ts`](src/input-modality.ts) | 发布在 `<html>` 上的全文档输入模态 |
|
|
121
|
+
|
|
122
|
+
<a id="input-modality"></a>
|
|
123
|
+
### 输入模态
|
|
124
|
+
|
|
125
|
+
[`input-modality.ts`](src/input-modality.ts) 为 tooltip 追踪输入,并把 `data-input-modality` 发布到 `<html>` 上,供 [ui-theme 的焦点样式](../ui-theme/README.zh.md#understand-the-implementation)读取。`pointerModality()` 在指针输入后为 true,在任意按键(包括 IME 组合键)后为 false;`Tooltip` 用它决定焦点是否可以弹出气泡。已发布属性保持 `pointer`,直到出现非组合的导航键(Tab、方向键、Home/End、PageUp/PageDown),或者非组合按键后焦点移动到另一个控件。重新聚焦同一控件不会恢复键盘模态。指针输入、IME 组合键与窗口失焦都会清掉待决按键;没有待决按键的焦点变化不改变模态。这些 listener 与文档同生命周期;Node 导入不安装任何 listener。焦点变化规则观察的是暴露给 window 的事件;组件在 shadow root 内自有的导航不会暴露这些事件。
|
|
126
|
+
|
|
127
|
+
### 流式 Markdown
|
|
128
|
+
|
|
129
|
+
回复流式输出期间,`MarkdownText` 增量解析:除末尾两个块外全部冻结为缓存的 React 元素,每个分片只重新解析其后的源文本尾部,因此每分片的工作量跟随尾部而非整个回复。末尾的顶层未闭合 fence 会保留已解析的 code node,只把最后一个已完成行与当前未完成行交给同一套 GFM grammar;闭合 fence 或有歧义的解析会回到普通尾部路径。高亮同样从保存的 Shiki grammar state 续接,并只发布新完成行与可变尾部。`CodeBlock` 把已完成行封入固定大小的 React 分组、复用更早的分组,并在代码与语言未变化时跨定稿保留整棵高亮树。定稿时的全量解析仍会解析跨过冻结边界的引用。
|
|
130
|
+
|
|
131
|
+
### 几何与溢出
|
|
132
|
+
|
|
133
|
+
输出卡片共享同一套几何模型:`white-space: pre` 并横向滚动,让按列对齐的内容保持对齐;超过 `maxLines`(默认 16)时折叠为头部切片加尾部切片,由展开按钮控制,长正文不会撑高卡片。`TerminalBlock` 用 `CommandText` 让每条作者写的命令行都可选中、可横向滚动;提示符与状态保持固定。只有横向溢出的文本才以具名 group 进入 Tab 顺序,名称取自 owner 本地化的 `commandLine(line)`。`TerminalBlock` 把 ANSI 解析为 React span,并带逐行列缓冲处理光标移动,遵循行内擦除、制表位与字符宽度。
|
|
134
|
+
|
|
135
|
+
</details>
|
|
136
|
+
|
|
137
|
+
-----
|
|
138
|
+
|
|
139
|
+
<a id="further-exploration"></a>
|
|
140
|
+
## 进一步探索
|
|
141
|
+
|
|
142
|
+
以下页面说明这些原子组件在客户端技术栈与设计系统中的位置。
|
|
143
|
+
|
|
144
|
+
- [ui-renderer](../ui-renderer/README.zh.md)——挂载组装后应用并绑定 slot 数据的 React 渲染器。
|
|
145
|
+
- [ui-tool](../ui-tool/README.zh.md)——拼装这些输出卡片的工具调用展示层。
|
|
146
|
+
- [ui-conversation](../ui-conversation/README.zh.md)——渲染 Markdown 回复与工具卡片的聊天界面。
|
|
147
|
+
- [ui-theme](../ui-theme/README.zh.md)——这些原子组件样式所依赖的 `--qilin-*` token 体系。
|
|
148
|
+
- [Web 样式](../../../docs/web-styling.zh.md)——Web 客户端组件的权威样式规则。
|
|
149
|
+
|
|
150
|
+
-----
|
|
151
|
+
|
|
152
|
+
<a id="model-experience"></a>
|
|
153
|
+
## 模型体验
|
|
154
|
+
|
|
155
|
+
无。该包是浏览器端 UI 插件层,不注册任何面向模型的内容。
|
|
156
|
+
|
|
157
|
+
#### KV Cache 影响
|
|
158
|
+
|
|
159
|
+
无;该包既不组装也不发送提供方请求。
|
|
160
|
+
|
|
161
|
+
## 已知限制与延期工作
|
|
162
|
+
|
|
163
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
这些限制说明原子组件在边缘情况下的行为;它们是当前包约束,不是组件路线图。
|
|
167
|
+
|
|
168
|
+
- **Diff 搜索有上限,输入处理仍为线性**:编辑距离上限使大量改动的片段采用粗粒度替换,不再精确对齐。规范化、回退行及复制内容仍随输入大小增长;高度限制只约束可见行数,不限制这些分配。
|
|
169
|
+
- **已知站点标记是固定列表**:只有列名的主机解析为自己的标记,其余外部主机仍使用地球;要识别任意站点需要通过网络抓取它的图标。
|
|
170
|
+
- **流式期间跨边界引用解析被推迟**:定义落在增量冻结边界另一侧的引用式链接或脚注,在回复流式输出期间渲染为字面文本;定稿时的全量解析会将其解析。
|
|
171
|
+
- **超长行不着色**:1,000 个 UTF-16 单元及以上的源码行在所有高亮面中渲染为单个纯文本 run,因为 TextMate 的扫描成本随行长超线性增长,一条生成出来的长行足以阻塞页面数秒。语法状态会跳过该行,因此在其上开启或结束的注释或模板字符串会让下一行着色错误。
|
|
172
|
+
|
|
173
|
+
- **长高亮 fence 会保留完整 token DOM**:流式路径避免重新解析、重新 tokenize 和 reconcile 已完成前缀,但不会丢弃旧颜色或虚拟化 token span。因此最终 DOM 数量仍随 fence 的 token 数增长;嵌套/容器内 fence 与病态的单个超长行仍走通用尾部路径。
|
|
174
|
+
- **字形级图标是重新绘制的近似版本**:鱼形标志与闪光标记来自字体字形,而本地设计数据无法导出其矢量几何;在获得精确导出路径前,使用手工重建版本代替。
|
|
175
|
+
- **`Pill` 与 `Input` 没有设计来源**:两个原子组件均自行定义;与其相似的侧边栏搜索字段和视图标签条由消费方组合,不是这些原子组件。
|
|
176
|
+
- **`StateDot` 没有 `Active` 变体**:支持的状态为 done、warning、ongoing、error 和 idle。
|
|
177
|
+
- **面向用户的文案必须由渲染点提供**:这些原子组件是 zero-Kylin 的,拿不到 `ctx.locale`;各功能必须通过原子组件的带类型 prop 提供完整本地化 label(见[决策](../../../.agents/notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.zh.md))。
|
|
178
|
+
- **`TerminalBlock` 不是终端模拟器**:它渲染已结束或仍在运行的命令输出,而不是交互式会话:SGR 颜色、回车、退格、行内擦除、制表位与字符宽度会被遵循;绝对光标定位、清屏与备用屏幕序列会被剥离。
|
|
179
|
+
|
|
180
|
+
<a id="dev-note"></a>
|
|
181
|
+
### 开发备注
|
|
182
|
+
|
|
183
|
+
<details>
|
|
184
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
185
|
+
|
|
186
|
+
无。
|
|
187
|
+
|
|
188
|
+
</details>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* Capsule geometry from the figma Button component (1:155 instances:
|
|
2
|
+
* h36, pad 14/7, gap 4, r18; the wide New Session form is r24 at h38 —
|
|
3
|
+
* owners with the wide form set their own radius/width). */
|
|
4
|
+
.button {
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
gap: 4px;
|
|
9
|
+
border: none;
|
|
10
|
+
border-radius: 18px;
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
font-size: 14px;
|
|
13
|
+
line-height: 22px;
|
|
14
|
+
color: var(--qilin-alias-label-primary);
|
|
15
|
+
background: transparent;
|
|
16
|
+
padding: 0 14px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.button:disabled {
|
|
20
|
+
cursor: not-allowed;
|
|
21
|
+
opacity: 0.4;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.md {
|
|
25
|
+
height: 36px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Compact height for dense rows; no dedicated figma node (Icon_container
|
|
29
|
+
* 28x28 is the icon-only form) — geometry is ours. */
|
|
30
|
+
.sm {
|
|
31
|
+
height: 28px;
|
|
32
|
+
font-size: 12px;
|
|
33
|
+
line-height: 18px;
|
|
34
|
+
padding: 0 10px;
|
|
35
|
+
border-radius: 14px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.primary {
|
|
39
|
+
background: var(--qilin-alias-button-primary-fill);
|
|
40
|
+
color: var(--qilin-alias-label-primary-foreground);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.primary:hover:not(:disabled) {
|
|
44
|
+
background: var(--qilin-alias-button-primary-hover);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.ghost:hover:not(:disabled) {
|
|
48
|
+
background: var(--qilin-alias-interactive-bg-hover);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ghost:active:not(:disabled) {
|
|
52
|
+
background: var(--qilin-alias-interactive-bg-active);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Dialog Cancel (figma 451:18655): bordered capsule on transparent fill. */
|
|
56
|
+
.outline {
|
|
57
|
+
border: 0.5px solid var(--qilin-alias-border-l3);
|
|
58
|
+
background: transparent;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.outline:hover:not(:disabled) {
|
|
62
|
+
background: var(--qilin-alias-interactive-bg-hover);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.toolbar {
|
|
66
|
+
background: var(--qilin-alias-button-tool-bar-fill);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.toolbar:hover:not(:disabled) {
|
|
70
|
+
background: var(--qilin-alias-button-tool-bar-hover);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.icon {
|
|
74
|
+
display: inline-flex;
|
|
75
|
+
width: 16px;
|
|
76
|
+
height: 16px;
|
|
77
|
+
align-items: center;
|
|
78
|
+
justify-content: center;
|
|
79
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.checkbox {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: 6px;
|
|
5
|
+
font-size: 14px;
|
|
6
|
+
line-height: 20px;
|
|
7
|
+
color: var(--qilin-alias-label-primary);
|
|
8
|
+
cursor: pointer;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.checkbox input {
|
|
12
|
+
flex: 0 0 auto;
|
|
13
|
+
width: 16px;
|
|
14
|
+
height: 16px;
|
|
15
|
+
margin: 0;
|
|
16
|
+
accent-color: var(--qilin-alias-brand-primary);
|
|
17
|
+
cursor: inherit;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.checkbox input:focus-visible {
|
|
21
|
+
outline: var(--qilin-focus-ring-width) solid var(--qilin-focus-ring-color, var(--qilin-alias-state-business-primary));
|
|
22
|
+
outline-offset: 2px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.checkbox:has(input:disabled) {
|
|
26
|
+
cursor: default;
|
|
27
|
+
opacity: 0.5;
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
.text {
|
|
2
|
+
display: block;
|
|
3
|
+
min-width: 0;
|
|
4
|
+
overflow-x: auto;
|
|
5
|
+
white-space: var(--qilin-command-whitespace, pre);
|
|
6
|
+
overflow-wrap: anywhere;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.text:focus-visible {
|
|
10
|
+
outline: var(--qilin-focus-ring-width) solid var(--qilin-focus-ring-color, var(--qilin-alias-state-business-primary));
|
|
11
|
+
outline-offset: -2px;
|
|
12
|
+
}
|