# Elastic UI 设计系统

## Elastic UI 设计系统界面预览

按消息或严重级别筛选事件流，保存单条事件，清空筛选结果，并切换浅色或深色外观。

## Elastic UI 设计系统设计参考

Elastic UI 123.0.0，搭配 Borealis 主题 8.1.0 和 React 18.3.1。表格使用已发布主题 JSON 中的名称；这些是导出键，并非 useEuiTheme 对象中的实际路径。实际预览使用原生的 React 主题 API。

### Borealis 语义颜色

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| euiColorPrimary | light: #0B64DD; dark: #61A2FF | 已发布的主题导出名称。在 React 中，应通过 useEuiTheme 使用对应的语义角色，而不是硬编码这些值。 |
| euiColorSuccess | light: #008A5E; dark: #24C292 | 已发布的主题导出名称。在 React 中，应通过 useEuiTheme 使用对应的语义角色，而不是硬编码这些值。 |
| euiColorWarning | light: #FACB3D; dark: #FACB3D | 已发布的主题导出名称。在 React 中，应通过 useEuiTheme 使用对应的语义角色，而不是硬编码这些值。 |
| euiColorDanger | light: #C61E25; dark: #F6726A | 已发布的主题导出名称。在 React 中，应通过 useEuiTheme 使用对应的语义角色，而不是硬编码这些值。 |
| euiColorEmptyShade | light: #FFFFFF; dark: #111C2C | 已发布的主题导出名称。在 React 中，应通过 useEuiTheme 使用对应的语义角色，而不是硬编码这些值。 |
| euiColorFullShade | light: #07101F; dark: #FFFFFF | 已发布的主题导出名称。在 React 中，应通过 useEuiTheme 使用对应的语义角色，而不是硬编码这些值。 |
| euiPageBackgroundColor | light: #ECF1F9; dark: #07101F | 已发布的主题导出名称。在 React 中，应通过 useEuiTheme 使用对应的语义角色，而不是硬编码这些值。 |
| euiTextColor | light: #384861; dark: #B4C1D5 | 已发布的主题导出名称。在 React 中，应通过 useEuiTheme 使用对应的语义角色，而不是硬编码这些值。 |
| euiTextSubduedColor | light: #516381; dark: #98A8C3 | 已发布的主题导出名称。在 React 中，应通过 useEuiTheme 使用对应的语义角色，而不是硬编码这些值。 |
### 间距比例

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| euiSizeXS | 4px | 使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏；在此主题导出中，基准值为 16px。 |
| euiSizeS | 8px | 使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏；在此主题导出中，基准值为 16px。 |
| euiSizeM | 12px | 使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏；在此主题导出中，基准值为 16px。 |
| euiSize | 16px | 使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏；在此主题导出中，基准值为 16px。 |
| euiSizeL | 24px | 使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏；在此主题导出中，基准值为 16px。 |
| euiSizeXL | 32px | 使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏；在此主题导出中，基准值为 16px。 |
| euiSizeXXL | 40px | 使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏；在此主题导出中，基准值为 16px。 |
### 排版

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| euiFontSizeXS | 12px | 已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。 |
| euiFontSizeS | 14px | 已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。 |
| euiFontSize | 14px | 已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。 |
| euiFontSizeM | 16px | 已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。 |
| euiFontSizeL | 20px | 已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。 |
| euiFontSizeXL | 24px | 已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。 |
| euiFontSizeXXL | 30px | 已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。 |
| euiLineHeight | 1.5 | 已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。 |
### 边框与形状

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| euiBorderWidthThin | 1px | 区分行内元素、控件、面板和外框的形状；若所有元素都使用同一个圆角半径，就会抹去这种层级差异。 |
| euiBorderWidthThick | 2px | 区分行内元素、控件、面板和外框的形状；若所有元素都使用同一个圆角半径，就会抹去这种层级差异。 |
| euiBorderColor | #E3E8F2 | 区分行内元素、控件、面板和外框的形状；若所有元素都使用同一个圆角半径，就会抹去这种层级差异。 |
| euiBorderRadiusInline | 4px | 区分行内元素、控件、面板和外框的形状；若所有元素都使用同一个圆角半径，就会抹去这种层级差异。 |
| euiBorderRadiusControl | 8px | 区分行内元素、控件、面板和外框的形状；若所有元素都使用同一个圆角半径，就会抹去这种层级差异。 |
| euiBorderRadiusPanel | 12px | 区分行内元素、控件、面板和外框的形状；若所有元素都使用同一个圆角半径，就会抹去这种层级差异。 |
| euiBorderRadiusFrame | 16px | 区分行内元素、控件、面板和外框的形状；若所有元素都使用同一个圆角半径，就会抹去这种层级差异。 |
### 焦点与动效

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| euiFocusRingSize | 2px | 保留原生焦点和动效行为，不要覆盖内部的 .eui 选择器。 |
| euiFocusBackgroundColor | #ECF1F9 | 保留原生焦点和动效行为，不要覆盖内部的 .eui 选择器。 |
| euiAnimSpeedExtraFast | 90ms | 保留原生焦点和动效行为，不要覆盖内部的 .eui 选择器。 |
| euiAnimSpeedFast | 150ms | 保留原生焦点和动效行为，不要覆盖内部的 .eui 选择器。 |
| euiAnimSpeedNormal | 250ms | 保留原生焦点和动效行为，不要覆盖内部的 .eui 选择器。 |
| euiAnimSpeedSlow | 350ms | 保留原生焦点和动效行为，不要覆盖内部的 .eui 选择器。 |
### 此浏览器中原生 API 的约定

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| EuiProvider | colorMode="light" \| "dark" | 提供主题上下文和 Emotion 样式。 |
| useEuiTheme | { euiTheme } | 为自定义元素和 EuiHealth 解析语义颜色。 |
| EuiBasicTable | items · itemId · columns | 使用稳定 ID 渲染受控的可见集合。 |
| EuiFieldSearch | value · onChange · incremental | 应用程序会筛选包含六个事件的本地集合。 |
| EuiButtonGroup | options · idSelected · onChange | 受控的严重性筛选器，并显示图例。 |
| EuiHealth | color + readable child label | 状态圆点用于补充严重性文字，而非取而代之。 |
## Elastic UI 设计系统完整指南

实用指南，介绍如何为真实界面选择、应用和评审此系统。

完整指导、实现起点和源代码链接。

## Elastic UI 设计系统浏览器结构

### 调查工作区，而非通用仪表板

> 此示例包含三项精简摘要统计数据和一个事件流。搜索框和严重性筛选组可缩小事件流范围；每行都提供独立的保存操作。将操作放在对应事件旁边，读者便能在不离开筛选上下文的情况下收集证据。摘要明确统计的是示例事件，而非实时流量。

### 使用当前主题边界

此示例搭配 EUI 123.0.0 和 Borealis 8.1.0。参考值取自已发布的主题，而非旧版 Amsterdam 截图。EuiProvider 管理视觉模式和样式上下文。应用状态独立于主题计算，因此更改外观不会重置已保存的事件或当前查询。

## Elastic UI 设计系统语义颜色角色

### 让严重性与颜色角色建立有意义的对应关系

在此应用中，Info 使用 primary 角色，Warning 使用 warning 角色，Error 使用 danger 角色。EuiHealth 会在圆点旁显示严重性文字。此映射属于应用策略，并不意味着每个主色元素都表示信息严重性。错误数量和事件标签仍需使用易读的文字。

### 根据当前主题解析颜色

Screen 会调用 useEuiTheme，并使用解析出的颜色设置画布、正文文字和健康状态指示器。导出表格列出了成对的浅色和深色模式值，以便清楚地呈现差异。不要在两种模式中复制使用同一个十六进制值，也不要通过定位生成的 Emotion 类名来强行匹配。

## Elastic UI 设计系统中的字体与数值层级

### 先为统计数据说明背景，再突出数字

EuiStat 将 6 个示例事件、1 个错误事件和已保存数量与相应描述配对。单独的数字可能含义不明。EuiTitle 用于引出调查内容，EuiText 则提供时间范围并说明数据为示例数据。事件流标题使用较小级别的标题，而不是第二个页面标题。

### 在紧凑布局中保持日志文字易读

Borealis 的基础导出字号为 14px，行高参考值为 1.5，并提供 12/16/20/24/30px 的字号阶梯。时间戳和服务名称使用固定列宽，事件消息则灵活占用剩余空间。此示例通过原生字体栈使用系统可用字体，不声称加载了远程 Inter 字体资源。

## Elastic UI 设计系统基础表格与受控筛选器

### 将展示与筛选分开处理

EuiBasicTable 接收已筛选的项目。列定义指定字段或自定义渲染器，itemId 则使用稳定的事件 ID。EuiFieldSearch 用于更新查询状态；EuiButtonGroup 用于更新所选严重性 ID。应用程序会先组合两个筛选条件，再将可见集合传给表格。

1. 输入服务名称或消息片段。

2. 选择 Error 或 Warning 以组合严重性筛选条件。

3. 使用对应行的操作保存匹配事件。

4. 清除筛选器，确认已保存的事件仍有标记。

### 根据事件 ID 保存，而非根据行位置

“保存事件”操作会在本地集合中切换某个 ID。排序或筛选不应因数组位置变化而使书签指向其他事件。此示例会在消息中显示“已在本地保存”，并将操作更改为“取消保存”，明确呈现可撤销的操作结果。

## Elastic UI 设计系统间距与响应式数据

### 在各个分组中使用原生间距比例

已发布的间距尺寸为 4、8、12、16、24、32 和 40px。EuiSpacer 和 EuiFlexGroup 用于设置摘要统计、筛选器与事件表格之间的间距。EuiPanel 提供容器形状；主题将面板的 12px 圆角与控件的 8px 圆角及框架的 16px 圆角区分开来。

### 让页面适配移动设备视口

摘要和筛选器分组会通过原生 FlexGroup 行为重新排列。表格位于有界的水平滚动区域中，应用最小宽度为 640px；这样既能保持时间戳、消息与操作之间的对应关系，也能避免整个页面溢出。生产环境中的表格可根据实际列设置，选择文档中说明的响应式表格选项。

## Elastic UI 设计系统中的搜索、空状态与已保存状态

### 无结果搜索应提供直接的恢复操作

当查询不可能匹配任何结果时，显示“没有事件符合当前筛选条件”以及“清除筛选器”操作。重置查询和严重性筛选器后，六个示例事件会全部恢复，已保存的 ID 不受影响。表格上方的数量会通过礼貌型实时区域播报匹配事件总数。

### 本地保存不等同于事件工作流

保存事件会更改可见行的标签和已保存统计数据，但不会联系 Elasticsearch、创建事件或在刷新后保留数据。真实的调查产品应添加有文档说明的持久化端点，并在声称已保存集合能够持久保留之前，报告待处理或失败的响应。

## Elastic UI 设计系统中的无障碍表格操作

### 为筛选器设置独立名称，不要只依赖占位符

搜索框具有无障碍名称，按钮组则提供严重性图例。表格列具有描述性标题；每行操作都是原生的 EuiButtonEmpty。EuiHealth 包含文字，因此状态颜色绝不会成为唯一信息。替换示例数据时，请保留这些约定。

- 使用可见文字标明严重性。

- 保留表格说明和列名称。

- 让已保存状态与稳定的事件 ID 关联。

### 确保键盘操作状态与视觉状态一致

用户可以将焦点移至搜索框、选择严重性筛选器、访问行操作，并切换外观设置。2px 焦点环属于主题参考的一部分。保存一行后以及清除筛选器后，都应测试筛选结果；操作标签和已保存统计数据应始终保持一致。

## Elastic UI 设计系统设置与主题集成

### 安装文档中列出的对等依赖

官方安装指南使用 Yarn，并将 Borealis、Emotion、datemath 和 moment 与 EUI 一同列出。本示例运行于 React 18，且不会将组件树包裹在 StrictMode 中。以下设置明确保留这些约束，而不是不加说明地继承新生成应用中的 React 19。

### 将完整入口文件作为实现

以下示例包含 provider、数据集、受控状态、列渲染器、筛选控件和 createRoot 入口。Vite 脚手架会提供 index.html。此原生示例使用 esbuild 打包并在浏览器中完成检查；官方安装指南说明了 Vite 及其他非 Webpack 打包器的注意事项。请独立测试生产环境使用的打包器。

## Elastic UI 设计系统组件示例

### main.jsx — 完整的原生日志浏览器

EuiProvider 和 Borealis 支持受控事件表格，并提供严重级别/搜索筛选以及本地已保存事件状态。

```
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import {
  EuiProvider,
  EuiPanel,
  EuiTitle,
  EuiText,
  EuiSpacer,
  EuiFlexGroup,
  EuiFlexItem,
  EuiFieldSearch,
  EuiButtonGroup,
  EuiButtonEmpty,
  EuiBasicTable,
  EuiHealth,
  EuiStat,
  EuiSwitch,
  EuiCallOut,
  useEuiTheme,
} from "@elastic/eui";
const logs = [
  {
    id: 1,
    time: "09:42:18",
    service: "gateway",
    message: "Request completed",
    level: "Info",
    duration: 42,
  },
  {
    id: 2,
    time: "09:42:21",
    service: "worker",
    message: "Retry queued after timeout",
    level: "Warning",
    duration: 1200,
  },
  {
    id: 3,
    time: "09:42:24",
    service: "gateway",
    message: "Upstream connection refused",
    level: "Error",
    duration: 800,
  },
  {
    id: 4,
    time: "09:42:29",
    service: "indexer",
    message: "Batch indexed",
    level: "Info",
    duration: 180,
  },
  {
    id: 5,
    time: "09:42:32",
    service: "worker",
    message: "Job completed",
    level: "Info",
    duration: 94,
  },
  {
    id: 6,
    time: "09:42:36",
    service: "gateway",
    message: "Upstream connection restored",
    level: "Info",
    duration: 51,
  },
];
function Screen({ dark, setDark }) {
  const { euiTheme } = useEuiTheme();
  const [query, setQuery] = useState(""),
    [level, setLevel] = useState("all"),
    [saved, setSaved] = useState([]);
  const visible = logs.filter(
    (x) =>
      (level === "all" || x.level === level) &&
      (x.service + " " + x.message).toLowerCase().includes(query.toLowerCase()),
  );
  const colors = {
    Info: euiTheme.colors.primary,
    Warning: euiTheme.colors.warning,
    Error: euiTheme.colors.danger,
  };
  const columns = [
    { field: "time", name: "Time", width: "105px" },
    { field: "service", name: "Service", width: "105px" },
    {
      field: "message",
      name: "Event",
      render: (value, item) => (
        <span>
          {value}
          {saved.includes(item.id) ? " · Saved locally" : ""}
        </span>
      ),
    },
    {
      field: "level",
      name: "Level",
      width: "110px",
      render: (value) => <EuiHealth color={colors[value]}>{value}</EuiHealth>,
    },
    {
      name: "Action",
      width: "110px",
      render: (item) => (
        <EuiButtonEmpty
          size="xs"
          onClick={() =>
            setSaved((v) =>
              v.includes(item.id)
                ? v.filter((id) => id !== item.id)
                : [...v, item.id],
            )
          }
        >
          {saved.includes(item.id) ? "Unsave" : "Save event"}
        </EuiButtonEmpty>
      ),
    },
  ];
  return (
    <main
      style={{
        padding: 24,
        background: euiTheme.colors.backgroundBasePlain,
        color: euiTheme.colors.textParagraph,
      }}
    >
      <EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
        <EuiFlexItem>
          <EuiText size="xs">
            <p>ELASTIC UI · LOG EXPLORER</p>
          </EuiText>
          <EuiTitle size="m">
            <h1>Investigate gateway events</h1>
          </EuiTitle>
        </EuiFlexItem>
        <EuiFlexItem grow={false}>
          <EuiSwitch
            label="Dark mode"
            checked={dark}
            onChange={(e) => setDark(e.target.checked)}
          />
        </EuiFlexItem>
      </EuiFlexGroup>
      <EuiSpacer size="l" />
      <EuiFlexGroup>
        <EuiFlexItem>
          <EuiPanel>
            <EuiStat title="6" description="Sample events" titleSize="m" />
          </EuiPanel>
        </EuiFlexItem>
        <EuiFlexItem>
          <EuiPanel>
            <EuiStat
              title="1"
              description="Error event"
              titleSize="m"
              titleColor="danger"
            />
          </EuiPanel>
        </EuiFlexItem>
        <EuiFlexItem>
          <EuiPanel>
            <EuiStat
              title={String(saved.length)}
              description="Saved in this preview"
              titleSize="m"
            />
          </EuiPanel>
        </EuiFlexItem>
      </EuiFlexGroup>
      <EuiSpacer size="l" />
      <EuiPanel>
        <EuiTitle size="xs">
          <h2>Event stream</h2>
        </EuiTitle>
        <EuiText size="s">
          <p>09:42:18–09:42:36 · fictional data · no live telemetry</p>
        </EuiText>
        <EuiSpacer size="m" />
        <EuiFlexGroup alignItems="center">
          <EuiFlexItem>
            <EuiFieldSearch
              fullWidth
              aria-label="Search log events"
              placeholder="Search service or message"
              value={query}
              onChange={(e) => setQuery(e.target.value)}
              incremental
            />
          </EuiFlexItem>
          <EuiFlexItem grow={false}>
            <EuiButtonGroup
              legend="Filter severity"
              options={[
                { id: "all", label: "All" },
                { id: "Warning", label: "Warning" },
                { id: "Error", label: "Error" },
              ]}
              idSelected={level}
              onChange={(id) => setLevel(id)}
            />
          </EuiFlexItem>
        </EuiFlexGroup>
        <EuiSpacer size="m" />
        <EuiText size="xs">
          <p aria-live="polite">{visible.length} matching events</p>
        </EuiText>
        <div style={{ overflowX: "auto", maxWidth: "100%" }}>
          <div style={{ minWidth: 640 }}>
            <EuiBasicTable
                  responsiveBreakpoint={false}
              items={visible}
              itemId="id"
              columns={columns}
              tableCaption="Fictional application events"
              noItemsMessage="No events match the current filters."
            />
          </div>
        </div>
        {!visible.length && (
          <EuiButtonEmpty
            onClick={() => {
              setLevel("all");
              setQuery("");
            }}
          >
            Clear filters
          </EuiButtonEmpty>
        )}
      </EuiPanel>
      <EuiSpacer size="l" />
      <EuiCallOut size="s" title="Local investigation workspace">
        Saving an event only changes this preview. No query is sent to
        Elasticsearch and no incident is created.
      </EuiCallOut>
      <EuiSpacer size="m" />
      <EuiText size="xs">
        <p>
          Native Elastic UI 123.0.0 · Borealis 8.1.0 theme · controlled filters
          and table actions.
        </p>
      </EuiText>
    </main>
  );
}
function App() {
  const [dark, setDark] = useState(false);
  return (
    <EuiProvider colorMode={dark ? "dark" : "light"}>
      <Screen dark={dark} setDark={setDark} />
    </EuiProvider>
  );
}
createRoot(document.getElementById("root")).render(<App />);
```

代码参考

- 在 React 脚手架中保存为 src/main.jsx。此入口不会启用 StrictMode。

- 640px 的表格限定宽度属于应用层 CSS；所有组件视觉样式均由 EUI 提供。

### 与主题联动的自定义说明

在 provider 内使用当前主题，避免自定义标记依赖生成的组件类名。

```
import React from 'react';
import { useEuiTheme } from '@elastic/eui';
export function InvestigationNote({ children }) {
  const { euiTheme } = useEuiTheme();
  return <aside style={{ padding: euiTheme.size.m, color: euiTheme.colors.textParagraph, borderLeft: `2px solid ${euiTheme.colors.primary}` }}>{children}</aside>;
}
```

代码参考

- 在 EuiProvider 下渲染 InvestigationNote。说明会继承所选的视觉模式。

## Elastic UI 设计系统 Agent 设置

### 实现起点

EUI 官方指南说明了 Yarn 安装方式及对 React 18 的支持。非 Webpack 打包器存在文档所述的注意事项；此原生示例使用 esbuild 打包并经过浏览器测试。请使用脚手架支持的 Node.js 版本。

```
yarn create vite elastic-investigation --template react
cd elastic-investigation
yarn add react@18.3.1 react-dom@18.3.1 @elastic/eui@123.0.0 @elastic/eui-theme-borealis@8.1.0 @elastic/datemath @emotion/react @emotion/css moment
# Replace src/main.jsx with the complete example above.
yarn dev
```

### 适用于你项目的提示词

```
使用 Elastic UI 123.0.0、Borealis 8.1.0 和 React 18 构建日志调查界面。阅读提供的主题导出文件和原生源代码。使用 EuiProvider、受控的 EuiBasicTable、无障碍搜索和严重级别筛选。按稳定的事件 ID 保存，在筛选时保留书签，并明确区分本地 UI 状态与真实的 Elasticsearch 查询或持久化响应。确保表格在移动设备上保持在容器内。
```

## Elastic UI 设计系统来源与范围

由 OpenDesign 编写的指南，以及使用 Borealis 8.1.0 和原生 Elastic UI 123.0.0 构建的虚构本地日志浏览器。示例事件并非实时遥测数据，保存操作也仅保存在内存中。EUI 有其自身的 Elastic/SSPL 许可选择；请根据你的发行方式查阅官方软件包许可证。

- 官方安装指南 Provider、安装、peer 依赖和环境支持。

- 基础表格 API 列定义、渲染器和原生表格行为。

- Borealis 浅色主题导出 此参考使用的版本化数值。

- EUI 源码与许可证 原生实现和许可信息。

## Elastic UI 设计系统相关资源

### Emplifi Soul

阅读 Soul 的数据表规范，了解其 6 像素间距、分层配色角色和紧凑的 Inter 字体。比较批量选择、缺失数据表示和汇总行，然后查看经过核实的私有 React 包入口。

### Esri Calcite

Calcite 的实用参考，聚焦于空间工具：语义化界面表面和蓝色操作、紧凑的检查面板、真实的输入 API，以及完整的本地站点审查示例。

### GitLab Pajamas

面向技术工作流的 Pajamas 指南：真实的语义令牌、流畅的字号与间距、分阶段的流水线检查器、清晰易读的失败输出，以及明确限定在本地的重试模拟。


- [实现参考](https://eui.elastic.co/docs/components/tables/basic/)
- [来源](https://cdn.jsdelivr.net/npm/@elastic/eui-theme-borealis@8.1.0/lib/eui_theme_borealis_light.json)
- [来源](https://eui.elastic.co/docs/components/tables/basic/)
- [代码参考](https://eui.elastic.co/docs/components/tables/basic/)
- [代码参考](https://eui.elastic.co/docs/getting-started/setup/)
- [官方安装指南 Provider、安装、peer 依赖和环境支持。](https://eui.elastic.co/docs/getting-started/setup/)
- [基础表格 API 列定义、渲染器和原生表格行为。](https://eui.elastic.co/docs/components/tables/basic/)
- [Borealis 浅色主题导出 此参考使用的版本化数值。](https://cdn.jsdelivr.net/npm/@elastic/eui-theme-borealis@8.1.0/lib/eui_theme_borealis_light.json)
- [EUI 源码与许可证 原生实现和许可信息。](https://github.com/elastic/eui)
