在 Remix 中使用 chroma-panel,不需要包装组件、ClientOnly,也不需要 typeof window 判断。通过 links 导出加入样式表,然后在路由中渲染 React 颜色选择器即可。同样的配置也适用于 React Router v7 的框架模式。
安装
npm install chroma-panel这个包没有任何依赖。react 和 react-dom 是 peer 依赖,所以用的就是你应用里已有的那一份。
引入样式表
面板在 effect 中注入自己的样式表,因此在水合完成之前,服务端渲染出的标记是没有样式的。Remix 有自己加载 CSS 的方式:导入文件的 URL,并在 links 导出中返回它。
// app/root.tsx
import type { LinksFunction } from "@remix-run/node";
import chromaPanelStyles from "chroma-panel/style.css?url";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: chromaPanelStyles },
];在 React Router v7 中,类型改从 react-router 导入:
import type { LinksFunction } from "react-router";
import chromaPanelStyles from "chroma-panel/style.css?url";然后在渲染面板的地方都传入 injectStyles={false},免得它再往 <style> 标签里写一份。
?url 是 Vite 的功能,不是 Remix 的
Vite 会去掉查询参数来解析这个导入路径,然后返回构建后文件的
URL。chroma-panel 在 exports 映射中列出了
"./style.css",所以这个子路径会解析到 dist/style.css。Remix v2 和 React
Router v7 都用 Vite 构建,所以两者都能这样用。如果你的构建不使用 Vite,就去掉
?url,改用其他方式加载 CSS。
如果你的应用使用 Tailwind,层级顺序比加载方式更重要。使用 Tailwind 样式给出了能让你的工具类始终优先生效的导入顺序。
路由示例
// app/routes/brand.tsx
import { useState } from "react";
import { ColorInput, type ColorChangeResult } from "chroma-panel";
export default function BrandRoute() {
const [color, setColor] = useState<string>("#3366cc");
const handleChange = (result: ColorChangeResult): void => {
setColor(result.hex);
};
return (
<main>
<h2>Brand color</h2>
<ColorInput value={color} onChange={handleChange} injectStyles={false} />
<p style={{ color }}>{color}</p>
</main>
);
}如果想把颜色提交给 action,而不是保存在状态中,就给 ColorInput 传一个 name,并去掉 value 和 onChange。它会渲染一个隐藏输入框,像其他控件一样随表单提交,详见表单。
服务端渲染
不需要做任何防护。上面的路由原样就能在服务端渲染:
- 包中所有涉及浏览器的文件都带有
'use client',模块顶层也不会读取window。 - 当
document未定义时,injectStyles会提前返回,所以服务端渲染时不会尝试创建<style>标签。 - 首次渲染会把颜色以 CSS 自定义属性的形式写到面板的根元素上,因此服务端渲染出的标记已经带有正确的颜色,水合之前不会闪烁。
服务端渲染针对 Next.js 和其他 SSR 方案讲了同样的要点;如果你不想打包默认导入注册的全部模式,入口列出了所有子路径。