# @inf-monkeys-tech/monkeys-design

Monkeys Design 是一个独立的 React 组件库，提供可复用的基础组件、数据展示组件、主题工具和样式资源。

## 公开文档

Monkeys Design 的官网与组件文档由
[`monkeys-docs`](https://github.com/infmonkeys/monkeys-docs) 统一维护和部署：

- 官网：<https://docs.infmonkeys.com/monkeys-design>
- 组件目录：<https://docs.infmonkeys.com/monkeys-design/components>

本仓库继续通过 Storybook 维护组件开发、交互测试、无障碍检查和视觉回归；`examples/public/` 是官网组件页与 Storybook 共享的公开示例来源。

## 安装

```bash
yarn add @inf-monkeys-tech/monkeys-design
```

或：

```bash
npm install @inf-monkeys-tech/monkeys-design
```

## 使用

从主入口导入通用能力：

```tsx
import {
  applyThemeTokens,
  compileThemeTokens,
} from "@inf-monkeys-tech/monkeys-design";
```

`ThemeTokens` 是唯一主题输入。Design 不接收旧主题配置，不提供转换器、别名或并行版本入口。

从子路径导入组件：

```tsx
import {
  BaseButton,
  BasePanel,
  type BaseAppearance,
} from "@inf-monkeys-tech/monkeys-design/components/base";

const appearance: BaseAppearance = {
  preset: "soft",
  radius: "lg",
  border: "subtle",
  background: "soft",
};

export function Example() {
  return (
    <BasePanel
      appearance={appearance}
      title="Design System"
      description="Composable UI primitives for Monkeys products."
      footer={<BaseButton appearance={appearance} label="Refresh" />}
    >
      Content stays in the consuming application.
    </BasePanel>
  );
}
```

## 包入口

| 路径                                                        | 内容                                                            |
| ----------------------------------------------------------- | --------------------------------------------------------------- |
| `@inf-monkeys-tech/monkeys-design`                          | 主入口，导出通用组件、主题工具和公共类型                        |
| `@inf-monkeys-tech/monkeys-design/components/base`          | 基础组件                                                        |
| `@inf-monkeys-tech/monkeys-design/components/data-explorer` | 数据展示与页面组合组件                                          |
| `@inf-monkeys-tech/monkeys-design/components/login`         | 登录相关 UI 组件                                                |
| `@inf-monkeys-tech/monkeys-design/components/layout`        | 布局组件                                                        |
| `@inf-monkeys-tech/monkeys-design/components/toolbar`       | 工具栏组件                                                      |
| `@inf-monkeys-tech/monkeys-design/components/capabilities`  | 声明式 Capability Catalog、provider/renderer runtime 与中性能力 |
| `@inf-monkeys-tech/monkeys-design/palette`                  | palette 工具                                                    |
| `@inf-monkeys-tech/monkeys-design/theme-system`             | canonical ThemeTokens 编译与应用工具                            |

## LoginPage

`LoginPage` 是通用登录页 UI 组件。组件库只负责页面结构和样式契约，不包含登录请求、OAuth、路由、租户判断、i18n 初始化或租户定制样式。

```tsx
import {
  LoginPage,
  type LoginPageMethodConfig,
} from "@inf-monkeys-tech/monkeys-design/components/login";
import "@inf-monkeys-tech/monkeys-design/styles/login-page.scss";

const methods: LoginPageMethodConfig[] = [
  {
    id: "email",
    label: "Email",
    fields: [],
    submitLabel: "Login",
    onSubmit: (event) => event.preventDefault(),
  },
];

export function Example() {
  return (
    <LoginPage
      background={{ imageUrl: "", gradient: "" }}
      logo={{ url: "", position: "bottom", scale: 1 }}
      toolbar={<div />}
      title="Welcome"
      methods={methods}
    />
  );
}
```

默认背景规则：

- 配置了 `background.imageUrl` 或 `background.gradient` 时，使用配置背景。
- 未配置背景时，浅色模式使用白色和主题色渐变。
- 未配置背景时，`.dark` 深色模式使用黑色和主题色渐变。

Logo 规则：

- `logo.url` 是浅色和深色模式共用的通用 Logo。
- `logo.lightUrl` / `logo.darkUrl` 分别用于浅色 / 深色模式；未配置时回退 `logo.url`。
- 宿主应用负责在登录页未单独配置 Logo 时，把品牌资产的浅色 / 深色 Logo 传入 `logo.lightUrl` / `logo.darkUrl`。

`LoginPage` 不接受原始颜色、圆角或任意 CSS props。宿主应用必须先用 `applyThemeTokens` 应用后端下发的 DTCG Token 合同，组件只消费统一的 `--primary`、`--primary-foreground`、`--radius` 等语义变量；深浅色切换、语言切换等应用级控件通过 `toolbar` 传入。

### LoginPage CSS 契约

页面级自定义样式只能依赖 `data-login-page`、`data-login-part` 和公开 CSS 变量。不要依赖租户 id，不要新增登录页代码分支。

推荐优先覆盖变量：

```css
[data-login-page] {
  --login-page-panel-bg: rgb(255 255 255 / 0.92);
  --login-page-panel-border: rgb(17 17 17 / 0.08);
  --login-page-panel-shadow: 0 24px 60px rgb(0 0 0 / 0.08);
  --login-page-control-bg: #ffffff;
  --login-page-control-border: rgb(17 17 17 / 0.12);
  --login-page-control-text: #111111;
  --login-page-control-placeholder: #9f9fa9;
  --login-page-form-width: 500px;
}

.dark [data-login-page] {
  --login-page-panel-bg: rgb(12 12 12 / 0.9);
  --login-page-control-bg: #111111;
}
```

公开部件：

```css
[data-login-part="root"] {
}
[data-login-part="background"] {
}
[data-login-part="toolbar"] {
}
[data-login-part="logo"] {
}
[data-login-part="logo-image"] {
}
[data-login-part="panel"] {
}
[data-login-part="back"] {
}
[data-login-part="back-icon"] {
}
[data-login-part="title"] {
}
[data-login-part="tabs"] {
}
[data-login-part="tab"] {
}
[data-login-part="tab-icon"] {
}
[data-login-part="form"] {
}
[data-login-part="field"] {
}
[data-login-part="input"] {
}
[data-login-part="checkbox-label"] {
}
[data-login-part="checkbox"] {
}
[data-login-part="method-footer"] {
}
[data-login-part="method-footer-action"] {
}
[data-login-part="submit-button"] {
}
[data-login-part="callout"] {
}
[data-login-part="callout-title"] {
}
[data-login-part="callout-description"] {
}
[data-login-part="callout-detail"] {
}
[data-login-part="callout-action"] {
}
[data-login-part="external-methods"] {
}
[data-login-part="external-label"] {
}
[data-login-part="external-list"] {
}
[data-login-part="external-button"] {
}
[data-login-part="external-icon"] {
}
```

## 基础组件

基础组件从 `components/base` 子路径导入：

```tsx
import {
  BaseButton,
  BaseContextMenu,
  BaseContextMenuContent,
  BaseContextMenuItem,
  BaseContextMenuTrigger,
  BasePanel,
  type BaseAppearance,
} from "@inf-monkeys-tech/monkeys-design/components/base";
```

当前包含：

- `BaseButton`
- `BaseBadge`
- `BasePanel`
- `BaseEmptyState`
- `BaseLoadingState`
- `BaseField`
- `BaseInput`
- `BaseTextarea`
- `BaseSelect`
- `BaseLayout`
- `BaseLayoutPane`
- `BaseLayoutSplit`
- `BaseLayoutResizeHandle`
- `BaseSwitch`
- `BaseCheckbox`
- `BaseRadioGroup`
- `BaseDialog`
- `BaseDropdownMenu`
- `BaseTooltip`
- `BaseTable`
- `BaseTableFooterBar`
- `BaseNotice`
- `BaseDivider`
- `BaseSectionHeader`
- `BaseTabs`
- `BaseSegmentedControl`
- `BaseProgress`
- `BaseSkeleton`
- `BaseAvatar`
- `BaseToolbar`
- `BaseBreadcrumb`
- `BasePagination`
- `BaseDescriptionList`
- `BaseAccordion`
- `BaseContextMenu`

`BaseAppearance` 用于控制 preset、density、radius、border、background、slot className 和 inline style 覆盖。

`BaseLayout` 提供中立布局 primitives：`BaseLayout` 管 header/sidebar/main/aside/footer 区域，`BaseLayoutPane` 管主题化区域表面，`BaseLayoutSplit` 管固定或可调的左右/上下分割。`gap` 可显式控制区域间距，number 按 px，string 原样作为 CSS 长度；不传时继续使用 `BaseAppearance` 的主题间距。可调分割支持 `defaultSize`、受控 `size`、`minSize`、`maxSize` 和 `onSizeChange`；尺寸是否持久化仍由使用方控制。

## Data Explorer

`components/data-explorer` 提供面向数据密集页面的组合组件。组件库负责可复用的控制、展示、预览、详情和布局结构；数据请求、权限、路由、弹窗和业务动作由使用方控制。

```tsx
import {
  DataExplorerView,
  type DataExplorerViewDefinition,
} from "@inf-monkeys-tech/monkeys-design/components/data-explorer";
```

常用组件包括：

- `DataExplorerView`
- `DataExplorerViewCollection`
- `DataExplorerDisplayCard`
- `DataExplorerDisplayCollectionView`
- `DataExplorerDisplayActionMenu`
- `DataExplorerDetailShell`
- `DataExplorerImagePreview`
- `DataExplorerToolbarShell`
- `DataExplorerActionBar`
- `DataExplorerTree`
- `DataExplorerPage`

## 声明式 Capability Catalog

`components/capabilities` 提供编辑器可发现的显式 Capability Catalog，以及基于 SDK `CapabilityManifest`、`ViewProviderDescriptor`、`RenderNode` 和 `RenderTree` 合同的 provider/renderer runtime。中性能力包括 shell/layout、sidebar/filter、gallery、record history/diff、Ontology/View management action launcher、统一 loading/empty/error/forbidden 状态、professional capability leaf，以及可序列化的 section header、notice、description list、content panel 和 toolbar。管理操作组件只发出精确、受治理的流程或命令 intent，不直接调用业务 API。`metadata/declarative-capability-audit.json` 逐项声明受治理 Base 组件和公开组件族是否可进入页面构建器；React export 本身不会产生 Capability。

Catalog 只包含 `NEUTRAL_CAPABILITY_CATALOG_ENTRIES` 中明确声明的条目。导出一个 React 组件不会自动使它成为可装配能力；新增能力必须同时声明 SDK manifest、provider descriptor、editor slots、ports、Ontology compatibility、i18n keys、token refs、a11y 与 performance evidence，再显式注册 provider 和 renderer。

```tsx
import {
  CapabilityRuntimeHost,
  NEUTRAL_DESIGN_CAPABILITY_RUNTIME,
} from "@inf-monkeys-tech/monkeys-design/components/capabilities";

export function DeclarativePage({ tree, access, bindings, t }) {
  return (
    <CapabilityRuntimeHost
      tree={tree}
      runtime={NEUTRAL_DESIGN_CAPABILITY_RUNTIME}
      access={access}
      bindings={bindings}
      translate={t}
    />
  );
}
```

有 `accessRef` 的节点必须收到调用方已经计算好的 access decision；缺失 decision 时 runtime 默认 forbidden，并且不会执行 provider。异步 provider 收到 `AbortSignal`，在 binding、访问状态或生命周期变化时取消旧请求并阻止 stale result 覆盖；`onPerformanceEvidence` 会收到 success、empty、error、cancelled 或 budget-exceeded 证据。Design 不负责计算业务权限，不拥有 Ontology 数据，不解释 raw API payload，也不把专业编辑器内部展开成第二棵 RenderTree。

## 主题系统

`theme-system` 只接受 DTCG 2025.10 `ThemeTokens` 文档，负责严格校验、解析引用、编译 CSS variables 和应用运行轴。类型和运行时 Schema 唯一来源是 `@inf-monkeys-tech/monkeys`。

```tsx
import {
  applyThemeTokens,
  compileThemeTokens,
} from "@inf-monkeys-tech/monkeys-design/theme-system";

const compiled = compileThemeTokens(themeTokens, {
  colorMode: "light",
  density: "default",
});

applyThemeTokens(themeTokens);
```

`compileThemeTokens` 支持 DTCG Group、`$root`、`$extends`、curly alias、JSON Pointer 和全部 13 种标准类型。light/dark 通过独立 Token 路径选择，不能塞进 `$value`。`applyThemeTokens` 只向指定 host root 写 canonical/semantic CSS variables 与 `data-monkeys-*` 运行轴，并会清除上一次应用留下的变量；它不读取产品业务字段。

Base 组件统一通过 `variant`、`density`、`state` 与 `appearance` 表达视觉和交互状态。组件不得把客户名、租户 ID、路由、页面场景或 raw API 字段作为视觉分支条件。

## 样式资源

可从包内直接引用样式文件：

```ts
import "@inf-monkeys-tech/monkeys-design/styles.css";
import "@inf-monkeys-tech/monkeys-design/styles/global.scss";
import "@inf-monkeys-tech/monkeys-design/styles/workflow-nodes.css";
```

`styles.css` 提供 canonical 产品控件的 Design-owned surface Token。它们不继承宿主页面的 `--background`、`--popover` 或 `--card`，因此不同产品 palette 不会改变同一标准控件的默认表面：

| Public CSS variable               | Consumers                                       | Light default    | Dark default      |
| --------------------------------- | ----------------------------------------------- | ---------------- | ----------------- |
| `--monkeys-color-control-surface` | Input、Textarea、outline Button、Select Trigger | `240 20% 99.02%` | `240 6% 7%`       |
| `--monkeys-color-menu-surface`    | Select/Dropdown content                         | `240 20% 99.02%` | `270 3.85% 10.2%` |
| `--monkeys-color-dialog-surface`  | Dialog/AlertDialog content                      | `240 20% 99.02%` | `240 6% 7%`       |

宿主若有经过评审的产品主题差异，可以在加载 `styles.css` 后显式覆盖对应的 `--monkeys-*` variable；不要通过 feature className 或泛化页面 Token 重写 canonical surface。未加载 `styles.css` 属于集成错误。

## Storybook

本仓库内置 Storybook，用于预览和调试组件：

```bash
npm run storybook
```

构建静态 Storybook：

```bash
npm run storybook:build
```

## 开发

```bash
# 安装依赖
npm install

# 开发模式
npm run dev

# 构建生产产物
npm run build

# 类型检查
npm run typecheck

# 合同、token compiler 与标准组件 props 测试
npm run test

# 检查实际发布文件
npm run pack:check

# 代码检查
npm run lint
```

## 发布

包名：`@inf-monkeys-tech/monkeys-design`。本仓库已经启用 GitHub Actions 自动发版，本地只负责提交代码、提升版本号和推送 tag；不需要在本地执行 `npm publish`，也不要提交 `.npmrc` 或 npm token。

自动发版规则：

- 推送任意 branch：自动发布唯一的 `canary` 测试包。
- 推送 `vX.Y.Z` tag：自动发布正式 `latest` 包。
- 发布认证通过 npm Trusted Publisher / GitHub Actions OIDC 完成，不依赖长期 npm token。

### 测试包（canary）

所有 branch push 都会触发 CI 发布一个唯一的 `canary` 测试包，不会更新默认的 `latest` 包。需要验证最新测试包时，安装 `canary` dist-tag：

```bash
npm install @inf-monkeys-tech/monkeys-design@canary
```

### 正式发布（latest）

维护者在 `main` 的干净工作区提升版本号，按发布范围选择 `patch`、`minor` 或 `major`，然后把分支和 tag 一起推到远端即可：

```bash
npm version patch
git push origin main --follow-tags
```

`npm version patch` 会更新 `package.json`、生成版本提交，并创建对应的 `vX.Y.Z` tag。CI 只会在 `vX.Y.Z` tag 与 `package.json` 版本一致时发布正式包，并使用 `latest` dist-tag。

如果要发布 minor 或 major 版本，将第一行改成：

```bash
npm version minor
# 或
npm version major
```

### npm Trusted Publisher

当前 npmjs 的 `@inf-monkeys-tech/monkeys-design` 包已经配置 Trusted Publisher/OIDC，绑定：

- Repository: `inf-monkeys/monkeys-design`
- Workflow filename: `npm-publish.yml`（npm 表单只填文件名；文件位于 `.github/workflows/npm-publish.yml`）

Trusted Publisher 配好后，CI 可以通过 OIDC 发布包，不需要在本地或仓库中保存 npm token。
由于当前 GitHub 仓库是 private，workflow 会显式关闭 npm provenance；Trusted Publishing/OIDC 仍然生效。
