哈喽艾瑞巴蒂,我是努力在写优秀技术爽文的 HoMeTown。
Chakra UI 最近发布了一个新的CSS框架,PandaCSS。
在Github扒了一下作者发现 PandaCSS 其实在2023 年 3 月 Segun Adebayo 发布的《Chakra UI的未来》 一文中有提到过,只不过最近才正式发布。
来自 Github README:
@layer
, css variables and more / 现代 CSS 输出 — 级联层 @layer、css 变量等文档中提到的支持的框架有:
文档的入门其实也写的比较详细了,这里简单尝试一下基于Vite + React的项目:
mkdir ./pandacss-react && cd ./pandacss-react
pnpm create vite . --template react-ts
pnpm install
安装 panda,使用panda init
可以生成配置文件:
pnpm install -D @pandacss/dev
pnpm panda init --postcss
这里需要注意的是,node版本不能过低,我首次init
时的node版本为14.x
,报错||=
的语法错误,升级为最新长期支持稳定版v18.16.1
后,就没问题了。
在package.json
中添加命令,构建panda
json
{
"scripts": {
"prepare": "panda codegen",
...
}
}
修改src/index.css
:
css
@layer reset, base, tokens, recipes, utilities;
组件中使用:
main.tsx
ts
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
App.tsx
ts
import { css } from "../styled-system/css";
function App() {
return (
<div className={css({ fontSize: "2xl", fontWeight: "bold" })}>
Hello 🐼!
</div>
);
}
export default App;
styled-system/css
目录是用 panda 构建的结果。运行一次构建,然后启动 Vite。
pnpm panda codegen
pnpm dev
启动页面如下:
非常简单。
Panda CSS 使用Cascade Layers来控制CSS优先级,所以就意味着当前浏览器必须支持Cascade Layers,根据 MDN,浏览器需要以下版本:
层数固定为以下五层:
图层的优先级顺序是@layer
在index.css等css中定义的,所以你可以根据需要更改级联层顺序。
使用 PandaCSS 时写 CSS 最简单的方法是通过css()
函数并在对象中写css属性。而且类型安全。
tsx
<div>
<div className={css({ color: "red" })}>red</div>
<div className={css({ fontWeight: "bold" })}>bold</div>
<div className={css({ color: "red", fontWeight: "bold" })}>red bold</div>
</div>
生成的CSS如下:
css
@layer utilities {
.text_red {
color: red;
}
.font_bold {
font-weight: var(--font-weights-bold);
}
}
可以看到生成的CSS内容会进行去重的处理。
tsx
<button className={css({ color: "red", _hover: { color: "blue" } })}>
Button
</button>
写CSS的时候会有一些频繁出现的布局或者样式,我们更希望这种情况能以模块的形式使用从而达到复用的目的,PandaCSS提供了一些内置的方法,比如center
:
tsx
import { center } from "../styled-system/patterns";
function App() {
return (
<div
className={center({
bg: "gray",
color: "white",
inlineSize: "200px",
blockSize: "200px",
})}
>
text
</div>
);
}
除了center
之外PandaCSS还提供了以下内置方法:
还可以自己自定义一个Pattern,参考。
Recipes主要用来封装组件样式,比如封装一个button组件的样式:
tsx
import { cva } from "../styled-system/css";
export const button = cva({
base: {
display: "flex",
borderWidth: "1px",
borderColor: "gray",
},
variants: {
type: {
default: { color: "gray" },
danger: { color: "red", borderColor: "red" },
},
size: {
small: { padding: "8px", fontSize: "12px" },
large: { padding: "16px", fontSize: "16px" },
},
},
defaultVariants: {
type: "default",
size: "small",
},
});
组件定义了两种类型default
和danger
,两种大小small
和large
。如果没有指定值,默认被设置为default
和small
,并且在写代码的时候也会有提示:
组件中使用:
tsx
import { hstack } from "../styled-system/patterns";
import { button } from "./button.css";
function App() {
return (
<>
<div className={hstack({ gap: "8px", padding: "16px" })}>
<button className={button({ size: "small", type: "default" })}>
Button
</button>
<button className={button({ size: "large", type: "default" })}>
Button
</button>
<button className={button({ size: "small", type: "danger" })}>
Button
</button>
<button className={button({ size: "large", type: "danger" })}>
Button
</button>
<button className={button()}>Button</button>
</div>
</>
);
}
这仅仅是样式的封装,如果想要封装成组件,并将这些属性作为props使用的话,可以利用RecipeVariantProps
提取类型:
Button.tsx
tsx
import { ReactNode } from "react";
import { RecipeVariantProps } from "../styled-system/css";
import { button } from "./button.css";
type Props = {
children: ReactNode;
} & RecipeVariantProps<typeof button>;
export const Button = ({ children, ...recipeVariantProps }: Props) => {
<button className={button(recipeVariantProps)}>{children}</button>;
};
使用:
tsx
import { Button } from "./Button";
function App() {
return (
<Button size="small" type="default">
Button
</Button>
);
}
这里很好的一个点是,我们通过这种方式,可以把样式props和其他的props隔离开。
上面的方式都是通过className
然后使用Panda生成的类来设计样式,类似unocss、tailwindcss这些css框架,除此之外,这些类在Panda中还可以作为一个JSX属性来使用。
首先,需要在panda.config.ts
中指定对应的框架:
ts
export default defineConfig({
...
jsxFramework: 'react'
})
然后通过引入styled
,使用styled.xxx
创建JSX元素:
tsx
import { VStack, styled } from "../styled-system/jsx";
function App() {
return (
<VStack gap="8px">
<styled.a href="https://example.com" color="red">
Link
</styled.a>
<styled.button type="button" color="blue">
Button
</styled.button>
</VStack>
);
}
export default App;
可以看到styled.a
处理<a>
标签的同时,也可以处理样式。
我个人还是更偏好于className
的方式。