Skip to content

yanUEd/figma-react

Repository files navigation

figma-react-layout

AI-native Design-as-Code layout components inspired by Figma Auto Layout

一套受 Figma Auto Layout 启发的 React 布局组件库,让"设计稿"这个中间产物消失,让设计语言与前端代码语言直接对齐。

🚀 特性

  • 🎨 Figma 对齐: 复刻 Figma Auto Layout 的使用体验
  • 🔧 零配置: 开箱即用,合理的默认值和智能回退机制
  • 📱 TypeScript: 完整的类型提示和检查
  • 🎯 语义化: 直观的属性名,一个属性表达完整意图
  • 🎨 Token 驱动: 统一的设计token确保视觉一致性
  • ⚡ 高性能: 最小化重复计算,优化重渲染

📦 安装

npm install figma-react-layout
#
yarn add figma-react-layout
#
pnpm add figma-react-layout

依赖要求

  • React 16.8+ (需要 hooks 支持)
  • Styled Components

🔧 基础使用

import { Box, Column, Row, ZStack } from 'figma-react-layout';

function App() {
  const handleLogin = () => console.log('Login clicked');
  const handleCancel = () => console.log('Cancel clicked');

  return (
    <Column gap="$md" padding="$xl" alignment="center-center" minHeight="100vh">
      <Text type="title-lg">欢迎回来</Text>

      <Column gap="$sm" width="320px">
        <Input label="邮箱" />
        <Input label="密码" type="password" />
      </Column>

      <Row gap="$sm">
        <Button variant="secondary" onClick={handleCancel}>取消</Button>
        <Button variant="primary" onClick={handleLogin}>登录</Button>
      </Row>
    </Column>
  );
}

🖱️ onClick 事件处理

所有 figma-react-layout 组件都支持标准的 React onClick 事件处理器,就像普通 HTML 元素一样。

基础用法

import { Box, Column, Row, ZStack } from 'figma-react-layout';

function ClickableExample() {
  const handleBoxClick = (event) => {
    console.log('Box clicked!', event.target);
  };

  const handleColumnClick = () => {
    alert('Column container clicked!');
  };

  const handleRowClick = (event) => {
    event.stopPropagation(); // 阻止事件冒泡
    console.log('Row coordinates:', event.clientX, event.clientY);
  };

  return (
    <Column gap="20px" padding="20px">
      {/* Box 点击 */}
      <Box
        width="200px"
        height="80px"
        fill="blue"
        onClick={handleBoxClick}
        style={{ cursor: 'pointer' }}
      >
        Click this Box
      </Box>

      {/* Column 点击 */}
      <Column
        width="250px"
        height="120px"
        fill="green"
        onClick={handleColumnClick}
        alignment="center-center"
        gap="10px"
      >
        <Box height="30px" fill="white">Item 1</Box>
        <Box height="30px" fill="lightgreen">Item 2</Box>
      </Column>

      {/* Row 点击 */}
      <Row
        width="300px"
        height="80px"
        fill="orange"
        onClick={handleRowClick}
        alignment="center-center"
        gap="20px"
      >
        <Box width="60px" height="50px" fill="white">Left</Box>
        <Box width="60px" height="50px" fill="yellow">Right</Box>
      </Row>

      {/* ZStack 点击 */}
      <ZStack
        width="200px"
        height="120px"
        onClick={() => console.log('ZStack clicked')}
      >
        <Box position="absolute" width="100%" height="100%" fill="lightgray" />
        <Box
          position="absolute"
          top="50%"
          left="50%"
          transform="translate(-50%, -50%)"
          width="100px"
          height="60px"
          fill="purple"
          alignment="center-center"
        >
          Stacked
        </Box>
      </ZStack>
    </Column>
  );
}

高级用法

使用 useCallback 优化性能

import React, { useCallback } from 'react';

function OptimizedExample({ data }) {
  const handleClick = useCallback((event) => {
    console.log('Clicked with data:', data);
  }, [data]);

  return (
    <Box
      width="200px"
      height="80px"
      fill="blue"
      onClick={handleClick}
    >
      Optimized Click
    </Box>
  );
}

嵌套组件事件处理

function NestedExample() {
  const handleParentClick = () => console.log('Parent clicked');
  const handleChildClick = (event) => {
    event.stopPropagation(); // 阻止触发父级点击
    console.log('Child clicked only');
  };

  return (
    <Column
      width="300px"
      height="150px"
      fill="lightblue"
      onClick={handleParentClick}
    >
      <Box
        height="50px"
        fill="red"
        onClick={handleChildClick}
      >
        Child (only fires this)
      </Box>
      <Box
        height="50px"
        fill="green"
      >
        Child (bubbles to parent)
      </Box>
    </Column>
  );
}

可访问性支持

function AccessibleExample() {
  const handleClick = () => console.log('Accessible click');

  return (
    <Box
      width="200px"
      height="60px"
      fill="blue"
      onClick={handleClick}
      tabIndex={0}
      role="button"
      onKeyDown={(event) => {
        if (event.key === 'Enter' || event.key === ' ') {
          event.preventDefault();
          handleClick();
        }
      }}
      style={{ cursor: 'pointer' }}
    >
      Accessible Button
    </Box>
  );
}

测试 onClick 处理器

import { render, screen, fireEvent } from '@testing-library/react';
import { Box } from 'figma-react-layout';

test('onClick handler works', () => {
  const handleClick = jest.fn();

  render(
    <Box
      data-testid="clickable-box"
      onClick={handleClick}
    >
      Click me
    </Box>
  );

  const box = screen.getByTestId('clickable-box');
  fireEvent.click(box);

  expect(handleClick).toHaveBeenCalledTimes(1);
});

故障排除

onClick 未触发

问题: onClick 处理器不执行。

解决方案:

  1. 检查 onClick prop 是否正确传递
  2. 确保组件有 proper 尺寸
  3. 确保没有其他元素覆盖组件
  4. 检查 CSS pointer-events: none 未被应用

事件冒泡问题

问题: 父级 onClick 在子级点击时意外触发。

解决方案: 在子级处理器中使用 event.stopPropagation():

const handleChildClick = (event) => {
  event.stopPropagation();
  // 子级特定逻辑
};

性能问题

问题: onClick 处理器导致不必要重渲染。

解决方案: 使用 useCallback 优化:

const handleClick = useCallback(() => {
  // 处理器逻辑
}, [dependencies]);

📚 组件文档

Box - 基础容器组件

最通用的视觉容器,用于包裹内容或定义卡片、面板、背景块。

<Box
  width="200px"
  height="100px"
  minWidth="150px"
  maxWidth="300px"
  alignment="center-center"
  distribution="space-between"
  gap="$md"
  padding="x:$lg y:$sm"
  fill="$surface"
  strokeColor="$border"
  strokeWeight="$sm"
  strokeStyle="dashed"
  radius="$md"
  overflow="hidden"
>
  内容
</Box>

Column - 垂直布局容器

将多个子元素垂直堆叠,自动管理间距与对齐。

<Column gap="$md" alignment="center-center">
  <Box>项目 1</Box>
  <Box>项目 2</Box>
  <Box>项目 3</Box>
</Column>

Row - 水平布局容器

让元素在水平方向排列,支持自动换行。

<Row gap="$sm" wrap="true" alignment="center-center">
  <Tag>标签 1</Tag>
  <Tag>标签 2</Tag>
  <Tag>标签 3</Tag>
</Row>

ZStack - 层叠布局容器

将多个子元素按层叠方式排列,先定义的元素在上层。

<ZStack width="200px" height="150px">
  <Box alignment="top-right" fill="$error" radius="full" width="16px" height="16px">
    <Text color="white" type="body-xs">3</Text>
  </Box>
  <Button variant="ghost">
    <Icon name="notification" />
  </Button>
</ZStack>

🎨 Token 系统

基础 Token

:root {
  /* 间距 */
  --xs: 4px;
  --sm: 8px;
  --md: 16px;
  --lg: 24px;
  --xl: 32px;

  /* 颜色 */
  --primary: #0066ff;
  --secondary: #6c757d;
  --surface: #ffffff;
  --border: #e9ecef;
  --error: #dc3545;
  --muted: #6c757d;

  /* 圆角 */
  --none: 0;
  --sm: 4px;
  --md: 8px;
  --lg: 16px;
  --full: 50%;
}

使用 Token

<Box padding="$lg" fill="$surface" strokeColor="$border" radius="$md">
  {/* 自动转换为 CSS 变量 */}
</Box>

🎯 方向控制语法

边距控制

<Box padding="x:$lg y:$sm">     {/* 水平$lg,垂直$sm */}
<Box padding="top:20px right:10px bottom:20px left:10px"> {/* 精确控制 */}>

边框控制

<Box
  strokeColor="top:$primary right:$secondary"
  strokeWeight="top:2px right:1px"
  strokeStyle="top:solid right:dashed"
>
  {/* 上边框:2px solid primary */}
  {/* 右边框:1px dashed secondary */}
</Box>

圆角控制

<Box radius="top-right:$md bottom-left:$lg">
  {/* 右上角:md */}
  {/* 左下角:lg */}
  {/* 其他角:0 */}
</Box>

🤖 智能默认值

Stroke 属性智能默认值:当任何 stroke 属性被设置时,其他缺失属性自动补充默认值:

// 只设置颜色,自动补充:weight="1px", style="solid"
<Box strokeColor="$primary" />

// 只设置粗细,自动补充:color="$border", style="solid"
<Box strokeWeight="2px" />

// 只设置样式,自动补充:color="$border", weight="1px"
<Box strokeStyle="dashed" />

🔄 Overflow 智能映射

根据容器类型,overflow 属性智能映射到不同 CSS 实现:

// Box: 直接映射
<Box overflow="hidden" />  // → overflow: hidden

// Column: 垂直方向控制
<Column overflow="auto" />  // → overflow-x: visible, overflow-y: auto

// Row: 水平方向控制
<Row overflow="auto" />     // → overflow-x: auto, overflow-y: visible

📏 尺寸控制

基础尺寸

<Box width="fill" height="hug">  {/* 宽度填满,高度适应内容 */}
<Box width="200px" height="100px">  {/* 固定尺寸 */}

尺寸约束

<Box
  width="fill"
  minWidth="200px"
  maxWidth="400px"
  height="hug"
  minHeight="100px"
  maxHeight="300px"
>
  响应式尺寸约束
</Box>

🎨 自定义主题

import { ThemeProvider } from 'styled-components';

const customTheme = {
  spacing: {
    xs: '2px',
    sm: '4px',
    md: '8px',
    lg: '16px',
    xl: '24px',
  },
  colors: {
    primary: '#your-brand-color',
    surface: '#your-surface-color',
  },
};

function App() {
  return (
    <ThemeProvider theme={customTheme}>
      {/* 你的组件 */}
    </ThemeProvider>
  );
}

📖 完整 API 规范

组件概览

figma-react-layout 提供四个核心组件,全部属性命名、取值范围、语义均参考 Figma 属性面板。

共同属性

属性 类型 默认值 说明
width/height 'fill' | 'hug' | string 'hug' 尺寸控制
minWidth/maxWidth string | null null 宽度约束
minHeight/maxHeight string | null null 高度约束
alignment Alignment 'top-left' 9点对齐
gap string '0' 子元素间距
padding string '0' 内边距(支持方向控制)
fill string | null null 背景色
strokeColor string | null null 边框颜色(支持方向控制)
strokeWeight string | null null 边框粗细(支持方向控制)
strokeStyle StrokeStyle | null null 边框样式
radius string | null null 圆角(支持方向控制)
opacity string | null null 透明度
overflow Overflow 'hidden' 溢出处理

Row 特有属性

属性 类型 默认值 说明
wrap 'true' | 'false' 'false' 是否自动换行

组件专属说明

Column: 继承 <Box> 全部属性,但不支持 distribution 属性(垂直布局中 distribution 意义有限)。

ZStack: 继承 <Box> 大部分属性,但移除了 gapdistribution 属性(层叠布局中无实际意义)。

类型定义

// 9点对齐方式
type Alignment =
  | 'top-left' | 'top-center' | 'top-right'
  | 'center-left' | 'center-center' | 'center-right'
  | 'bottom-left' | 'bottom-center' | 'bottom-right';

// 分布方式
type Distribution = 'pack' | 'center' | 'space' | 'space-between';

// 溢出处理
type Overflow = 'visible' | 'hidden' | 'scroll' | 'auto';

// 边框样式
type StrokeStyle = 'solid' | 'dashed' | 'dotted' | 'double' | 'groove' | 'ridge' | 'inset' | 'outset' | null;

// 尺寸
type WidthHeight = 'fill' | 'hug' | string;

🔧 完整的 React 属性支持

figma-react-layout 支持完整的 React 属性生态系统,自动识别和转发所有标准 React 属性。

支持的属性类别

  • 🖱️ 所有 React 事件属性(onClick, onKeyDown, onTouchStart 等)
  • 🏷️ 基础 HTML 属性(id, className, style, title 等)
  • 可访问性属性(role, aria-label, aria-expanded 等)
  • 📊 自定义数据属性(data-testid, data-user-id 等)
  • 🔧 React 特殊属性(key, ref, dangerouslySetInnerHTML 等)
  • 📝 表单属性(name, value, checked, disabled 等)
  • 🎬 媒体属性(src, alt, controls, loop 等)

智能属性过滤

组件采用智能属性过滤系统:

const smartShouldForwardProp = (prop: string): boolean => {
  // 1. 过滤 transient props ($ 前缀)
  if (prop.startsWith('$')) return false;

  // 2. 过滤内部布局属性
  if (isInternalLayoutProp(prop)) return false;

  // 3. 自动允许所有事件处理器
  if (isEventProp(prop)) return true;

  // 4. 自动允许数据属性
  if (isDataProp(prop)) return true;

  // 5. 自动允许 ARIA 属性
  if (isAriaProp(prop)) return true;

  // 6. 允许 React 标准属性
  if (isReactStandardProp(prop)) return true;

  // 7. 保守策略:未知属性默认允许
  return true;
};

使用示例

现在可以像使用普通 React 组件一样使用布局组件:

<Box
  // 布局属性
  width="300px" height="150px" fill="#e3f2fd"

  // 完整的 React 属性支持
  id="my-box" className="interactive" role="button"
  aria-label="交互式组件" data-testid="test-box"
  onClick={handleClick} onKeyDown={handleKeyDown}
  onMouseEnter={handleMouseEnter} onFocus={handleFocus}
  draggable tabIndex={0}
>
  内容
</Box>

🔧 styled-components 未知属性警告修复

使用 figma-react-layout 时,可能会出现 styled-components 未知属性警告(如 "gap", "padding", "distribution" 等)。我们采用了 Transient Props($前缀) 方案,这是 styled-components 推荐的最佳实践。

修复效果

  • ✅ 完全消除了未知属性警告
  • ✅ 遵循 styled-components 最佳实践
  • ✅ 不破坏现有 API 使用方式
  • ✅ 确保所有组件实现一致性

使用方式

基本使用(无变化)

import { Box, Row, Column } from 'figma-react-layout';

// 使用方式完全不变
<Box gap="md" padding="16px" fill="blue">
  <Row distribution="space-between" alignment="center">
    <Column gap="sm">内容</Column>
  </Row>
</Box>

StyleProvider 配置(可选但推荐)

import { StyleProvider } from 'figma-react-layout';

function App() {
  return (
    <StyleProvider>
      {/* 你的应用 */}
    </StyleProvider>
  );
}

技术细节

Transient Props 工作原理

  1. 组件接收标准 props(如 gap, padding
  2. 内部转换为 $gap, $padding 等 transient props
  3. styled-components 自动过滤 $ 前缀的 props,不传递到 DOM
  4. 样式函数中接收 transient props 并转换为标准 props 供 CSS 生成器使用

双重保护机制

  1. 组件层面: 转换为 transient props
  2. 样式层面: shouldForwardProp 配置过滤 $ 前缀
  3. 全局层面: StyleProvider 的 shouldForwardProp 配置

这种多层保护确保即使有遗漏,自定义属性也不会传递到 DOM。

🆚 对比传统方案

我们的方案 - 语义化、简洁

<Column gap="$lg" padding="$xl" alignment="center-center" minHeight="100vh">
  <Text type="title-lg">欢迎回来</Text>
  <Column gap="$md" width="320px">
    <Input label="邮箱" />
    <Input label="密码" type="password" />
  </Column>
  <Row gap="$sm">
    <Button variant="secondary">取消</Button>
    <Button variant="primary">登录</Button>
  </Row>
</Column>

Tailwind 方案 - 冗长、难维护

<div className="flex flex-col items-center justify-center gap-6 p-8 min-h-screen">
  <h1 className="text-xl font-semibold">欢迎回来</h1>
  <div className="flex flex-col gap-4 w-80">
    <div className="space-y-2">
      <label className="text-sm font-medium">邮箱</label>
      <input className="w-full px-3 py-2 border rounded-md" />
    </div>
    <div className="space-y-2">
      <label className="text-sm font-medium">密码</label>
      <input className="w-full px-3 py-2 border rounded-md" type="password" />
    </div>
  </div>
  <div className="flex gap-2">
    <button className="px-4 py-2 bg-gray-100 text-gray-700 rounded-md">取消</button>
    <button className="px-4 py-2 bg-blue-500 text-white rounded-md">登录</button>
  </div>
</div>

优势对比

  • 代码量: 我们的方案节省 60% 代码量
  • 可读性: alignment="center-center" vs items-center justify-center
  • 一致性: 统一的 $lg token vs 混合的 p-8 gap-6 gap-4
  • 维护性: 修改间距只需改一个 token

🚀 集成到现有项目

1. 安装依赖

npm install figma-react-layout styled-components

2. 设置 CSS 变量(可选)

/* 在你的全局CSS中 */
:root {
  --xs: 4px;
  --sm: 8px;
  --md: 16px;
  --lg: 24px;
  --xl: 32px;

  --primary: #0066ff;
  --surface: #ffffff;
  --border: #e9ecef;
  --error: #dc3545;
  --muted: #6c757d;

  --none: 0;
  --sm: 4px;
  --md: 8px;
  --lg: 16px;
  --full: 50%;
}

3. 开始使用

import { Box, Column, Row } from 'figma-react-layout';

// 直接替换原有的 div 和样式
function MyComponent() {
  return (
    <Column gap="$md" padding="$lg">
      <Box fill="$surface" strokeColor="$border" radius="$md">
        内容
      </Box>
    </Column>
  );
}

📄 许可证

MIT License

🤝 贡献

欢迎提交 Issues 和 Pull Requests!

🔗 相关链接

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •