♻️ refactor: Refactor static syle

This commit is contained in:
canisminor1990
2025-12-27 13:35:23 +08:00
parent 52518af03d
commit 5ce0756206
18 changed files with 1010 additions and 72 deletions
@@ -0,0 +1,959 @@
# createStaticStyles 迁移指南
## 📖 概述
`createStaticStyles` 是 `antd-style` 提供的静态样式创建函数,相比 `createStyles`(hook 方案)具有零运行时开销的优势。样式在模块加载时计算一次,而不是每次组件渲染时计算。
## 🎯 适用场景
### ✅ 可以优化的场景
1. **纯静态样式**:不依赖运行时动态值
2. **使用标准 token**:所有 token 都在 `cssVar.json` 中有对应项
3. **简单的条件逻辑**:可以通过静态样式拆分处理
### ❌ 无法优化的场景
1. **JS 计算函数**:`readableColor()`, `chroma()`, `mix()`, `calc()` 中使用 token 数值
2. **复杂的动态 props**:需要运行时计算的复杂逻辑
3. **动态 prefixCls**:需要运行时传入的类名前缀(但可以硬编码为 `'ant'`)
## 🔄 基本转换步骤
### 1. 样式文件转换
**之前(createStyles):**
```typescript
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css, token }) => {
return {
root: css`
color: ${token.colorText};
font-size: ${token.fontSize}px;
`,
};
});
```
**之后(createStaticStyles):**
```typescript
import { createStaticStyles } from 'antd-style';
export const styles = createStaticStyles(({ css, cssVar }) => {
return {
root: css`
color: ${cssVar.colorText};
font-size: ${cssVar.fontSize};
`,
};
});
```
### 2. 组件文件转换
**之前:**
```typescript
import { useStyles } from './style';
const Component = () => {
const { styles, cx } = useStyles();
return <div className={cx(styles.root, className)} />;
};
```
**之后:**
```typescript
import { cx } from 'antd-style';
import { styles } from './style';
const Component = () => {
return <div className={cx(styles.root, className)} />;
};
```
## 🛠️ 常见场景处理
### 场景 1: Token 转换
**规则:**
- `token.xxx` → `cssVar.xxx`
- 注意:`cssVar.fontSize` 已经包含 `px` 单位,不需要再加 `px`
**示例:**
```typescript
// ❌ 错误
font-size: ${cssVar.fontSize}px; // cssVar.fontSize 已经是 "14px"
// ✅ 正确
font-size: ${cssVar.fontSize}; // 直接使用
```
**特殊情况 - calc ():**
```typescript
// ❌ 错误
calc(${token.fontSize}px * 2.5)
// ✅ 正确
calc(${cssVar.fontSize} * 2.5) // cssVar.fontSize 已经包含单位
```
### 场景 2: 动态 Props → CSS 变量
**适用:** 数值、字符串类型的 props
**步骤:**
1. 在样式文件中使用 CSS 变量(带默认值)
2. 在组件中通过 `style` prop 设置 CSS 变量
**示例:**
**样式文件:**
```typescript
export const styles = createStaticStyles(({ css }) => {
return {
root: css`
width: var(--component-size, 24px);
height: var(--component-size, 24px);
`,
};
});
```
**组件文件:**
```typescript
import { useMemo } from 'react';
const Component = ({ size = 24, style, ...rest }) => {
const cssVariables = useMemo<Record<string, string>>(
() => ({
'--component-size': `${size}px`,
}),
[size],
);
return (
<div
className={styles.root}
style={{
...cssVariables,
...style,
}}
{...rest}
/>
);
};
```
**已优化示例:**
- `Video`: `maxHeight`, `maxWidth`, `minHeight`, `minWidth`
- `ScrollShadow`: `size`
- `MaskShadow`: `size`
- `ColorSwatches`: `size`
- `Grid`: `rows`, `maxItemWidth`, `gap`
- `Layout`: `headerHeight`
- `Footer`: `contentMaxWidth`
### 场景 3: 布尔值 Props → 静态样式拆分
**适用:** 简单的布尔值 props(2-3 个)
**步骤:**
1. 创建所有可能的组合样式
2. 运行时使用 `cx` 组合
**示例:**
**样式文件:**
```typescript
export const styles = createStaticStyles(({ css }) => {
return {
root: css`
/* base styles */
`,
root_closable_true: css`
/* closable styles */
`,
root_closable_false: css`
/* no closable styles */
`,
root_hasTitle_true: css`
/* has title styles */
`,
root_hasTitle_false: css`
/* no title styles */
`,
};
});
```
**组件文件:**
```typescript
const Component = ({ closable, hasTitle }) => {
const className = cx(
styles.root,
styles[`root_closable_${!!closable}`],
styles[`root_hasTitle_${!!hasTitle}`],
);
return <div className={className} />;
};
```
**已优化示例:**
- `Alert`: `closable`, `hasTitle`, `showIcon` → 8 个组合(2×2×2)
- `Image`: `alwaysShowActions` → 2 个样式
- `StoryBook`: `noPadding` → 2 个样式
### 场景 4: isDarkMode → 静态样式拆分
**适用:** 依赖 `isDarkMode` 的条件样式
**有两种处理方式:**
#### 方式 A: 直接条件选择(简单场景)
**步骤:**
1. 创建 `Dark` 和 `Light` 两个静态样式
2. 运行时根据 `theme.isDarkMode` 选择
**示例:**
**样式文件:**
```typescript
export const styles = createStaticStyles(({ css, cssVar }) => {
return {
rootDark: css`
background: ${cssVar.colorFillTertiary};
color: ${cssVar.colorTextLightSolid};
`,
rootLight: css`
background: ${cssVar.colorFillQuaternary};
color: ${cssVar.colorText};
`,
};
});
```
**组件文件:**
```typescript
import { useThemeMode } from 'antd-style';
const Component = () => {
const { isDarkMode } = useThemeMode();
return (
<div
className={cx(
isDarkMode ? styles.rootDark : styles.rootLight
)}
/>
);
};
```
#### 方式 B: 使用 cva 将 isDarkMode 作为 variant(推荐,适用于复杂场景)
**步骤:**
1. 创建 `Dark` 和 `Light` 两个静态样式
2. 在 `cva` 中将 `isDarkMode` 作为 variant prop
3. 运行时直接传入 `isDarkMode` 值
**示例:**
**样式文件:**
```typescript
import { createStaticStyles } from 'antd-style';
import { cva } from 'class-variance-authority';
export const styles = createStaticStyles(({ css, cssVar }) => {
return {
filledDark: css`
background: ${cssVar.colorFillTertiary};
color: ${cssVar.colorTextLightSolid};
`,
filledLight: css`
background: ${cssVar.colorFillQuaternary};
color: ${cssVar.colorText};
`,
outlined: css`
border: 1px solid ${cssVar.colorBorder};
`,
root: css`
/* base styles */
`,
};
});
export const variants = cva(styles.root, {
defaultVariants: {
isDarkMode: false,
variant: 'filled',
},
variants: {
isDarkMode: {
false: null,
true: null, // isDarkMode 本身不添加样式,通过 compoundVariants 组合
},
variant: {
filled: null, // variant 本身不添加样式,通过 compoundVariants 组合
outlined: styles.outlined,
},
},
compoundVariants: [
{
class: styles.filledDark,
isDarkMode: true,
variant: 'filled',
},
{
class: styles.filledLight,
isDarkMode: false,
variant: 'filled',
},
],
});
```
**组件文件:**
```typescript
import { useThemeMode } from 'antd-style';
import { variants } from './style';
const Component = ({ variant = 'filled' }) => {
const { isDarkMode } = useThemeMode();
return (
<div
className={variants({ isDarkMode, variant })}
/>
);
};
```
**优势:**
- ✅ 不需要 `useMemo` 动态创建 variants
- ✅ 更符合 `cva` 的设计理念
- ✅ 代码更简洁,性能更好
- ✅ 类型安全,IDE 自动补全
**已优化示例:**
- `TypewriterEffect`: `textDark` / `textLight`(方式 A)
- `Collapse`: `filledDark` / `filledLight`(可优化为方式 B)
- `Hotkey`: `inverseThemeDark` / `inverseThemeLight`(可优化为方式 B)
- `GuideCard`: `filledDark` / `filledLight`(可优化为方式 B)
- `GradientButton`: `buttonDark` / `buttonLight`(方式 A)
### 场景 5: responsive → 静态 responsive
**适用:** 使用响应式断点
**步骤:**
1. 导入静态 `responsive` from `antd-style`
2. 使用 `responsive.sm` 替代 `responsive.mobile`
3. 从 `createStyles` 参数中移除 `responsive`
**示例:**
**之前:**
```typescript
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css, responsive }) => ({
root: css`
${responsive.mobile} {
padding: 12px;
}
`,
}));
```
**之后:**
```typescript
import { createStaticStyles } from 'antd-style';
import { responsive } from 'antd-style';
export const styles = createStaticStyles(({ css }) => ({
root: css`
${responsive.sm} {
padding: 12px;
}
`,
}));
```
**注意:**
- `responsive.mobile` → `responsive.sm`
- 静态 `responsive` 提供:`xs`, `sm`, `md`, `lg`, `xl`, `xxl`
**已优化示例:**
- `Header`: `responsive.mobile` → `responsive.sm`
- `FormModal`: `responsive.mobile` → `responsive.sm`
- `Hero`: `responsive.mobile` → `responsive.sm`
### 场景 6: stylish → lobeStaticStylish
**适用:** 使用自定义 `stylish` 工具
**步骤:**
1. 导入 `lobeStaticStylish` from `@/styles`
2. 替换 `stylish.xxx` → `lobeStaticStylish.xxx`
**示例:**
**之前:**
```typescript
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css, stylish }) => ({
root: css`
${stylish.blur};
${stylish.variantFilled};
`,
}));
```
**之后:**
```typescript
import { createStaticStyles } from 'antd-style';
import { lobeStaticStylish } from '@/styles';
export const styles = createStaticStyles(({ css }) => ({
root: css`
${lobeStaticStylish.blur};
${lobeStaticStylish.variantFilled};
`,
}));
```
**已优化示例:**
- `Button`: `stylish.blur` → `lobeStaticStylish.blur`
- `Hero`: `stylish.gradientAnimation` → `lobeStaticStylish.gradientAnimation`
### 场景 7: prefixCls → 硬编码
**适用:** 使用动态 `prefixCls` 参数
**步骤:**
1. 在文件顶部硬编码 `const prefixCls = 'ant'`
2. 从 `createStyles` 参数中移除 `prefixCls`
**示例:**
**之前:**
```typescript
export const useStyles = createStyles(({ css }, prefixCls: string) => ({
root: css`
.${prefixCls}-button {
/* styles */
}
`,
}));
```
**之后:**
```typescript
const prefixCls = 'ant';
export const styles = createStaticStyles(({ css }) => ({
root: css`
.${prefixCls}-button {
/* styles */
}
`,
}));
```
**已优化示例:**
- `Alert`, `Collapse`, `FormModal`, `Image`, `Burger`, `DraggablePanel`, `DraggableSideNav`, `Toc`, `ColorSwatches`, `EmojiPicker`, `Form`, `awesome/Features`
### 场景 8: readableColor () → Token 替换
**适用:** 使用 `readableColor()` 计算对比色
**规则:**
- `readableColor(token.colorPrimary)` → `cssVar.colorTextLightSolid`(主色背景用白色文字)
- `readableColor(token.colorTextQuaternary)` → `cssVar.colorText`(浅色背景用深色文字)
**示例:**
**之前:**
```typescript
import { readableColor } from 'polished';
export const useStyles = createStyles(({ css, token }) => ({
checked: css`
background-color: ${token.colorPrimary};
color: ${readableColor(token.colorPrimary)};
`,
}));
```
**之后:**
```typescript
export const styles = createStaticStyles(({ css, cssVar }) => ({
checked: css`
background-color: ${cssVar.colorPrimary};
color: ${cssVar.colorTextLightSolid};
`,
}));
```
**已优化示例:**
- `Checkbox`: `readableColor(token.colorPrimary)` → `cssVar.colorTextLightSolid`
### 场景 9: rgba () → color-mix ()
**适用:** 使用 `rgba()` 设置透明度
**步骤:**
1. 使用 CSS 原生的 `color-mix()` 函数
2. 格式:`color-mix(in srgb, ${cssVar.xxx} alpha%, transparent)`
**示例:**
**之前:**
```typescript
import { rgba } from 'polished';
export const useStyles = createStyles(({ css, token }) => ({
root: css`
background-color: ${rgba(token.colorBgLayout, 0.4)};
`,
}));
```
**之后:**
```typescript
export const styles = createStaticStyles(({ css, cssVar }) => ({
root: css`
background-color: color-mix(in srgb, ${cssVar.colorBgLayout} 40%, transparent);
`,
}));
```
**已优化示例:**
- `Header`: `rgba(cssVar.colorBgLayout, 0.4)` → `color-mix(...)`
- `FormModal`: `rgba(cssVar.colorBgContainer, 0)` → `color-mix(...)`
### 场景 10: keyframes → css
**适用:** 使用 `keyframes` 创建动画
**步骤:**
1. 在 `createStaticStyles` 外部定义 `keyframes`
2. 在样式内部使用
**示例:**
**之前:**
```typescript
export const useStyles = createStyles(({ css, keyframes }) => {
const spin = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
return {
icon: css`
animation: ${spin} 1s linear infinite;
`,
};
});
```
**之后:**
```typescript
import { keyframes } from 'antd-style';
const spin = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
export const styles = createStaticStyles(({ css }) => ({
icon: css`
animation: ${spin} 1s linear infinite;
`,
}));
```
**已优化示例:**
- `Icon`: `keyframes` 动画
- `Skeleton`: `keyframes` shimmer 动画
## ⚠️ 反模式:避免使用 createVariants (isDarkMode)
**不推荐的做法:**
```typescript
// ❌ 不推荐:在组件中动态创建 variants
export const createVariants = (isDarkMode: boolean) =>
cva(styles.root, {
variants: {
variant: {
filled: isDarkMode ? styles.filledDark : styles.filledLight,
},
},
});
// 组件中
const variants = useMemo(() => createVariants(isDarkMode), [isDarkMode]);
```
**推荐的做法:**
将 `isDarkMode` 作为 `cva` 的 variant prop(见场景 4 方式 B),这样:
- ✅ 不需要 `useMemo` 动态创建
- ✅ 更符合 `cva` 的设计理念
- ✅ 代码更简洁,性能更好
- ✅ 类型安全,IDE 自动补全
```typescript
// ✅ 推荐:将 isDarkMode 作为 variant prop
export const variants = cva(styles.root, {
variants: {
isDarkMode: {
false: null,
true: null,
},
variant: {
filled: null,
},
},
compoundVariants: [
{
class: styles.filledDark,
isDarkMode: true,
variant: 'filled',
},
{
class: styles.filledLight,
isDarkMode: false,
variant: 'filled',
},
],
});
// 组件中
const { isDarkMode } = useThemeMode();
const className = variants({ isDarkMode, variant: 'filled' });
```
## ⚠️ 无法优化的场景
### 1. JS 计算函数
**无法优化:**
- `chroma()` - 颜色计算库
- `readableColor()` - 需要运行时计算(但可以用 token 替代)
- `mix()` - 颜色混合计算
- `calc()` 中使用 token 数值进行复杂计算
**示例:**
```typescript
// ❌ 无法优化
const scale = chroma.bezier([token.colorText, backgroundColor]).scale().colors(6);
```
### 2. 复杂的动态 Props
**无法优化:**
- 需要复杂计算的 props
- 对象 / 数组类型的 props
- 函数类型的 props
### 3. useTheme Hook
**无法优化:**
- 直接使用 `useTheme()` hook 获取运行时值
- 例如:`awesome/Giscus/style.ts` 使用 `useTheme()` 获取主题值
## 📋 迁移检查清单
### 样式文件检查
- [ ] `createStyles` → `createStaticStyles`
- [ ] `token.xxx` → `cssVar.xxx`
- [ ] 移除 `px` 后缀(`cssVar` 已包含单位)
- [ ] `responsive.mobile` → `responsive.sm`(如果使用)
- [ ] `stylish.xxx` → `lobeStaticStylish.xxx`(如果使用)
- [ ] `rgba()` → `color-mix()`(如果使用)
- [ ] `readableColor()` → token 替换(如果使用)
- [ ] `prefixCls` 参数 → 硬编码 `const prefixCls = 'ant'`(如果使用)
- [ ] `isDarkMode` → 静态样式拆分(如果使用)
- [ ] 动态 props → CSS 变量(如果使用)
### 组件文件检查
- [ ] `useStyles()` → `import { styles } from './style'`
- [ ] `import { cx } from 'antd-style'`(如果需要)
- [ ] `import { useTheme } from 'antd-style'`(如果需要 `theme.isDarkMode`)
- [ ] 动态 props → CSS 变量设置(如果使用)
- [ ] `isDarkMode` 条件 → `theme.isDarkMode` 判断(如果使用)
## 🎯 优化优先级
### 高优先级(简单优化)
1. ✅ 纯静态样式(无动态 props)
2. ✅ `isDarkMode` 拆分
3. ✅ `responsive.mobile` → `responsive.sm`
4. ✅ `stylish` → `lobeStaticStylish`
5. ✅ `readableColor()` → token 替换
### 中优先级(需要转换)
6. ✅ 简单的动态 props → CSS 变量(1-2 个)
7. ✅ 布尔值 props → 静态样式拆分(2-3 个)
### 低优先级(复杂优化)
8. ⚠️ 多个动态 props → CSS 变量(3+ 个)
9. ⚠️ 复杂的条件逻辑拆分
## 📚 参考示例
### 完整示例 1: 简单组件
**样式文件:**
```typescript
import { createStaticStyles } from 'antd-style';
export const styles = createStaticStyles(({ css, cssVar }) => ({
root: css`
padding: ${cssVar.padding};
color: ${cssVar.colorText};
border-radius: ${cssVar.borderRadius};
`,
}));
```
**组件文件:**
```typescript
import { cx } from 'antd-style';
import { styles } from './style';
const Component = ({ className }) => {
return <div className={cx(styles.root, className)} />;
};
```
### 完整示例 2: 带动态 Props
**样式文件:**
```typescript
import { createStaticStyles } from 'antd-style';
export const styles = createStaticStyles(({ css, cssVar }) => ({
root: css`
width: var(--component-size, 24px);
height: var(--component-size, 24px);
background: ${cssVar.colorBgContainer};
`,
}));
```
**组件文件:**
```typescript
import { cx } from 'antd-style';
import { useMemo } from 'react';
import { styles } from './style';
const Component = ({ size = 24, className, style, ...rest }) => {
const cssVariables = useMemo<Record<string, string>>(
() => ({
'--component-size': `${size}px`,
}),
[size],
);
return (
<div
className={cx(styles.root, className)}
style={{
...cssVariables,
...style,
}}
{...rest}
/>
);
};
```
### 完整示例 3: 带 isDarkMode
**样式文件:**
```typescript
import { createStaticStyles } from 'antd-style';
export const styles = createStaticStyles(({ css, cssVar }) => ({
rootDark: css`
background: ${cssVar.colorFillTertiary};
color: ${cssVar.colorTextLightSolid};
`,
rootLight: css`
background: ${cssVar.colorFillQuaternary};
color: ${cssVar.colorText};
`,
}));
```
**组件文件:**
```typescript
import { cx, useTheme } from 'antd-style';
import { styles } from './style';
const Component = ({ className }) => {
const { theme } = useTheme();
return (
<div
className={cx(
theme.isDarkMode ? styles.rootDark : styles.rootLight,
className
)}
/>
);
};
```
## 🔍 验证步骤
1. **类型检查:** `pnpm run type-check`
2. **运行时测试:** 确保视觉效果一致
3. **性能验证:** 检查样式计算是否在模块加载时完成
## 📊 优化效果
- ✅ **零运行时开销**:样式在模块加载时计算一次
- ✅ **减少重新渲染**:组件不再依赖样式 hook
- ✅ **更好的性能**:减少每次渲染的计算开销
- ✅ **代码更简洁**:直接导入样式对象
## 🔧 场景 11: useTheme () → useThemeMode () /cssVar
**适用:** 组件中只使用 `theme.isDarkMode` 或其他 token 值
**规则:**
- 如果只使用 `theme.isDarkMode`,使用 `const { isDarkMode } = useThemeMode()` 替代
- 如果使用其他 token(如 `theme.colorText`, `theme.borderRadius` 等),使用 `cssVar` 替代
- `useThemeMode()` 比 `useTheme()` 更轻量,只返回 `isDarkMode` 值
**示例:**
**之前:**
```typescript
import { useTheme } from 'antd-style';
const Component = () => {
const theme = useTheme();
return (
<div className={theme.isDarkMode ? styles.dark : styles.light}>
{theme.colorText}
</div>
);
};
```
**之后:**
```typescript
import { cssVar, useThemeMode } from 'antd-style';
const Component = () => {
const { isDarkMode } = useThemeMode();
return (
<div className={isDarkMode ? styles.dark : styles.light}>
{cssVar.colorText}
</div>
);
};
```
**已优化示例:**
- `AuroraBackground`, `Select`, `Input`, `Button`, `DatePicker`, `AutoComplete`, `InputNumber`, `InputPassword`, `InputOPT`, `TextArea`, `SpotlightCardItem`, `Spotlight`, `HotkeyInput` - 只使用 `isDarkMode` → `useThemeMode()`
- `Image`, `GradientButton`, `Empty`, `FileTypeIcon`, `FormSubmitFooter`, `CodeEditor`, `LobeChat`, `Drawer`, `Modal`, `Avatar`, `AvatarGroup`, `SkeletonAvatar`, `SkeletonButton`, `SkeletonTags`, `Callout`, `LobeHub`, `GridBackground`, `FolderIcon`, `FileIcon`, `TokenTag`, `ChatSendButton`, `AvatarUploader` - 使用 token → `cssVar`
**无法优化的文件(需要保留 `useTheme()`):**
- `useMermaid`, `useStreamMermaid`, `useHighlight`, `useStreamHighlight` - 需要完整的 theme 对象传给第三方库
- `Alert`, `Tag`, `Menu`, `EmojiPicker` - 需要实际颜色值传给颜色计算函数
- `SkeletonTitle`, `SkeletonTags` - 需要数值进行数学运算
- `GridShowcase`, `GridBackground/demos` - 需要实际颜色值传给 `rgba()` 函数
- `CustomFonts` - 需要实际字符串值进行字符串拼接
- `Giscus/style.ts` - 需要实际颜色值传给 `readableColor()` 和 `rgba()` 函数(其他 token 已优化为 `cssVar`)
**注意事项:**
- `useThemeMode()` 只返回 `{ isDarkMode }`,不返回完整的 theme 对象
- `cssVar` 的值是字符串(如 `"14px"`, `"#ffffff"`),可以直接在 JSX 中使用
- 如果 token 需要用于数值计算(如 `Math.round(theme.fontSize * 1.5)`),需要保留 `useTheme()`
## 🎉 总结
`createStaticStyles` 迁移是一个渐进式的优化过程。对于简单的静态样式,可以直接转换;对于复杂的动态场景,需要根据具体情况选择合适的优化策略。关键是要理解每种场景的处理方式,并灵活运用 CSS 变量、静态样式拆分等技术。
### useTheme () 优化总结
- ✅ **使用 `useThemeMode()`**:当组件只使用 `theme.isDarkMode` 时
- ✅ **使用 `cssVar`**:当组件使用其他 token 值(颜色、尺寸等)时
- ⚠️ **保留 `useTheme()`**:当 token 需要用于数值计算或传给第三方库时
+1 -2
View File
@@ -74,12 +74,11 @@ export default defineConfig({
define: {
'process.env': process.env,
},
exportStatic: {},
extraBabelPlugins: ['babel-plugin-antd-style'],
favicons: ['https://lobehub.com/favicon.ico'],
jsMinifier: 'swc',
locales: [{ id: 'en-US', name: 'English' }],
mako: isWin || isProduction ? false : {},
mfsu: isWin ? false : {},
npmClient: 'pnpm',
publicPath: '/',
resolve: isProduction
+4 -4
View File
@@ -85,7 +85,7 @@
]
},
"dependencies": {
"antd-style": "^4.0.0",
"antd-style": "^4.1.0",
"lucide-react": "^0.469.0",
"polished": "^4.3.1"
},
@@ -93,7 +93,7 @@
"@babel/runtime": "^7.28.4",
"@commitlint/cli": "^19.8.1",
"@lobehub/lint": "^1.26.3",
"@lobehub/ui": "^4.1.2",
"@lobehub/ui": "^4.3.3",
"@testing-library/react": "^16.3.1",
"@types/pangu": "^4.0.2",
"@types/react": "^19.2.7",
@@ -108,7 +108,7 @@
"dotenv": "^17.2.3",
"dpdm": "^3.14.0",
"dumi": "^2.4.21",
"dumi-theme-lobehub": "^4.0.0",
"dumi-theme-lobehub": "^4.0.1",
"es-toolkit": "^1.43.0",
"eslint": "^8.57.1",
"father": "^4.6.11",
@@ -134,7 +134,7 @@
"vitest": "^3.2.4"
},
"peerDependencies": {
"@lobehub/ui": "^4.0.0",
"@lobehub/ui": "^4.3.3",
"antd": "^6.1.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
+5 -7
View File
@@ -1,10 +1,10 @@
'use client';
import { CopyButton, Flexbox, Tooltip } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { createStaticStyles, cx } from 'antd-style';
import { memo } from 'react';
const useStyles = createStyles(({ css, token, cx }) => {
const styles = createStaticStyles(({ css, cssVar }) => {
return {
btn: cx(
'copy-button',
@@ -20,10 +20,10 @@ const useStyles = createStyles(({ css, token, cx }) => {
width: 98px;
height: 98px;
border: 1px solid ${token.colorBorder};
border-radius: ${token.borderRadius}px;
border: 1px solid ${cssVar.colorBorder};
border-radius: ${cssVar.borderRadius};
background: ${token.colorBgContainer};
background: ${cssVar.colorBgContainer};
&:hover {
.copy-button {
@@ -39,8 +39,6 @@ export interface IconPreviewProps {
}
const IconPreview = memo<IconPreviewProps>(({ color }) => {
const { styles } = useStyles();
return (
<Tooltip title={color}>
<Flexbox
+5 -6
View File
@@ -1,7 +1,7 @@
'use client';
import { ActionIcon, Block, Center, CopyButton, Flexbox, Text } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { createStaticStyles, cx } from 'antd-style';
import { Link } from 'dumi';
import { DownloadIcon, SearchIcon } from 'lucide-react';
import { readableColor } from 'polished';
@@ -9,7 +9,7 @@ import { ReactNode, memo, useCallback, useRef } from 'react';
import { customKebabCase } from '@/components/Dashboard/utils';
const useStyles = createStyles(({ cx, css, token }) => {
const styles = createStaticStyles(({ css, cssVar }) => {
const colorText = cx(
'color-text',
css`
@@ -29,8 +29,8 @@ const useStyles = createStyles(({ cx, css, token }) => {
overflow: hidden;
`,
color: css`
border-inline-end: 1px solid ${token.colorBorderSecondary};
font-family: ${token.fontFamilyCode};
border-inline-end: 1px solid ${cssVar.colorBorderSecondary};
font-family: ${cssVar.fontFamilyCode};
&:hover {
.color-text {
@@ -45,7 +45,7 @@ const useStyles = createStyles(({ cx, css, token }) => {
colorText,
copy,
row: css`
border-block-start: 1px solid ${token.colorFillSecondary};
border-block-start: 1px solid ${cssVar.colorFillSecondary};
`,
title: css`
margin: 0;
@@ -72,7 +72,6 @@ interface IconItemProps {
}
const IconItem = memo<IconItemProps>(({ children, title, color, id }) => {
const { styles } = useStyles();
const ref = useRef<HTMLDivElement>(null);
const handleDownload = useCallback((iconId: string) => {
+3 -4
View File
@@ -3,24 +3,23 @@
import * as Icons from '@lobehub/icons';
import { Flexbox } from '@lobehub/ui';
import { StoryBook, useControls, useCreateStore } from '@lobehub/ui/storybook';
import { createStyles } from 'antd-style';
import { createStaticStyles } from 'antd-style';
import { memo } from 'react';
import IconPreview from '@/components/IconPreview';
const data = Object.values(Icons).filter((icon: any) => icon?.colorPrimary);
const useStyles = createStyles(({ css, token }) => ({
const styles = createStaticStyles(({ css, cssVar }) => ({
item: css`
height: 96px;
padding: 16px;
border: none;
background: ${token.colorBgContainer};
background: ${cssVar.colorBgContainer};
`,
}));
const Dashboard = memo<{ className: string }>(({ className }) => {
const { styles } = useStyles();
const store = useCreateStore();
const { size, color } = useControls(
+2 -3
View File
@@ -3,7 +3,7 @@
import * as Icons from '@lobehub/icons';
import { Flexbox, Grid, SearchBar } from '@lobehub/ui';
import { Empty, Segmented } from 'antd';
import { useTheme } from 'antd-style';
import { cssVar } from 'antd-style';
import { memo, useMemo, useState } from 'react';
import IconItem from './IconItem';
@@ -14,7 +14,6 @@ enum ColorType {
}
const Dashboard = memo(() => {
const theme = useTheme();
const [type, setType] = useState<ColorType>(ColorType.Color);
const [keyword, setKeyword] = useState<string>();
@@ -60,7 +59,7 @@ const Dashboard = memo(() => {
},
]}
style={{
border: `1px solid ${theme.colorBorder}`,
border: `1px solid ${cssVar.colorBorder}`,
}}
/>
</Flexbox>
+2 -3
View File
@@ -1,10 +1,10 @@
'use client';
import { Flexbox, Highlighter } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { createStaticStyles } from 'antd-style';
import { memo } from 'react';
const useStyles = createStyles(({ css }) => {
const styles = createStaticStyles(({ css }) => {
return {
code: css`
pre {
@@ -27,7 +27,6 @@ interface PreviewProps {
}
const Preview = memo<PreviewProps>(({ svg, title, viewbox, textMode }) => {
const { styles } = useStyles();
const sizeAttributes = textMode
? `height={size}
style={{ flex: 'none', lineHeight: 1, ...style }}`
+2 -4
View File
@@ -1,10 +1,10 @@
'use client';
import { Flexbox, Highlighter } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { createStaticStyles } from 'antd-style';
import { memo } from 'react';
const useStyles = createStyles(({ css }) => {
const styles = createStaticStyles(({ css }) => {
return {
code: css`
pre {
@@ -26,8 +26,6 @@ interface PreviewProps {
}
const Preview = memo<PreviewProps>(({ svg, title, viewbox }) => {
const { styles } = useStyles();
return (
<Flexbox flex={'none'} gap={16} style={{ position: 'relative', width: '100%' }}>
<Flexbox align={'center'} gap={8} horizontal>
+2 -4
View File
@@ -1,10 +1,10 @@
import { Flexbox, Highlighter, Tag } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { createStaticStyles } from 'antd-style';
import { Ref, memo } from 'react';
import IconPreview from '@/components/IconPreview';
const useStyles = createStyles(({ css }) => {
const styles = createStaticStyles(({ css }) => {
return {
code: css`
flex: 1;
@@ -30,8 +30,6 @@ interface PreviewProps {
}
const Preview = memo<PreviewProps>(({ svg, title, precent, ref }) => {
const { styles } = useStyles();
return (
<Flexbox flex={'none'} gap={16} style={{ position: 'relative', width: '100%' }}>
<Flexbox align={'center'} gap={8} horizontal>
+2 -4
View File
@@ -1,10 +1,10 @@
'use client';
import { Flexbox, Highlighter } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { createStaticStyles } from 'antd-style';
import { memo } from 'react';
const useStyles = createStyles(({ css }) => {
const styles = createStaticStyles(({ css }) => {
return {
code: css`
pre {
@@ -26,8 +26,6 @@ interface PreviewProps {
}
const Preview = memo<PreviewProps>(({ svg, title, viewbox }) => {
const { styles } = useStyles();
return (
<Flexbox flex={'none'} gap={16} style={{ position: 'relative', width: '100%' }}>
<Flexbox align={'center'} gap={8} horizontal>
+4 -5
View File
@@ -1,6 +1,6 @@
import { Flexbox } from '@lobehub/ui';
import { StoryBook, useControls, useCreateStore } from '@lobehub/ui/storybook';
import { createStyles } from 'antd-style';
import { createStaticStyles } from 'antd-style';
import { useEffect, useRef, useState } from 'react';
import { useSvgo } from '@/components/Editor/useSvgo';
@@ -12,11 +12,11 @@ import Text from './Text';
import { svgIcon } from './data';
import { defaultPlugins } from './svgo';
const useStyles = createStyles(({ css, token }) => {
const styles = createStaticStyles(({ css, cssVar }) => {
return {
container: css`
border: 1px solid ${token.colorBorder};
border-radius: ${token.borderRadius}px;
border: 1px solid ${cssVar.colorBorder};
border-radius: ${cssVar.borderRadius};
`,
};
});
@@ -26,7 +26,6 @@ export default () => {
const [viewbox, setViewbox] = useState<string>('0 0 24 24');
const [colorSvg, setColorSvg] = useState<string>('');
const [monoSvg, setMonoSvg] = useState<string>('');
const { styles } = useStyles();
const levaStore = useCreateStore();
const { svg, text } = useControls(
{
+6 -8
View File
@@ -1,10 +1,10 @@
'use client';
import { CopyButton, Flexbox } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { createStaticStyles, cx } from 'antd-style';
import { memo } from 'react';
const useStyles = createStyles(({ css, token, cx }) => {
const styles = createStaticStyles(({ css, cssVar }) => {
return {
btn: cx(
'copy-button',
@@ -22,13 +22,13 @@ const useStyles = createStyles(({ css, token, cx }) => {
width: 98px;
height: 98px;
border: 1px solid ${token.colorBorder};
border-radius: ${token.borderRadius}px;
border: 1px solid ${cssVar.colorBorder};
border-radius: ${cssVar.borderRadius};
font-family: ${token.fontFamilyCode};
font-family: ${cssVar.fontFamilyCode};
line-height: 1;
background: ${token.colorBgContainer};
background: ${cssVar.colorBgContainer};
&:hover {
.copy-button {
@@ -44,8 +44,6 @@ export interface IconPreviewProps {
}
const IconPreview = memo<IconPreviewProps>(({ color }) => {
const { styles } = useStyles();
return (
<Flexbox
align={'center'}
+5 -6
View File
@@ -1,10 +1,10 @@
import { Flexbox, FlexboxProps } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { createStaticStyles, cx } from 'antd-style';
import { ReactNode, Ref, memo, useRef } from 'react';
import DownloadButton from '@/components/DownloadButton';
const useStyles = createStyles(({ css, token, cx }) => {
const styles = createStaticStyles(({ css, cssVar }) => {
return {
btn: cx(
'copy-button',
@@ -21,12 +21,12 @@ const useStyles = createStyles(({ css, token, cx }) => {
flex: none;
padding: 12px;
border: 1px solid ${token.colorBorder};
border-radius: ${token.borderRadius}px;
border: 1px solid ${cssVar.colorBorder};
border-radius: ${cssVar.borderRadius};
line-height: 1;
background: ${token.colorBgContainer};
background: ${cssVar.colorBgContainer};
&:hover {
.copy-button {
@@ -56,7 +56,6 @@ export interface IconPreviewProps extends FlexboxProps {
}
const IconPreview = memo<IconPreviewProps>(({ className, children, ...rest }) => {
const { cx, styles } = useStyles();
const ref = useRef<HTMLDivElement>(null);
const isString = typeof children === 'string';
+2 -3
View File
@@ -1,5 +1,5 @@
import { Center } from '@lobehub/ui';
import { useTheme } from 'antd-style';
import { cssVar } from 'antd-style';
import { memo } from 'react';
import { IconAvatarProps } from '@/features/IconAvatar';
@@ -17,12 +17,11 @@ const DefaultAvatar = memo<Omit<IconAvatarProps, 'Icon'>>(
iconStyle,
iconClassName,
}) => {
const theme = useTheme();
return (
<Center
flex={'none'}
style={{
background: background || theme.colorFillSecondary,
background: background || cssVar.colorFillSecondary,
borderRadius: shape === 'circle' ? '50%' : Math.floor(size * 0.1),
color,
height: size,
+2 -3
View File
@@ -1,5 +1,5 @@
import { Icon } from '@lobehub/ui';
import { useTheme } from 'antd-style';
import { cssVar } from 'antd-style';
import { Brain } from 'lucide-react';
import { CSSProperties, memo } from 'react';
@@ -11,8 +11,7 @@ interface DefaultIconProps {
}
const DefaultAvatar = memo<DefaultIconProps>(({ color, size = 12, ...rest }) => {
const theme = useTheme();
return <Icon color={color || theme.colorTextDescription} icon={Brain} size={size} {...rest} />;
return <Icon color={color || cssVar.colorTextDescription} icon={Brain} size={size} {...rest} />;
});
export default DefaultAvatar;
+2 -3
View File
@@ -1,5 +1,5 @@
import { Center } from '@lobehub/ui';
import { useTheme } from 'antd-style';
import { cssVar } from 'antd-style';
import { memo } from 'react';
import type { IconAvatarProps } from '../IconAvatar';
@@ -16,12 +16,11 @@ const DefaultAvatar = memo<Omit<IconAvatarProps, 'Icon'>>(
iconStyle,
iconClassName,
}) => {
const theme = useTheme();
return (
<Center
flex={'none'}
style={{
background: background || theme.colorFillSecondary,
background: background || cssVar.colorFillSecondary,
borderRadius: shape === 'circle' ? '50%' : Math.floor(size * 0.1),
color,
height: size,
+2 -3
View File
@@ -1,5 +1,5 @@
import { Icon } from '@lobehub/ui';
import { useTheme } from 'antd-style';
import { cssVar } from 'antd-style';
import { Radar } from 'lucide-react';
import { type CSSProperties, memo } from 'react';
@@ -11,8 +11,7 @@ interface DefaultIconProps {
}
const DefaultIcon = memo<DefaultIconProps>(({ color, size = 12, ...rest }) => {
const theme = useTheme();
return <Icon color={color || theme.colorTextDescription} icon={Radar} size={size} {...rest} />;
return <Icon color={color || cssVar.colorTextDescription} icon={Radar} size={size} {...rest} />;
});
export default DefaultIcon;