gea-ui-components

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

@geajs/ui Components

@geajs/ui 组件库

@geajs/ui is a component library for the Gea framework that pairs Tailwind CSS styling with Zag.js state machines for accessible, interactive components. It provides ~35 ready-to-use components: simple styled primitives (Button, Card, Input) and behavior-rich widgets (Select, Dialog, Tabs, Toast).
Read
reference.md
in this skill directory for the full component API with props tables.
@geajs/ui 是面向 Gea 框架的组件库,结合了 Tailwind CSS 样式与 Zag.js 状态机,可提供无障碍、高交互性的组件。它内置了约35个开箱即用的组件:基础样式原语(Button、Card、Input)以及具备丰富交互能力的组件(Select、Dialog、Tabs、Toast)。
你可以阅读本 skill 目录下的
reference.md
文件,查看包含 props 表格的完整组件 API 文档。

Setup

配置步骤

Install

安装

bash
npm install @geajs/core @geajs/ui
npm install -D vite @geajs/vite-plugin tailwindcss @tailwindcss/vite
bash
npm install @geajs/core @geajs/ui
npm install -D vite @geajs/vite-plugin tailwindcss @tailwindcss/vite

Vite config

Vite 配置

js
import { defineConfig } from 'vite'
import { geaPlugin } from '@geajs/vite-plugin'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [geaPlugin(), tailwindcss()],
})
js
import { defineConfig } from 'vite'
import { geaPlugin } from '@geajs/vite-plugin'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [geaPlugin(), tailwindcss()],
})

Import styles

引入样式

ts
import '@geajs/ui/style.css'
ts
import '@geajs/ui/style.css'

Import components

引入组件

tsx
import { Button, Select, Dialog, Toaster, ToastStore } from '@geajs/ui'
tsx
import { Button, Select, Dialog, Toaster, ToastStore } from '@geajs/ui'

Component Categories

组件分类

Simple styled components (no state machine)

基础样式组件(无状态机)

Button, Card (+ CardHeader/CardTitle/CardDescription/CardContent/CardFooter), Input, Textarea, Label, Badge, Alert (+ AlertTitle/AlertDescription), Separator, Skeleton.
These are thin Gea
Component
wrappers with Tailwind classes. They accept
class
for custom styling and
children
for content.
Button、Card(含 CardHeader/CardTitle/CardDescription/CardContent/CardFooter)、Input、Textarea、Label、Badge、Alert(含 AlertTitle/AlertDescription)、Separator、Skeleton。
这些是基于 Gea
Component
封装的轻量组件,内置 Tailwind 类名,支持传入
class
自定义样式,传入
children
定义内容。

Zag-powered components (interactive)

Zag 驱动组件(具备交互能力)

Accordion, Avatar, Checkbox, Clipboard, Collapsible, Combobox, Dialog, FileUpload, HoverCard, Menu, NumberInput, Pagination, PinInput, Popover, Progress, RadioGroup, RatingGroup, Select, Slider, Switch, Tabs, TagsInput, Toast (Toaster + ToastStore), ToggleGroup, Tooltip, TreeView.
These extend
ZagComponent
— a base class that connects Zag.js state machines to Gea's reactivity system. They manage ARIA attributes, keyboard interactions, and focus automatically.
Accordion、Avatar、Checkbox、Clipboard、Collapsible、Combobox、Dialog、FileUpload、HoverCard、Menu、NumberInput、Pagination、PinInput、Popover、Progress、RadioGroup、RatingGroup、Select、Slider、Switch、Tabs、TagsInput、Toast(Toaster + ToastStore)、ToggleGroup、Tooltip、TreeView。
这些组件继承自
ZagComponent
—— 这是一个将 Zag.js 状态机与 Gea 响应式系统关联的基类,可自动管理 ARIA 属性、键盘交互和焦点逻辑。

Usage Patterns

使用示例

Basic usage

基础用法

tsx
import { Component } from '@geajs/core'
import { Button, Badge, Separator } from '@geajs/ui'

export default class App extends Component {
  template() {
    return (
      <div>
        <Button click={() => console.log('clicked')}>Save</Button>
        <Button variant="destructive">Delete</Button>
        <Button variant="outline" size="sm">Cancel</Button>
        <Badge variant="secondary">Draft</Badge>
        <Separator />
      </div>
    )
  }
}
tsx
import { Component } from '@geajs/core'
import { Button, Badge, Separator } from '@geajs/ui'

export default class App extends Component {
  template() {
    return (
      <div>
        <Button click={() => console.log('clicked')}>Save</Button>
        <Button variant="destructive">Delete</Button>
        <Button variant="outline" size="sm">Cancel</Button>
        <Badge variant="secondary">Draft</Badge>
        <Separator />
      </div>
    )
  }
}

Controlled components

受控组件

Zag-powered components follow a controlled pattern: pass
value
(or
checked
/
open
) and listen for changes via
onValueChange
(or
onCheckedChange
/
onOpenChange
). Store the value in a class field so Gea's reactivity keeps the UI in sync.
tsx
import { Component } from '@geajs/core'
import { Select, Switch, Slider } from '@geajs/ui'

export default class Settings extends Component {
  theme = ''
  darkMode = false
  volume = 50

  template() {
    return (
      <div>
        <Select
          label="Theme"
          items={[
            { value: 'light', label: 'Light' },
            { value: 'dark', label: 'Dark' },
            { value: 'system', label: 'System' },
          ]}
          value={this.theme ? [this.theme] : []}
          onValueChange={(d: any) => { this.theme = d.value[0] || '' }}
        />
        <p>Selected: {this.theme || '(none)'}</p>

        <Switch
          label="Dark mode"
          checked={this.darkMode}
          onCheckedChange={(d: any) => { this.darkMode = d.checked }}
        />

        <Slider
          label="Volume"
          value={[this.volume]}
          min={0}
          max={100}
          onValueChange={(d: any) => { this.volume = d.value[0] }}
        />
      </div>
    )
  }
}
Zag 驱动组件遵循受控模式:传入
value
(或
checked
/
open
),通过
onValueChange
(或
onCheckedChange
/
onOpenChange
)监听值变化。将值存储在类字段中,Gea 的响应式能力会自动同步 UI。
tsx
import { Component } from '@geajs/core'
import { Select, Switch, Slider } from '@geajs/ui'

export default class Settings extends Component {
  theme = ''
  darkMode = false
  volume = 50

  template() {
    return (
      <div>
        <Select
          label="Theme"
          items={[
            { value: 'light', label: 'Light' },
            { value: 'dark', label: 'Dark' },
            { value: 'system', label: 'System' },
          ]}
          value={this.theme ? [this.theme] : []}
          onValueChange={(d: any) => { this.theme = d.value[0] || '' }}
        />
        <p>Selected: {this.theme || '(none)'}</p>

        <Switch
          label="Dark mode"
          checked={this.darkMode}
          onCheckedChange={(d: any) => { this.darkMode = d.checked }}
        />

        <Slider
          label="Volume"
          value={[this.volume]}
          min={0}
          max={100}
          onValueChange={(d: any) => { this.volume = d.value[0] }}
        />
      </div>
    )
  }
}

Toast notifications

Toast 通知

Toast uses a static
ToastStore
class and a
<Toaster />
component rendered once at the root.
tsx
import { Component } from '@geajs/core'
import { Button, Toaster, ToastStore } from '@geajs/ui'

export default class App extends Component {
  save() {
    ToastStore.success({ title: 'Saved!', description: 'Changes persisted.' })
  }

  template() {
    return (
      <div>
        <Button click={this.save}>Save</Button>
        <Toaster />
      </div>
    )
  }
}
Toast methods:
ToastStore.success(opts)
,
.error(opts)
,
.info(opts)
,
.loading(opts)
,
.create(opts)
,
.dismiss(id?)
.
Toast 依赖静态
ToastStore
类和根节点处全局渲染一次的
<Toaster />
组件。
tsx
import { Component } from '@geajs/core'
import { Button, Toaster, ToastStore } from '@geajs/ui'

export default class App extends Component {
  save() {
    ToastStore.success({ title: 'Saved!', description: 'Changes persisted.' })
  }

  template() {
    return (
      <div>
        <Button click={this.save}>Save</Button>
        <Toaster />
      </div>
    )
  }
}
Toast 方法包括:
ToastStore.success(opts)
.error(opts)
.info(opts)
.loading(opts)
.create(opts)
.dismiss(id?)

Dialog

Dialog 对话框

tsx
<Dialog title="Confirm" description="Are you sure?" triggerLabel="Open">
  <p>Dialog body content goes here.</p>
</Dialog>
tsx
<Dialog title="Confirm" description="Are you sure?" triggerLabel="Open">
  <p>Dialog body content goes here.</p>
</Dialog>

Tabs

Tabs 标签页

tsx
<Tabs
  defaultValue="account"
  items={[
    { value: 'account', label: 'Account', content: <p>Account settings</p> },
    { value: 'security', label: 'Security', content: <p>Security settings</p> },
  ]}
/>
tsx
<Tabs
  defaultValue="account"
  items={[
    { value: 'account', label: 'Account', content: <p>Account settings</p> },
    { value: 'security', label: 'Security', content: <p>Security settings</p> },
  ]}
/>

Cards with form inputs

带表单输入的 Card 卡片

tsx
<Card>
  <CardHeader>
    <CardTitle>Profile</CardTitle>
    <CardDescription>Update your details</CardDescription>
  </CardHeader>
  <CardContent>
    <Label htmlFor="name">Name</Label>
    <Input inputId="name" placeholder="Enter your name" value={this.name} />
  </CardContent>
  <CardFooter>
    <Button click={this.save}>Save</Button>
  </CardFooter>
</Card>
tsx
<Card>
  <CardHeader>
    <CardTitle>Profile</CardTitle>
    <CardDescription>Update your details</CardDescription>
  </CardHeader>
  <CardContent>
    <Label htmlFor="name">Name</Label>
    <Input inputId="name" placeholder="Enter your name" value={this.name} />
  </CardContent>
  <CardFooter>
    <Button click={this.save}>Save</Button>
  </CardFooter>
</Card>

Theming

主题定制

@geajs/ui uses CSS custom properties for theming. Override them in your stylesheet:
css
:root {
  --primary: 222 47% 11%;
  --primary-foreground: 210 40% 98%;
  --radius: 0.75rem;
}
Dark mode activates with the
dark
class on
<html>
:
html
<html class="dark">
Base color CSS variables:
--background
,
--foreground
.
Color CSS variables related to elements containing text (styled with
-foreground
counterpart):
--primary
,
--secondary
,
--muted
,
--accent
,
--destructive
,
--card
,
--popover
.
Color CSS variables related to elements without text:
--border
,
--input
(input field borders),
--ring
(focus ring color),
--dialog-background
.
Other CSS variables:
--radius
(base border radius).
@geajs/ui 使用 CSS 自定义属性实现主题能力,你可以在自己的样式表中覆盖这些属性:
css
:root {
  --primary: 222 47% 11%;
  --primary-foreground: 210 40% 98%;
  --radius: 0.75rem;
}
<html>
标签添加
dark
类即可启用深色模式:
html
<html class="dark">
基础色 CSS 变量:
--background
--foreground
与文本容器相关的 CSS 变量(都有对应的
-foreground
变量):
--primary
--secondary
--muted
--accent
--destructive
--card
--popover
与非文本元素相关的 CSS 变量:
--border
--input
(输入框边框)、
--ring
(焦点环颜色)、
--dialog-background
其他 CSS 变量:
--radius
(基础圆角值)。

Styling components

组件样式自定义

  • Pass
    class
    to any component for additional Tailwind classes.
  • Use
    data-part
    and
    data-state
    selectors for fine-grained CSS overrides on Zag-powered components:
css
.select-trigger[data-state="open"] { border-color: hsl(var(--ring)); }
.switch-control[data-state="checked"] { background: hsl(var(--primary)); }
  • 给任意组件传入
    class
    即可添加额外的 Tailwind 类名。
  • 对于 Zag 驱动组件,你可以使用
    data-part
    data-state
    选择器实现更细粒度的样式覆盖:
css
.select-trigger[data-state="open"] { border-color: hsl(var(--ring)); }
.switch-control[data-state="checked"] { background: hsl(var(--primary)); }

Extending: Creating Custom Zag Components

扩展:创建自定义 Zag 组件

To wrap a new Zag.js machine, extend
ZagComponent
and implement five methods:
tsx
import * as myWidget from '@zag-js/my-widget'
import { normalizeProps } from '@zag-js/vanilla'
import { ZagComponent } from '@geajs/ui'
import type { SpreadMap } from '@geajs/ui'

export default class MyWidget extends ZagComponent {
  declare open: boolean

  createMachine() { return myWidget.machine }

  getMachineProps(props: any) {
    return {
      id: this.id,
      // map Gea props → Zag machine props
      onOpenChange: (d: any) => { this.open = d.open; props.onOpenChange?.(d) },
    }
  }

  connectApi(service: any) {
    return myWidget.connect(service, normalizeProps)
  }

  getSpreadMap(): SpreadMap {
    return {
      '[data-part="root"]': 'getRootProps',
      '[data-part="trigger"]': 'getTriggerProps',
      '[data-part="content"]': 'getContentProps',
    }
  }

  syncState(api: any) { this.open = api.open }

  template(props: any) {
    return (
      <div data-part="root">
        <button data-part="trigger">Toggle</button>
        <div data-part="content">{props.children}</div>
      </div>
    )
  }
}
The
SpreadMap
maps CSS selectors to Zag API getter names (strings) or functions
(api, el) => props
. After each render,
ZagComponent
applies Zag's ARIA/event attributes to matching DOM elements via
spreadProps
.
如果要封装新的 Zag.js 状态机,只需继承
ZagComponent
并实现5个方法即可:
tsx
import * as myWidget from '@zag-js/my-widget'
import { normalizeProps } from '@zag-js/vanilla'
import { ZagComponent } from '@geajs/ui'
import type { SpreadMap } from '@geajs/ui'

export default class MyWidget extends ZagComponent {
  declare open: boolean

  createMachine() { return myWidget.machine }

  getMachineProps(props: any) {
    return {
      id: this.id,
      // 将 Gea props 映射为 Zag 状态机 props
      onOpenChange: (d: any) => { this.open = d.open; props.onOpenChange?.(d) },
    }
  }

  connectApi(service: any) {
    return myWidget.connect(service, normalizeProps)
  }

  getSpreadMap(): SpreadMap {
    return {
      '[data-part="root"]': 'getRootProps',
      '[data-part="trigger"]': 'getTriggerProps',
      '[data-part="content"]': 'getContentProps',
    }
  }

  syncState(api: any) { this.open = api.open }

  template(props: any) {
    return (
      <div data-part="root">
        <button data-part="trigger">Toggle</button>
        <div data-part="content">{props.children}</div>
      </div>
    )
  }
}
SpreadMap
会将 CSS 选择器映射为 Zag API 的 getter 名称(字符串)或函数
(api, el) => props
。每次渲染后,
ZagComponent
会通过
spreadProps
将 Zag 生成的 ARIA/事件属性应用到匹配的 DOM 元素上。

cn
Utility

cn
工具函数

Merge Tailwind classes (powered by
clsx
+
tailwind-merge
):
ts
import { cn } from '@geajs/ui'

const cls = cn('px-4 py-2', active && 'bg-primary', className)
用于合并 Tailwind 类名(基于
clsx
+
tailwind-merge
实现):
ts
import { cn } from '@geajs/ui'

const cls = cn('px-4 py-2', active && 'bg-primary', className)