@flanksource/clicky-ui

中文文档
0.3.62发布于 2026/11/13Apache-2.0TypeScriptESM + CJS构建来源已验证近 30 天下载 6046

Flanksource Clicky UI — React component library built on shadcn/ui with light/dark and density theming.

原始使用文档
文档目录19 节
  1. Install
  2. Usage
  3. Runtime settings
  4. Verification details
  5. Typed properties
  6. Clicky AST Renderer
  7. Operation explorer
  8. Timeseries widgets and WorkloadCard
  9. Change listeners ( x-on-change )
  10. Markdown editor field
  11. Bundle size guidance
  12. Expression language support
  13. IntelliJ icon catalog
  14. Tailwind preset
  15. Theming
  16. Multiplexing SSE streams
  17. Lint rules
  18. Comments in an external document
  19. License

@flanksource/clicky-ui

Flanksource's React component library built on shadcn/ui with light/dark theming and density presets.

Install

pnpm add @flanksource/clicky-ui @tanstack/react-query react react-dom tailwindcss
npm install @flanksource/clicky-ui @tanstack/react-query react react-dom tailwindcss
yarn add @flanksource/clicky-ui @tanstack/react-query react react-dom tailwindcss
bun add @flanksource/clicky-ui @tanstack/react-query react react-dom tailwindcss

Usage

import { Button } from "@flanksource/clicky-ui/components";
import { ThemeProvider, DensityProvider } from "@flanksource/clicky-ui/hooks";
import "@flanksource/clicky-ui/styles.css";

export function App() {
  return (
    <ThemeProvider>
      <DensityProvider>
        <Button variant="default">Click me</Button>
      </DensityProvider>
    </ThemeProvider>
  );
}

Runtime settings

Compose SpecRuntimeBar with the detailed SpecRuntimeEditor from @flanksource/clicky-ui/ai. The bar owns mode, model, effort, permission posture, cost, timeout, source/worktree, and commit timing. The detailed editor provides prompt, fallback, tool-policy, environment, checkout, and workflow details. Token and turn limit inputs are omitted; existing values survive edits and serialization.

Session ID is editable only with variant="preset". Environment variables always use SecretKeySelector for source, resource name, and key. Supply secretSelector catalog loaders to offer resource and key suggestions; without a catalog, users enter reference names and keys directly. Literal values remain a separate source. Shared comboboxes and dropdown menus expand to the available viewport height and scroll when their options exceed it.

import { useState } from "react";
import {
  SpecRuntimeBar,
  SpecRuntimeEditor,
  type AISpecRuntimeValue,
} from "@flanksource/clicky-ui/ai";

export function RuntimeSettings() {
  const [value, setValue] = useState<AISpecRuntimeValue>({ mode: "agent" });
  return (
    <SpecRuntimeEditor
      value={value}
      onChange={setValue}
      beforeSections={<SpecRuntimeBar value={value} onChange={setValue} />}
    />
  );
}

Pass the host's models and families catalogs to both components. effectiveMode and effectiveModel describe inherited identity without copying it into the draft. Set readOnly on both components for protected layers. inheritedCommits supplies the lower layer's commit policy; an explicit empty commit list selects Never. variant="preset" omits task-owned worktree paths, and sections restricts bar actions for hosts such as verification-only editors. Use PromptRunEditor for a run composer with single/multi-model runtime bars and shared limits.

Verification details

VerificationResults renders typed fixture steps through verificationAdapters(). Detail explains execution: commands, assertions, a compact wait summary, events and exported variables. Outputs contains the selected attempt's final structured result, including null and empty objects. The header's attempt selector appears for multiple attempts and defaults to the latest; selecting an attempt switches its execution, wait, Outputs and Copy details together, while the tree and run totals retain the step's overall verdict. Reports without stored historical evidence say so when that attempt is selected.

Waits retain the effective policy, outcome, actual elapsed durationMs, final outputs, and optional terminal error and trace. Intermediate poll values are not persisted. The summary shows the condition, elapsed badge, outcome and truncated output preview on one line; the full condition is available on hover, full outputs in the Outputs tab, and schedule/error diagnostics in the Wait details disclosure. TestRunner and VerificationResults accept activeAttempt and onAttemptChange for hosts that store the selection in the URL. Domain adapters supply { id, status, durationMs, node } views through detailAttempts.

Hosts can replace just the wait section without replacing the rest of the fixture renderer:

import {
  VerificationResults,
  verificationAdapters,
  type VerifyReport,
} from "@flanksource/clicky-ui/data";

const adapters = verificationAdapters({
  renderWait: (wait) => (
    <p>
      {wait.outcome}: {wait.durationMs}ms
    </p>
  ),
});

export function VerificationPanel({ report }: { report: VerifyReport }) {
  return <VerificationResults report={report} adapters={adapters} />;
}

The optional renderWait callback receives the parsed final WaitDetail. Omitting it uses the compact row; returning null deliberately hides the wait section. StepWait exposes the standard rendering for custom compositions, and StepOutputs renders final outputs independently of the execution sections.

Typed properties

Properties displays metadata as a two-column list. Each item has a stable key, a raw value, and an optional type and unit. Objects and arrays use the expandable JSON viewer; strings remain literal unless type: "json" is specified. Numeric types require finite numbers, and formatted values retain the original value in a tooltip.

import { Properties } from "@flanksource/clicky-ui/data";

export function RuntimeProperties() {
  return (
    <Properties
      keyRef="runtime"
      items={[
        { key: "memory", value: 1048576, type: "bytes" },
        { key: "uptime", value: 90000, type: "duration", unit: "ms" },
        { key: "load", value: 0.25, type: "number", unit: "percentunit" },
        { key: "enabled", value: false, type: "boolean" },
        { key: "started", value: 1704067200000, type: "date" },
        { key: "config", value: '{"enabled":true}', type: "json" },
      ]}
    />
  );
}

type accepts text, json, bytes, duration, number, boolean, or date. Bytes use binary scaling. Durations default to milliseconds; set unit: "s" for seconds. Numeric units also accept the shared unit registry, including percent, percentunit, and Bps. Dates accept a Date, an ISO string, or a timestamp in milliseconds. keyRef persists the density selected in the display menu. Custom renderValue renderers override typed formatting, and prefixActions or suffixActions add per-row actions without changing the raw value.

Clicky AST Renderer

import { Clicky, type ClickyDocument } from "@flanksource/clicky-ui/clicky";

const document: ClickyDocument = {
  version: 1,
  node: {
    kind: "text",
    text: "hello from clicky",
    plain: "hello from clicky",
  },
};

export function ClickyPanel() {
  return <Clicky data={document} />;
}

Clicky also accepts a JSON string payload. The intended producer is the sibling clicky repo's tagged html-react AST, which preserves structural types such as trees, tables, code blocks, collapsed sections, buttons, and nested text content.

Operation explorer

OperationCatalog and EntityExplorerApp (both exported from @flanksource/clicky-ui/rpc) render an OpenAPI spec — fetched via an OperationsApiClient — as a navigable list of operations grouped by entity surface. They expect the spec to declare x-clicky surface metadata for the surfaces they should expose. See apps/kitchen-sink/src/demos/OperationExplorerDemo.tsx for a fake-client example.

Timeseries widgets and WorkloadCard

TimeseriesPanel, TimeseriesGauge, TimeseriesCoreBars and WorkloadCard (from @flanksource/clicky-ui/data) poll their series with @tanstack/react-query, so they must render under the host application's QueryClientProvider. React Query v5 is a required peer dependency, ensuring clicky-ui and its host use the same Query Client context rather than installing isolated copies.

Each series is either URL-backed (baseUrl + id, loaded through fetcher(url)) or function-backed via load:

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { WorkloadCard } from "@flanksource/clicky-ui/data";

const queryClient = new QueryClient();

<QueryClientProvider client={queryClient}>
  <WorkloadCard
    workload={{
      type: "EC2 instance",
      name: "build-runner-1",
      status: { label: "running", health: "healthy" },
      metadata: [{ label: "region", value: "us-east-1" }],
    }}
    metrics={{
      cpu: {
        value: {
          id: "i-0a1b2c3d.cpu", // cache identity: unique per data source
          load: ({ range, signal }) =>
            api.instanceCpu("i-0a1b2c3d", { range, signal }),
        },
        max: 4000,
      },
    }}
  />
</QueryClientProvider>;

A load-backed series is cached under ["timeseries", "load", id, range]: the id is the cache identity, so two series that share an id share one cached response. The loader receives the widget's range and react-query's AbortSignal, which aborts when the query is cancelled (for example on unmount). URL-backed series keep their ["timeseries", requestUrl] key. See the timeseries guide for details.

Change listeners (x-on-change)

A property's x-on-change entries act on its sibling fields while when holds (and else while it does not). hide/show, enable/disable, require/optional and patch are derived state, recomputed from the current value on every render. set and reset fire only when the field is edited. patch merges standard keywords over a sibling, so a listener can make it readOnly or writeOnly, or change its title, enum or bounds:

"Status": {
  "type": "string",
  "enum": ["draft", "signed"],
  "x-on-change": [{
    "when": { "const": "signed" },
    "patch": {
      "Amount": { "readOnly": true },
      "Rate": { "title": "Agreed rate", "minimum": 0, "maximum": 25 }
    }
  }]
}

A patch may not change a field's shape (type, properties, items, allOf, …), its required-ness or its value (default, const); the form throws, pointing at the action that does. The Form listeners kitchen-sink demo has runnable examples of every action.

A hide/show/enable/disable target or a patch key may be a /-separated path into a sibling's subtree: groups/Rates hides a group, and groups/Rates/Fee hides one column of it, because an array is crossed into its items. An object's x-on-load entries have the same shape. Their when reads the object itself, they apply before any x-on-change, and they allow only the derived actions and patch. See the JSON Schema extensions reference in the docs app for the full grammar and ordering.

A writeOnly: true field is an input that is never read back: an editable form renders it normally, while a view renders nothing for it. A view is a readOnly form, anything under a readOnly object, array or map, or a field that is itself readOnly. x-disabled is not a view, so a disabled subtree keeps its controls. Use format: "password" to mask the value while it is edited.

Markdown editor field

JsonSchemaForm fields with format: md — and the standalone MdxEditorField exported from @flanksource/clicky-ui/mdx-editor — render an MDXEditor-backed rich-text field. Its base styles ship as a separate stylesheet so apps that don't use the field don't pay its weight (the editor's JavaScript is also loaded lazily, on first render). Import it once at the app root, in addition to styles.css:

import "@flanksource/clicky-ui/styles.css";
import "@flanksource/clicky-ui/mdx-editor.css"; // only if you render the markdown field

Without this import the field renders, but its toolbar and content are unstyled.

If Markdown cannot be imported, the field displays the parsing error and preserves the document in a source editor. Correct the source and choose Retry rich text to reopen the rich-text editor. Source edits use the same onChange callback; read-only and disabled fields remain protected. Loading another document through value retries its import automatically.

Bundle size guidance

Prefer subpath imports in production apps:

import { Button } from "@flanksource/clicky-ui/components";
import { cn } from "@flanksource/clicky-ui/utils";
import { DataTable } from "@flanksource/clicky-ui/data";

The root @flanksource/clicky-ui barrel remains supported for compatibility and convenience, but subpaths give bundlers a smaller entry surface. Import @flanksource/clicky-ui/styles.css once at the app root. Markdown parsing and code highlighting are loaded asynchronously by their components.

The published package uses plain semver dependency ranges and does not require pnpm workspace or catalog protocols. The package ships ESM and CommonJS entrypoints for the public subpaths above.

Expression language support

Import gomplate-compatible Monaco language support from @flanksource/clicky-ui/expressions, or the optional React playground from @flanksource/clicky-ui/expressions/playground. The language entry registers CEL, Go templates, JSONPath, and templates embedded in YAML, JSON, or text without importing the playground UI. See the expressions guide.

UI subpaths that render icons are intended for browser bundlers such as Vite, Rollup, Webpack, Rspack, and Bun's bundler. The Iconify React packages expose .jsx modules and CSS imports, so plain Node import/require of icon-heavy subpaths is not a supported verification target.

IntelliJ icon catalog

Import the offline components from @flanksource/clicky-ui/icons:

import {
  UiBreakpoint,
  UiMessageQueue,
  UiSqlPrimaryKey,
} from "@flanksource/clicky-ui/icons";

The jb-site: entries in icons/icon-selections.json resolve against the IntelliJ icon catalog. Run pnpm --filter @flanksource/clicky-ui download:icons after changing selections, then pnpm --filter @flanksource/clicky-ui build:icons:force. The downloader checks each catalog path and requires an Apache 2.0 header in every downloaded SVG. It stores URL-fetched sources from JetBrains, Iconify, and other providers under icons/svg/remote; generated React components are built from those local files. The per-component source list is in NOTICE.md.

Downloaded IntelliJ icon ZIPs can be imported with pnpm --filter @flanksource/clicky-ui exec tsx scripts/import-jetbrains-zips.ts <download-dir> <since-ISO> --licensed-only. The command extracts each light/dark pair into icons/svg/downloaded, adds programming selections, and writes icons/jetbrains-download-audit.json for the processed archives. Use --include-without-header instead to explicitly include pairs without an embedded Apache header. Those pairs use jb-download-unverified: source IDs and appear in the audit's withoutHeader list; their license is not verified by the import. Both SVG directories are offline build inputs; remote distinguishes URL-fetched artwork from ZIP-extracted artwork. Rebuild with build:icons:force after importing. Each concept has a preferred Ui<Concept> icon and numbered alternatives such as UiFunction1 and UiFunction2. Imported icons have matching Dark variants, while existing preferred components keep their artwork. programmingIconCatalog supplies typed concept, variation, color-role, and header-status metadata for palette pages.

Markdown and code highlighting use optional peer dependencies (marked, shiki, and @shikijs/*). Install them in applications that render those components.

Tailwind preset

Tailwind CSS v4 consumers can load the preset through its compatibility directive:

/* src/styles.css */
@config "../tailwind.config.ts";
@import "tailwindcss";
// tailwind.config.ts
import preset from "@flanksource/clicky-ui/tailwind-preset";

export default {
  presets: [preset],
};

The preset wires up:

  • Theme tokens via [data-theme="light" | "dark"] attributes on <html>.
  • Density variants via [data-density="compact" | "comfortable" | "spacious"].
  • Spacing utilities (gap-density-2, p-density-4, etc.) scaled by the active density.

Theming

const { theme, resolvedTheme, setTheme } = useTheme();
const { density, setDensity } = useDensity();

Both hooks persist their choice to localStorage under clicky-ui-theme / clicky-ui-density. Add this inline script to <head> to avoid FOUC:

<script>
  (function () {
    try {
      var t = localStorage.getItem("clicky-ui-theme") || "system";
      var d = localStorage.getItem("clicky-ui-density") || "comfortable";
      var resolved =
        t === "system"
          ? window.matchMedia("(prefers-color-scheme: dark)").matches
            ? "dark"
            : "light"
          : t;
      document.documentElement.setAttribute("data-theme", resolved);
      document.documentElement.setAttribute("data-density", d);
    } catch (_) {}
  })();
</script>

Multiplexing SSE streams

Browsers cap HTTP/1.1 at six connections per host, shared across tabs, so an app that opens one native EventSource per topic can starve its own fetches. createEventHub returns an EventSourceFactory that multiplexes every stream the hooks open (useTaskRun, usePrompts, useLogTail, ...) over one real connection to a server events endpoint (such as clicky's sse.Hub). Wrap the tree in EventSourceProvider once:

import {
  EventSourceProvider,
  createEventHub,
} from "@flanksource/clicky-ui/hooks";

// Create the hub once at module scope so every render shares one connection.
// `buildId` is the build the page was served with (e.g. from a <meta> tag);
// when the server's hello reports a different build the page reloads once.
// Omit it (or pass null) to never reload.
const eventHub = createEventHub({
  buildId: document.querySelector<HTMLMetaElement>('meta[name="app-build"]')
    ?.content,
});

root.render(
  <EventSourceProvider value={eventHub}>
    <App />
  </EventSourceProvider>,
);

Options: url (default /api/events), buildId (string | null), and onBuildMismatch (default window.location.reload()). Each hub keeps its own connection, so two hubs never share state.

Lint rules

The package ships opt-in oxlint rules that keep consuming projects on clicky-ui's components, overlays, theming and icons instead of rebuilding them by hand.

Install oxlint in the consuming project. If the project does not already depend on @flanksource/clicky-ui, add that as the normal runtime dependency too:

pnpm add @flanksource/clicky-ui
pnpm add -D oxlint

Then enable the JS plugin in the consumer's .oxlintrc.json:

{
  "jsPlugins": [
    {
      "name": "clicky-ui",
      "specifier": "@flanksource/clicky-ui/oxlint-plugins"
    }
  ],
  "rules": {
    "clicky-ui/prefer-clicky-components": "warn",
    "clicky-ui/no-adhoc-overlay": "error",
    "clicky-ui/prefer-tailwind-classes": "warn",
    "clicky-ui/prefer-theme-tokens": "error",
    "clicky-ui/prefer-clicky-icons": "warn"
  }
}

Add a package script that runs oxlint over the consumer source:

{
  "scripts": {
    "lint:clicky-ui": "oxlint -c .oxlintrc.json src --deny-warnings"
  }
}

The specifier form resolves through the package export. In a local sibling checkout, or if the package export is not available to your toolchain, point the plugin directly at the file instead:

{
  "jsPlugins": [
    "./node_modules/@flanksource/clicky-ui/oxlint-plugins/clicky-ui.js"
  ]
}

The rules flag rebuilt primitives (<button>, <table>, …), ad-hoc dialogs and arbitrary z-index, hand-rolled inline styles, hardcoded colors / raw theme storage access, and icons imported from unapproved libraries or faked with emoji. See oxlint-plugins/README.md for the full guide.

Comments in an external document

CommentProvider accepts onScrollToAnchor(anchor, options) and getAnchorTop(anchor) for hosts whose annotated document lives outside the rail's frame. onScrollToAnchor must return whether the anchor is available and navigation was dispatched; getAnchorTop returns its current viewport offset, or null when unavailable. Supplying a navigation delegate replaces local DOM scrolling. Without these callbacks the provider retains its registered-element behavior. Use CommentSidePanel with focusedAlignment="flow" for a detached rail, and synchronize selection and highlighting through useCommentContext.

<CommentProvider
  comments={comments}
  config={config}
  initialRailMode="all"
  getAnchorTop={(anchor) => documentAnchors.get(anchor)?.top ?? null}
  onScrollToAnchor={(anchor, options) => {
    if (!documentAnchors.has(anchor)) return false;
    annotatedWindow.postMessage(
      { type: "scroll", anchor, options },
      annotatedOrigin,
    );
    return true;
  }}
>
  <CommentSidePanel focusedAlignment="flow" />
</CommentProvider>

The host owns message validation, comment persistence and mutation callbacks. See the playground bookmarklet for an origin-checked bridge and an external-page review surface.

License

Apache-2.0

版本与发布记录

@flanksource/clicky-ui 共发布 84 个版本,首次发布于 2026/04/21;过去 12 个月发布了 84 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.3.62latest2026/11/132 天前
0.3.612026/11/132 天前
0.3.602026/11/132 天前
0.3.592026/11/115 天前
0.3.582026/11/0312 天前
0.3.572026/11/0313 天前

关于 @flanksource/clicky-ui 的常见问题

@flanksource/clicky-ui 是什么?

Flanksource Clicky UI — React component library built on shadcn/ui with light/dark and density theming.

如何安装 @flanksource/clicky-ui?

在项目目录运行 npm install @flanksource/clicky-ui,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^18.0.0 || ^19.0.0)、recharts(^3.0.0)、react-dom(^18.0.0 || ^19.0.0)、tailwindcss(^4.0.0)、monaco-editor(>=0.48 <1)、@tanstack/react-query(^5.66.8)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@flanksource/clicky-ui 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@flanksource/clicky-ui 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@flanksource/clicky-ui 可以商用吗?

@flanksource/clicky-ui 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@flanksource/clicky-ui 的下载量有多少?

根据 npm 官方下载统计,@flanksource/clicky-ui 在 2026/10/15 – 2026/11/13 这 30 天内被下载 6,046 次,日均约 202 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@flanksource/clicky-ui 还在维护吗?

最新版本 0.3.62 发布于 2026/11/13(2 天前);至今共发布 84 个版本,其中过去 12 个月发布了 84 个;首次发布于 2026/04/21。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。