mm
This commit is contained in:
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"name": "@dg/css",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
"*.css"
|
||||
],
|
||||
"module": "./dist/index.es.js",
|
||||
"main": "./dist/index.cjs.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"style": "./dist/index.css",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.es.js",
|
||||
"require": "./dist/index.cjs.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./index.css": "./dist/index.css"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"gen-index": "ts-node scripts/gen-index.ts",
|
||||
"gen-dts": "tsc -p tsconfig.build.json",
|
||||
"dev": "pnpm gen-index && vite build --watch",
|
||||
"build": "pnpm gen-index && vite build && pnpm gen-dts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"tinyglobby": "^0.2.16",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19",
|
||||
"react-dom": "^19"
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
/**
|
||||
* 默认测试 / story / example / fixture / type 文件匹配规则
|
||||
* 仅用于“识别”,不直接决定是否排除
|
||||
*/
|
||||
const DEFAULT_TEST_FILE_PATTERNS: RegExp[] = [
|
||||
// =========================
|
||||
// Test / Spec
|
||||
// =========================
|
||||
/\.(test|spec)\.(ts|tsx|js|jsx)$/,
|
||||
/\.(test|spec)\.(ts|tsx|js|jsx)\?.*$/, // query param safe
|
||||
|
||||
// =========================
|
||||
// E2E / Playwright / Cypress
|
||||
// =========================
|
||||
/\.(cy|playwright|e2e)\.(ts|tsx|js|jsx)$/,
|
||||
|
||||
// =========================
|
||||
// Storybook
|
||||
// =========================
|
||||
/\.(story|stories)\.(ts|tsx|js|jsx|mdx)$/,
|
||||
|
||||
// =========================
|
||||
// Snapshot / Mock / Fixture
|
||||
// =========================
|
||||
/\.snap$/,
|
||||
/\.mock\.(ts|tsx|js|jsx)$/,
|
||||
/\.fixture\.(ts|tsx|js|jsx)$/,
|
||||
|
||||
// =========================
|
||||
// Type declarations
|
||||
// =========================
|
||||
/\.d\.ts$/,
|
||||
|
||||
// =========================
|
||||
// Examples / Demos / Docs
|
||||
// =========================
|
||||
/\.(example|demo|docs)\.(ts|tsx|js|jsx)$/,
|
||||
];
|
||||
|
||||
/**
|
||||
* 测试 / 辅助目录(目录级排除)
|
||||
*/
|
||||
const DEFAULT_TEST_DIR_PATTERNS: RegExp[] = [
|
||||
/[/\\](__tests__|tests|test|spec|__mocks__|__fixtures__|__snapshots__)[\/\\]/,
|
||||
/[/\\](cypress|playwright|e2e|__e2e__|__playwright__)[\/\\]/,
|
||||
/[/\\](story|stories|\.storybook)[\/\\]/,
|
||||
/[/\\](types|type|typings|interfaces)[\/\\]/,
|
||||
/[/\\](examples|example|demo|demos|docs)[\/\\]/,
|
||||
];
|
||||
|
||||
type Config = {
|
||||
outputDir: string;
|
||||
outputFile: string;
|
||||
scanDirs: string[];
|
||||
preamble?: string[];
|
||||
importPrefix?: string;
|
||||
entryFilePatterns?: RegExp[];
|
||||
barrelFirstMode?: boolean;
|
||||
includeFilePatterns?: RegExp[];
|
||||
excludeFilePatterns?: RegExp[];
|
||||
excludeDirPatterns?: RegExp[];
|
||||
};
|
||||
|
||||
const tsConfig: Config = {
|
||||
outputDir: "src",
|
||||
outputFile: "index.ts",
|
||||
scanDirs: ["src"],
|
||||
importPrefix: "export * from",
|
||||
barrelFirstMode: true,
|
||||
preamble: ["import './index.css';"],
|
||||
entryFilePatterns: [/^index\.(js|ts|jsx|tsx)$/],
|
||||
includeFilePatterns: [/\.(js|ts|jsx|tsx)$/],
|
||||
excludeFilePatterns: DEFAULT_TEST_FILE_PATTERNS,
|
||||
excludeDirPatterns: DEFAULT_TEST_DIR_PATTERNS,
|
||||
};
|
||||
|
||||
const cssConfig: Config = {
|
||||
outputDir: "src",
|
||||
outputFile: "index.css",
|
||||
scanDirs: ["src"],
|
||||
importPrefix: "@import",
|
||||
barrelFirstMode: false,
|
||||
entryFilePatterns: [/^index\.(css)$/],
|
||||
includeFilePatterns: [/\.(css)$/],
|
||||
excludeFilePatterns: DEFAULT_TEST_FILE_PATTERNS,
|
||||
excludeDirPatterns: DEFAULT_TEST_DIR_PATTERNS,
|
||||
};
|
||||
|
||||
function isEntryFile(fileName: string, config: Config): boolean {
|
||||
const regExps = config.entryFilePatterns;
|
||||
|
||||
if (!regExps || regExps.length === 0) return false;
|
||||
|
||||
return regExps.some((regExp) => regExp.test(fileName));
|
||||
}
|
||||
|
||||
function isIncludeFile(fileName: string, config: Config): boolean {
|
||||
const regExps = config.includeFilePatterns;
|
||||
|
||||
if (!regExps || regExps.length === 0) return false;
|
||||
|
||||
return regExps.some((regExp) => regExp.test(fileName));
|
||||
}
|
||||
|
||||
function isExcludeFile(fileName: string, config: Config): boolean {
|
||||
const regExps = config.excludeFilePatterns;
|
||||
|
||||
if (!regExps || regExps.length === 0) return false;
|
||||
|
||||
return regExps.some((regExp) => regExp.test(fileName));
|
||||
}
|
||||
|
||||
function isExcludeDir(filePath: string, config: Config): boolean {
|
||||
const regExps = config.excludeDirPatterns;
|
||||
|
||||
if (!regExps || regExps.length === 0) return false;
|
||||
|
||||
return regExps.some((regExp) => regExp.test(filePath));
|
||||
}
|
||||
|
||||
function isValidDir(filePath: string, config: Config): boolean {
|
||||
if (isExcludeDir(filePath, config)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isValidFile(fileName: string, config: Config): boolean {
|
||||
if (isIncludeFile(fileName, config) && !isExcludeFile(fileName, config)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const buildExportStatement = (filePath: string, config: Config) => {
|
||||
// 文件夹(不是文件)与目标文件的相对路径
|
||||
let exportFilePath = path.relative(config.outputDir, filePath);
|
||||
|
||||
// 去除扩展名,并且转化成正斜杠
|
||||
const exportFilePathWithoutExt = path
|
||||
.join(
|
||||
path.dirname(exportFilePath),
|
||||
path.basename(exportFilePath, path.extname(exportFilePath)),
|
||||
)
|
||||
.replace(/\\/g, "/");
|
||||
|
||||
// 加上 ./ 前缀
|
||||
return `${config.importPrefix} './${exportFilePathWithoutExt}';`;
|
||||
};
|
||||
|
||||
function isOutputEntry(filePath: string, config: Config) {
|
||||
const outputFilePath = path.resolve(config.outputDir, config.outputFile);
|
||||
const targetFilePath = path.resolve(filePath);
|
||||
|
||||
return outputFilePath === targetFilePath;
|
||||
}
|
||||
|
||||
function generateExports(dirPath: string, config: Config): string[] {
|
||||
const fileNames = fs.readdirSync(dirPath);
|
||||
const exports: string[] = [];
|
||||
|
||||
// =========================
|
||||
// 逻辑分支:Barrel First Mode
|
||||
// =========================
|
||||
if (config.barrelFirstMode) {
|
||||
// 查找当前目录有没有 index.ts
|
||||
const entryFileName = fileNames.find((fileName) => {
|
||||
const filePath = path.join(dirPath, fileName);
|
||||
return fs.statSync(filePath).isFile() && isEntryFile(fileName, config);
|
||||
});
|
||||
// 如果有
|
||||
if (entryFileName) {
|
||||
const entryFilePath = path.join(dirPath, entryFileName);
|
||||
|
||||
// 如果是 outputFile 自身,则跳过
|
||||
if (isOutputEntry(entryFilePath, config)) {
|
||||
console.log(`⏭️ 跳过自身入口文件: ${entryFilePath}`);
|
||||
} else {
|
||||
exports.push(buildExportStatement(entryFilePath, config));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =========================
|
||||
// 逻辑分支:index.ts 不存在,则继续正常遍历与递归
|
||||
// =========================
|
||||
fileNames.forEach((fileName) => {
|
||||
const filePath = path.join(dirPath, fileName);
|
||||
const stat = fs.statSync(filePath);
|
||||
|
||||
// 情况1:是文件,且通过了校验
|
||||
if (
|
||||
stat.isFile() &&
|
||||
isValidFile(fileName, config) &&
|
||||
!isEntryFile(fileName, config)
|
||||
) {
|
||||
exports.push(buildExportStatement(filePath, config));
|
||||
}
|
||||
// 情况2:是文件夹,且通过了校验,递归扫描子文件夹
|
||||
else if (stat.isDirectory() && isValidDir(filePath, config)) {
|
||||
const subExports = generateExports(filePath, config);
|
||||
exports.push(...subExports);
|
||||
}
|
||||
});
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
async function genIndexFile(config: Config) {
|
||||
const allExports: string[] = [];
|
||||
|
||||
// 遍历所有需要扫描的根目录
|
||||
config.scanDirs.forEach((scanDir) => {
|
||||
// 只有当目录真实存在时才进行扫描
|
||||
if (fs.existsSync(scanDir)) {
|
||||
const exports = generateExports(scanDir, config);
|
||||
allExports.push(...exports);
|
||||
} else {
|
||||
console.warn(`⚠️ 警告:扫描目录不存在,已跳过 -> ${scanDir}`);
|
||||
}
|
||||
});
|
||||
|
||||
// 拼接最终的文件内容:前言 + 导出语句(使用 Set 自动去重)
|
||||
const fileContent = [
|
||||
...(config.preamble ?? []),
|
||||
...Array.from(new Set(allExports)),
|
||||
].join("\n");
|
||||
|
||||
// 写入之前,确保输出目录存在,如果不存在就递归创建
|
||||
if (!fs.existsSync(config.outputDir)) {
|
||||
fs.mkdirSync(config.outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 将内容写入到最终的 index.ts 文件中
|
||||
const outputFilePath = path.join(config.outputDir, config.outputFile);
|
||||
fs.writeFileSync(outputFilePath, fileContent, "utf-8");
|
||||
|
||||
console.log(`✨ 成功生成入口文件: ${outputFilePath}`);
|
||||
}
|
||||
|
||||
// ================= 脚本执行入口 =================
|
||||
try {
|
||||
console.log("🚀 开始扫描并生成入口文件...");
|
||||
await genIndexFile(cssConfig);
|
||||
await genIndexFile(tsConfig);
|
||||
console.log("✅ 脚本执行完毕!");
|
||||
} catch (error) {
|
||||
console.error("❌ 脚本执行失败:", error);
|
||||
process.exit(1); // 如果报错,让进程以非 0 状态码退出
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
@import "./styles/base/root";
|
||||
@import "./styles/utility/size-insensitive/align-content";
|
||||
@import "./styles/utility/size-insensitive/align-items";
|
||||
@import "./styles/utility/size-insensitive/align-self";
|
||||
@import "./styles/utility/size-insensitive/animation";
|
||||
@import "./styles/utility/size-insensitive/box-decoration";
|
||||
@import "./styles/utility/size-insensitive/box-inside";
|
||||
@import "./styles/utility/size-insensitive/box-sizing";
|
||||
@import "./styles/utility/size-insensitive/brand";
|
||||
@import "./styles/utility/size-insensitive/break-after";
|
||||
@import "./styles/utility/size-insensitive/break-before";
|
||||
@import "./styles/utility/size-insensitive/cursor";
|
||||
@import "./styles/utility/size-insensitive/display";
|
||||
@import "./styles/utility/size-insensitive/drop-shadow";
|
||||
@import "./styles/utility/size-insensitive/flex-direction";
|
||||
@import "./styles/utility/size-insensitive/flex-wrap";
|
||||
@import "./styles/utility/size-insensitive/font-family";
|
||||
@import "./styles/utility/size-insensitive/justify-content";
|
||||
@import "./styles/utility/size-insensitive/justify-items";
|
||||
@import "./styles/utility/size-insensitive/justify-self";
|
||||
@import "./styles/utility/size-insensitive/overflow";
|
||||
@import "./styles/utility/size-insensitive/overscroll-behavior";
|
||||
@import "./styles/utility/size-insensitive/position";
|
||||
@import "./styles/utility/size-insensitive/screen-reader";
|
||||
@import "./styles/utility/size-insensitive/theme";
|
||||
@import "./styles/utility/size-insensitive/user-select";
|
||||
@import "./styles/utility/size-insensitive/variant";
|
||||
@import "./styles/utility/size-insensitive/z-index";
|
||||
@import "./styles/utility/size-sensitive/border-radius";
|
||||
@import "./styles/utility/size-sensitive/font-size";
|
||||
@import "./styles/utility/size-sensitive/font-weight";
|
||||
@import "./styles/utility/size-sensitive/gap";
|
||||
@import "./styles/utility/size-sensitive/height";
|
||||
@import "./styles/utility/size-sensitive/margin";
|
||||
@import "./styles/utility/size-sensitive/padding";
|
||||
@import "./styles/utility/size-sensitive/width";
|
||||
@@ -1,7 +0,0 @@
|
||||
import "./index.css";
|
||||
export * from "./recipes/brandRecipe";
|
||||
export * from "./recipes/inlineSizeRecipe";
|
||||
export * from "./recipes/itemSizeRecipe";
|
||||
export * from "./recipes/variantRecipe";
|
||||
export * from "./styles/utils/cpm";
|
||||
export * from "./styles/utils/cvr";
|
||||
Vendored
-4
@@ -1,4 +0,0 @@
|
||||
declare module "*.css" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": false,
|
||||
"emitDeclarationOnly": true,
|
||||
"declaration": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"declarationDir": "./dist"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
".turbo/**/*",
|
||||
".cache/**/*",
|
||||
".vite/**/*",
|
||||
"vite.config.ts",
|
||||
"*.config.ts",
|
||||
"*.config.js",
|
||||
"tsconfig.*.json",
|
||||
"__tests__/**/*",
|
||||
"test/**/*",
|
||||
"tests/**/*",
|
||||
"**/*.test.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.ts",
|
||||
"**/*.spec.tsx",
|
||||
".storybook/**/*",
|
||||
"stories/**/*",
|
||||
"example/**/*",
|
||||
"examples/**/*",
|
||||
"scripts/**/*",
|
||||
".env",
|
||||
".env.*",
|
||||
"public/**/*",
|
||||
"docs/**/*",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2025",
|
||||
"lib": ["ES2025", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"types": ["node", "vite/client"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src", "scripts", "vite.config.ts"]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
|
||||
build: {
|
||||
cssMinify: false,
|
||||
|
||||
lib: {
|
||||
entry: path.resolve(import.meta.dirname, "src/index.ts"),
|
||||
formats: ["es", "cjs"],
|
||||
fileName: (format) => `index.${format}.js`,
|
||||
},
|
||||
|
||||
rolldownOptions: {
|
||||
external: ["react", "react-dom", "react/jsx-runtime"],
|
||||
output: {
|
||||
// 强制将生成的 CSS 命名为 index.css
|
||||
assetFileNames: (assetInfo) => {
|
||||
if (assetInfo.name && assetInfo.name.endsWith(".css")) {
|
||||
return "index.css";
|
||||
}
|
||||
return "[name].[hash][extname]";
|
||||
},
|
||||
globals: {
|
||||
react: "React",
|
||||
"react-dom": "ReactDOM",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
cssCodeSplit: false,
|
||||
outDir: "dist",
|
||||
},
|
||||
});
|
||||
@@ -26,18 +26,16 @@
|
||||
"gen-dts": "tsc -p tsconfig.build.json",
|
||||
"build": "pnpm gen-index && vite build && pnpm gen-dts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dg/css": "workspace:*"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"tinyglobby": "^0.2.16",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.0.10",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3"
|
||||
"vite": "^8.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19",
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
type ComponentPropsWithoutRef,
|
||||
forwardRef,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
|
||||
import { Icon } from "../Icon/Icon";
|
||||
import { SpinnerSvg } from "../../assets/svg/SpinnerSvg";
|
||||
import { cpm } from "../../styles/utils/cpm";
|
||||
import { itemSizeRecipe } from "../../recipes/itemSizeRecipe";
|
||||
import { variantRecipe } from "../../recipes/variantRecipe";
|
||||
import { brandRecipe } from "../../recipes/brandRecipe";
|
||||
import { inlineSizeRecipe } from "../../recipes/inlineSizeRecipe";
|
||||
|
||||
export type ButtonSize = "xs" | "sm" | "md" | "lg";
|
||||
|
||||
export type ButtonProps = ComponentPropsWithoutRef<"button"> & {
|
||||
size?: ButtonSize;
|
||||
variant?: "filled" | "outline" | "subtle" | "ghost";
|
||||
shape?: "rounded" | "square" | "circle";
|
||||
brand?: "success" | "danger" | "info" | "warning" | "emphasize";
|
||||
} & {
|
||||
iconOnly?: boolean;
|
||||
hideIcon?: boolean;
|
||||
loading?: boolean;
|
||||
} & {
|
||||
iconSvg?: ReactNode;
|
||||
loadingIconSvg?: ReactNode;
|
||||
};
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
(props, ref) => {
|
||||
const {
|
||||
size = "md",
|
||||
variant = "filled",
|
||||
shape = "rounded",
|
||||
brand = "info",
|
||||
// ===========================
|
||||
iconOnly = false,
|
||||
hideIcon = false,
|
||||
loading,
|
||||
disabled = false,
|
||||
// =========================
|
||||
className = "",
|
||||
children,
|
||||
// ==========================
|
||||
iconSvg,
|
||||
loadingIconSvg = <SpinnerSvg />,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const mergedClass = cpm(
|
||||
itemSizeRecipe({ size, shape, iconOnly }),
|
||||
variantRecipe({ variant, disabled }),
|
||||
brandRecipe({ brand }),
|
||||
className,
|
||||
);
|
||||
|
||||
const contentClass = loading
|
||||
? cpm("visible--hidden", inlineSizeRecipe({ size }))
|
||||
: inlineSizeRecipe({ size });
|
||||
|
||||
const loadingIconClass = loading ? "position--absolute" : "";
|
||||
|
||||
const icon = <Icon size={size}>{iconSvg}</Icon>;
|
||||
const loadingIcon = (
|
||||
<Icon className={loadingIconClass} size={size}>
|
||||
{loadingIconSvg}
|
||||
</Icon>
|
||||
);
|
||||
|
||||
return (
|
||||
<button ref={ref} className={mergedClass} disabled={disabled} {...rest}>
|
||||
<span className={contentClass}>
|
||||
{!hideIcon && iconSvg && icon}
|
||||
{!iconOnly && children}
|
||||
</span>
|
||||
{loading && loadingIcon}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
@@ -0,0 +1,21 @@
|
||||
import { forwardRef, type ComponentPropsWithoutRef } from "react";
|
||||
import { Slot } from "../../utils/Slot";
|
||||
import { inlineSizeRecipe } from "../../recipes/inlineSizeRecipe";
|
||||
import { cpm } from "../../styles/utils/cpm";
|
||||
|
||||
export type IconProps = {
|
||||
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
|
||||
} & ComponentPropsWithoutRef<"svg">;
|
||||
|
||||
export const Icon = forwardRef<HTMLOrSVGElement, IconProps>((props, ref) => {
|
||||
const { size = "md", className = "", children, ...rest } = props;
|
||||
const mergedClass = cpm(inlineSizeRecipe({ size }), className);
|
||||
|
||||
return (
|
||||
<Slot ref={ref} className={mergedClass} {...rest}>
|
||||
{children}
|
||||
</Slot>
|
||||
);
|
||||
});
|
||||
|
||||
Icon.displayName = "Icon";
|
||||
@@ -1,19 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import { Slot } from "../../utils/Slot";
|
||||
import { useButtonContext } from "./common/ButtonContext";
|
||||
|
||||
export const ButtonIcon = (props: any) => {
|
||||
const { children, ...rest } = props;
|
||||
|
||||
const { slotRegistry } = useButtonContext();
|
||||
|
||||
useEffect(() => {
|
||||
slotRegistry.setSlots?.((prev) => ({
|
||||
...prev,
|
||||
icon: <Slot {...rest}>{children}</Slot>,
|
||||
}));
|
||||
}, [children, slotRegistry]);
|
||||
|
||||
return null;
|
||||
};
|
||||
ButtonIcon.displayName = "ButtonIcon";
|
||||
@@ -1,25 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { useButtonContext } from "./common/ButtonContext";
|
||||
import { Slot } from "../../utils/Slot";
|
||||
|
||||
|
||||
type ButtonLoadingProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const ButtonLoading = (props: ButtonLoadingProps) => {
|
||||
const { children, ...rest } = props;
|
||||
const { slotRegistry } = useButtonContext();
|
||||
|
||||
useEffect(() => {
|
||||
slotRegistry.setSlots?.(prev => ({
|
||||
...prev,
|
||||
loading: <Slot {...rest}>{children}</Slot>,
|
||||
}));
|
||||
}, [children, slotRegistry]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
ButtonLoading.displayName = "ButtonLoading";
|
||||
@@ -1,60 +0,0 @@
|
||||
import { type ComponentPropsWithoutRef, forwardRef } from "react";
|
||||
import { ButtonContext, type ButtonSlot } from "./common/ButtonContext";
|
||||
import { useSlotRegistry } from "../../utils/useSlotRegistry";
|
||||
import { mergeProps } from "../../utils/mergeProps";
|
||||
import { useDefaultedProps } from "../../utils/useDefaultedProps";
|
||||
import { brandRecipe, cpm, itemSizeRecipe, variantRecipe } from "@dg/css";
|
||||
|
||||
export type ButtonRootOwnProps = {
|
||||
size?: "xs" | "sm" | "md" | "lg";
|
||||
variant?: "filled" | "outline" | "subtle" | "ghost";
|
||||
shape?: "rounded" | "square" | "circle";
|
||||
brand?: "success" | "danger" | "info" | "warning" | "emphasize";
|
||||
iconOnly?: boolean;
|
||||
hideIcon?: boolean;
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export type ButtonRootPrimitiveProps = Omit<
|
||||
ComponentPropsWithoutRef<"button">,
|
||||
keyof ButtonRootOwnProps
|
||||
>;
|
||||
|
||||
export type ButtonRootProps = ButtonRootOwnProps & ButtonRootPrimitiveProps;
|
||||
|
||||
export const ButtonRoot = forwardRef<HTMLButtonElement, ButtonRootProps>(
|
||||
(props, ref) => {
|
||||
const defaultedProps = useDefaultedProps(props, {
|
||||
size: "md",
|
||||
variant: "filled",
|
||||
shape: "rounded",
|
||||
brand: "info",
|
||||
iconOnly: false,
|
||||
disabled: false,
|
||||
});
|
||||
const { size, variant, shape, brand, iconOnly, disabled, children } =
|
||||
defaultedProps;
|
||||
|
||||
const clx = cpm(
|
||||
itemSizeRecipe({ size, shape, iconOnly }),
|
||||
variantRecipe({ variant, disabled }),
|
||||
brandRecipe({ brand }),
|
||||
);
|
||||
|
||||
const mergedProps = mergeProps({ className: clx }, defaultedProps);
|
||||
|
||||
const slotRegistry = useSlotRegistry<ButtonSlot>();
|
||||
|
||||
return (
|
||||
<ButtonContext.Provider value={{ slotRegistry }}>
|
||||
<button ref={ref} {...mergedProps}>
|
||||
{slotRegistry.slots?.icon}
|
||||
{children}
|
||||
{slotRegistry.slots?.loading}
|
||||
</button>
|
||||
</ButtonContext.Provider>
|
||||
);
|
||||
},
|
||||
);
|
||||
ButtonRoot.displayName = "Button.Root";
|
||||
@@ -1,26 +0,0 @@
|
||||
// ButtonContext.ts
|
||||
import { createContext, useContext, type ReactNode } from "react";
|
||||
|
||||
export type ButtonSlot = {
|
||||
icon?: ReactNode;
|
||||
loading?: ReactNode;
|
||||
};
|
||||
|
||||
type ButtonContextValue = {
|
||||
slotRegistry: {
|
||||
slots?: ButtonSlot;
|
||||
setSlots?: React.Dispatch<React.SetStateAction<ButtonSlot | undefined>>;
|
||||
};
|
||||
};
|
||||
|
||||
export const ButtonContext = createContext<ButtonContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export const useButtonContext = () => {
|
||||
const ctx = useContext(ButtonContext);
|
||||
if (!ctx) {
|
||||
throw new Error("Button 子组件必须在 Button.Root 内使用");
|
||||
}
|
||||
return ctx;
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
import { ButtonIcon } from "./ButtonIcon";
|
||||
import { ButtonLoading } from "./ButtonLoading";
|
||||
import { ButtonRoot } from "./ButtonRoot";
|
||||
|
||||
export const Button = {
|
||||
Root: ButtonRoot,
|
||||
Icon: ButtonIcon,
|
||||
Loading: ButtonLoading,
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
@import './styles/base/root';
|
||||
@import './styles/utility/size-insensitive/align-content';
|
||||
@import './styles/utility/size-insensitive/align-items';
|
||||
@import './styles/utility/size-insensitive/align-self';
|
||||
@import './styles/utility/size-insensitive/animation';
|
||||
@import './styles/utility/size-insensitive/box-decoration';
|
||||
@import './styles/utility/size-insensitive/box-inside';
|
||||
@import './styles/utility/size-insensitive/box-sizing';
|
||||
@import './styles/utility/size-insensitive/brand';
|
||||
@import './styles/utility/size-insensitive/break-after';
|
||||
@import './styles/utility/size-insensitive/break-before';
|
||||
@import './styles/utility/size-insensitive/cursor';
|
||||
@import './styles/utility/size-insensitive/display';
|
||||
@import './styles/utility/size-insensitive/drop-shadow';
|
||||
@import './styles/utility/size-insensitive/flex-direction';
|
||||
@import './styles/utility/size-insensitive/flex-wrap';
|
||||
@import './styles/utility/size-insensitive/font-family';
|
||||
@import './styles/utility/size-insensitive/justify-content';
|
||||
@import './styles/utility/size-insensitive/justify-items';
|
||||
@import './styles/utility/size-insensitive/justify-self';
|
||||
@import './styles/utility/size-insensitive/overflow';
|
||||
@import './styles/utility/size-insensitive/overscroll-behavior';
|
||||
@import './styles/utility/size-insensitive/position';
|
||||
@import './styles/utility/size-insensitive/screen-reader';
|
||||
@import './styles/utility/size-insensitive/theme';
|
||||
@import './styles/utility/size-insensitive/user-select';
|
||||
@import './styles/utility/size-insensitive/variant';
|
||||
@import './styles/utility/size-insensitive/visibility';
|
||||
@import './styles/utility/size-insensitive/z-index';
|
||||
@import './styles/utility/size-sensitive/border-radius';
|
||||
@import './styles/utility/size-sensitive/font-size';
|
||||
@import './styles/utility/size-sensitive/font-weight';
|
||||
@import './styles/utility/size-sensitive/gap';
|
||||
@import './styles/utility/size-sensitive/height';
|
||||
@import './styles/utility/size-sensitive/margin';
|
||||
@import './styles/utility/size-sensitive/padding';
|
||||
@import './styles/utility/size-sensitive/width';
|
||||
@@ -1,29 +1,32 @@
|
||||
import "./index.css";
|
||||
export * from "./assets/svg/BoldSvg";
|
||||
export * from "./assets/svg/CheckIndicatorSvg";
|
||||
export * from "./assets/svg/ChevronRightSvg";
|
||||
export * from "./assets/svg/CutSvg";
|
||||
export * from "./assets/svg/DownloadSvg";
|
||||
export * from "./assets/svg/FileSvg";
|
||||
export * from "./assets/svg/KeySvg";
|
||||
export * from "./assets/svg/MeshSvg";
|
||||
export * from "./assets/svg/MoonSvg";
|
||||
export * from "./assets/svg/PasteSvg";
|
||||
export * from "./assets/svg/Ruler";
|
||||
export * from "./assets/svg/SearchSvg";
|
||||
export * from "./assets/svg/SettingSvg";
|
||||
export * from "./assets/svg/SpinnerSvg";
|
||||
export * from "./assets/svg/SunSvg";
|
||||
export * from "./assets/svg/UserSvg";
|
||||
export * from "./assets/svg/VolumeHighSvg";
|
||||
export * from "./assets/svg/VolumeLowSvg";
|
||||
export * from "./assets/svg/VolumeMuteSvg";
|
||||
export * from "./componnets/button/index";
|
||||
export * from "./componnets/button/ButtonIcon";
|
||||
export * from "./componnets/button/ButtonLoading";
|
||||
export * from "./componnets/button/ButtonRoot";
|
||||
export * from "./componnets/button/common/ButtonContext";
|
||||
export * from "./utils/mergeProps";
|
||||
export * from "./utils/Slot";
|
||||
export * from "./utils/useDefaultedProps";
|
||||
export * from "./utils/useSlotRegistry";
|
||||
import './index.css';
|
||||
export * from './assets/svg/BoldSvg';
|
||||
export * from './assets/svg/CheckIndicatorSvg';
|
||||
export * from './assets/svg/ChevronRightSvg';
|
||||
export * from './assets/svg/CutSvg';
|
||||
export * from './assets/svg/DownloadSvg';
|
||||
export * from './assets/svg/FileSvg';
|
||||
export * from './assets/svg/KeySvg';
|
||||
export * from './assets/svg/MeshSvg';
|
||||
export * from './assets/svg/MoonSvg';
|
||||
export * from './assets/svg/PasteSvg';
|
||||
export * from './assets/svg/Ruler';
|
||||
export * from './assets/svg/SearchSvg';
|
||||
export * from './assets/svg/SettingSvg';
|
||||
export * from './assets/svg/SpinnerSvg';
|
||||
export * from './assets/svg/SunSvg';
|
||||
export * from './assets/svg/UserSvg';
|
||||
export * from './assets/svg/VolumeHighSvg';
|
||||
export * from './assets/svg/VolumeLowSvg';
|
||||
export * from './assets/svg/VolumeMuteSvg';
|
||||
export * from './componnets/Button/Button';
|
||||
export * from './componnets/Icon/Icon';
|
||||
export * from './recipes/brandRecipe';
|
||||
export * from './recipes/inlineSizeRecipe';
|
||||
export * from './recipes/itemSizeRecipe';
|
||||
export * from './recipes/variantRecipe';
|
||||
export * from './styles/utils/cpm';
|
||||
export * from './styles/utils/cvr';
|
||||
export * from './utils/camelToKebab';
|
||||
export * from './utils/mergeProps';
|
||||
export * from './utils/Slot';
|
||||
export * from './utils/useSlotsRegistry';
|
||||
+10
-16
@@ -5,10 +5,10 @@ export const itemSizeRecipe = cvr({
|
||||
variants: {
|
||||
size: {
|
||||
xs: "text--xs h--item-xs gap--xs px--xs",
|
||||
sm: "text--sm h--item-sm gap-sm px--sm",
|
||||
md: "text--md h--item-md gap-md px--md",
|
||||
lg: "text--lg h--item-lg gap-lg px--lg",
|
||||
xl: "text--xl h--item-xl gap-xl px--xl",
|
||||
sm: "text--sm h--item-sm gap--sm px--sm",
|
||||
md: "text--md h--item-md gap--md px--md",
|
||||
lg: "text--lg h--item-lg gap--lg px--lg",
|
||||
xl: "text--xl h--item-xl gap--xl px--xl",
|
||||
"2xl": "text--2xl h--item-2xl gap--2xl px--2xl",
|
||||
},
|
||||
shape: {
|
||||
@@ -22,19 +22,13 @@ export const itemSizeRecipe = cvr({
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{ iconOnly: false, size: "xs", class: "px--xs" },
|
||||
{ iconOnly: false, size: "sm", class: "px--sm" },
|
||||
{ iconOnly: false, size: "md", class: "px--md" },
|
||||
{ iconOnly: false, size: "lg", class: "px--lg" },
|
||||
{ iconOnly: false, size: "xl", class: "px--xl" },
|
||||
{ iconOnly: false, size: "2xl", class: "px--2xl" },
|
||||
// ==================================================
|
||||
{ shape: "rounded", size: "xs", class: "rounded--sm" },
|
||||
{ shape: "rounded", size: "sm", class: "rounded--md" },
|
||||
{ shape: "rounded", size: "md", class: "rounded--lg" },
|
||||
{ shape: "rounded", size: "lg", class: "rounded--xl" },
|
||||
{ shape: "rounded", size: "xl", class: "rounded--2xl" },
|
||||
{ shape: "rounded", size: "2xl", class: "rounded--3xl" },
|
||||
{ shape: "rounded", size: "xs", class: "rounded--xs" },
|
||||
{ shape: "rounded", size: "sm", class: "rounded--sm" },
|
||||
{ shape: "rounded", size: "md", class: "rounded--md" },
|
||||
{ shape: "rounded", size: "lg", class: "rounded--lg" },
|
||||
{ shape: "rounded", size: "xl", class: "rounded--xl" },
|
||||
{ shape: "rounded", size: "2xl", class: "rounded--2xl" },
|
||||
// ==================================================
|
||||
{ iconOnly: true, size: "xs", class: "w--item-xs" },
|
||||
{ iconOnly: true, size: "sm", class: "w--item-sm" },
|
||||
+1
-1
@@ -16,4 +16,4 @@
|
||||
|
||||
.position--sticky {
|
||||
position: sticky;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
@layer utility {
|
||||
.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
.visible--hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
.visible--collapse {
|
||||
visibility: collapse;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -12,12 +12,12 @@
|
||||
gap: calc(var(--spacing) * 3);
|
||||
}
|
||||
.gap--lg {
|
||||
gap: calc(var(--spacing) * 2);
|
||||
gap: calc(var(--spacing) * 4);
|
||||
}
|
||||
.gap--xl {
|
||||
gap: calc(var(--spacing) * 2.5);
|
||||
gap: calc(var(--spacing) * 5);
|
||||
}
|
||||
.gap--2xl {
|
||||
gap: calc(var(--spacing) * 2.5);
|
||||
gap: calc(var(--spacing) * 6);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -11,7 +11,7 @@
|
||||
|
||||
.w--item-md {
|
||||
/* 34px most used size */
|
||||
width: var(--height-item-lg);
|
||||
width: var(--height-item-md);
|
||||
}
|
||||
|
||||
.w--item-lg {
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
.w--item--2xl {
|
||||
/* 64px navigation bar */
|
||||
width: var(--height-item-xl);
|
||||
width: var(--height-item-2xl);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
@@ -53,4 +53,4 @@
|
||||
.w--inline-xl {
|
||||
width: calc(var(--font-size-2xl) * var(--line-height-2xl));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,14 @@ export function cpm(...classes: (string[] | string)[]) {
|
||||
const map = new Map<string, string>();
|
||||
|
||||
function dealClass(cls: string) {
|
||||
// 提取前缀和后缀
|
||||
const prifix = cls.split("--")[0];
|
||||
const clsArr = cls.trim().split(/\s+/);
|
||||
// 提取前缀
|
||||
clsArr.forEach((i) => {
|
||||
const prifix = i.trim().split("--")[0];
|
||||
|
||||
// 使用set覆盖(自动新建),而不是push
|
||||
map.set(prifix, cls);
|
||||
// 使用set覆盖(自动新建),而不是push
|
||||
map.set(prifix, i);
|
||||
});
|
||||
}
|
||||
|
||||
classes.forEach((item) => {
|
||||
@@ -0,0 +1,3 @@
|
||||
export function camelToKebab(str: string) {
|
||||
return str.replace(/([A-Z])/g, "-$1").toLowerCase();
|
||||
}
|
||||
@@ -9,28 +9,23 @@ function defaultClassMergeFn(...classes: string[]): string {
|
||||
}
|
||||
|
||||
// 工具类型
|
||||
type UnionToIntersection<U> =
|
||||
(U extends any ? (k: U) => void : never) extends
|
||||
(k: infer I) => void ? I : never;
|
||||
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
|
||||
k: infer I,
|
||||
) => void
|
||||
? I
|
||||
: never;
|
||||
|
||||
// 泛型入口
|
||||
export function mergeProps<
|
||||
T extends AnyProps[]
|
||||
>(
|
||||
export function mergeProps<T extends AnyProps[]>(
|
||||
...args: [...T]
|
||||
): UnionToIntersection<T[number]>;
|
||||
|
||||
export function mergeProps<
|
||||
T extends AnyProps[]
|
||||
>(
|
||||
export function mergeProps<T extends AnyProps[]>(
|
||||
options: { classMergeFn: (...cls: string[]) => string },
|
||||
...args: [...T]
|
||||
): UnionToIntersection<T[number]>;
|
||||
|
||||
export function mergeProps(
|
||||
arg1: any,
|
||||
...rest: any[]
|
||||
): any {
|
||||
export function mergeProps(arg1: any, ...rest: any[]): any {
|
||||
let options = {
|
||||
classMergeFn: defaultClassMergeFn,
|
||||
};
|
||||
@@ -104,4 +99,4 @@ export function mergeProps(
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
export function useDefaultedProps<
|
||||
P extends Record<string, any>,
|
||||
D extends Partial<P>,
|
||||
>(props: P, defaults: D): P & D {
|
||||
return useMemo(() => {
|
||||
return { ...defaults, ...props };
|
||||
}, [props, defaults]);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// useButtonSlots.ts
|
||||
import { useState } from "react";
|
||||
|
||||
export function useSlotRegistry<S>() {
|
||||
const [slots, setSlots] = useState<S>()
|
||||
|
||||
return { slots, setSlots }
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
export type UseSlotsRegistryReturn<Names extends readonly string[]> = {
|
||||
slots: Partial<SlotRecord<Names>>;
|
||||
canRender: boolean;
|
||||
generateId: (prefix: string) => string;
|
||||
registerSlot: (
|
||||
id: string,
|
||||
slotName: Names[number],
|
||||
content: React.ReactNode,
|
||||
) => void;
|
||||
markReady: (id: string) => void;
|
||||
};
|
||||
|
||||
type SlotRecord<Names extends readonly string[]> = {
|
||||
[K in Names[number]]: React.ReactNode[];
|
||||
};
|
||||
|
||||
export function useSlotsRegistry<
|
||||
const Names extends readonly string[],
|
||||
>(): UseSlotsRegistryReturn<Names> {
|
||||
type SlotName = Names[number];
|
||||
|
||||
const [slots, setSlots] = useState<Partial<SlotRecord<Names>>>({});
|
||||
const readySet = useRef(new Set<string>());
|
||||
const totalSet = useRef(new Set<string>());
|
||||
const [canRender, setCanRender] = useState(false);
|
||||
const [ids, setIds] = useState<string[]>([]);
|
||||
|
||||
const generateId = (prefix: string): string => {
|
||||
while (true) {
|
||||
const rand = Math.random().toString(36).slice(2, 8);
|
||||
const id = `${prefix}-${rand}`;
|
||||
if (!ids.includes(id)) {
|
||||
setIds((prev) => [...prev, id]);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const registerSlot = (
|
||||
id: string,
|
||||
slotName: SlotName,
|
||||
content: React.ReactNode,
|
||||
) => {
|
||||
totalSet.current.add(id);
|
||||
|
||||
setSlots((prev) => ({
|
||||
...prev,
|
||||
[slotName]: [...(prev[slotName] ?? []), content],
|
||||
}));
|
||||
};
|
||||
|
||||
const markReady = (id: string) => {
|
||||
readySet.current.add(id);
|
||||
|
||||
if (readySet.current.size === totalSet.current.size) {
|
||||
setCanRender(true);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
slots,
|
||||
canRender,
|
||||
generateId,
|
||||
registerSlot,
|
||||
markReady,
|
||||
};
|
||||
}
|
||||
@@ -4,14 +4,12 @@
|
||||
"noEmit": false,
|
||||
"emitDeclarationOnly": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
"outDir": "./dist",
|
||||
"declarationDir": "./dist"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"src/App.tsx",
|
||||
"src/main.tsx",
|
||||
"node_modules",
|
||||
"dist",
|
||||
".turbo/**/*",
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
// 1. 引入 tinyglobby
|
||||
import { globSync } from "tinyglobby";
|
||||
|
||||
interface Config {
|
||||
outputDir: string;
|
||||
outputFilenameWithExt: string;
|
||||
scanDirs: string[];
|
||||
importPrefix: string;
|
||||
predefineStatements: string[];
|
||||
includeExtensions: string[];
|
||||
excludeDirs: string[];
|
||||
excludeFileExtensions: string[];
|
||||
excludePatterns: RegExp[];
|
||||
}
|
||||
|
||||
const cssConfig: Config = {
|
||||
outputDir: "src",
|
||||
outputFilenameWithExt: "index.css",
|
||||
scanDirs: ["src"],
|
||||
importPrefix: "@import",
|
||||
predefineStatements: ['@import "tailwindcss";'],
|
||||
includeExtensions: [".css"],
|
||||
excludeDirs: ["__tests__", "tests", "story", "stories", "types"],
|
||||
excludeFileExtensions: [],
|
||||
excludePatterns: [/^index\.(css)$/, /\.(test|spec)\./, /\.(story|stories)\./],
|
||||
};
|
||||
|
||||
const tsConfig: Config = {
|
||||
outputDir: "src",
|
||||
outputFilenameWithExt: "index.ts",
|
||||
scanDirs: ["src"],
|
||||
importPrefix: "export * from",
|
||||
predefineStatements: ["import './index.css'"],
|
||||
includeExtensions: [".ts", "tsx", "js", "jsx"],
|
||||
excludeDirs: ["__tests__", "tests", "story", "stories", "types"],
|
||||
excludeFileExtensions: [".d.ts"],
|
||||
excludePatterns: [
|
||||
/^index\.(ts|tsx|js|jsx)$/,
|
||||
/\.(test|spec)\./,
|
||||
/\.(story|stories)\./,
|
||||
],
|
||||
};
|
||||
|
||||
const normalizePath = (p: string) => p.replace(/\\/g, "/");
|
||||
|
||||
const isExcludeDir = (filePath: string, excludeDirs: string[]) => {
|
||||
const normalized = normalizePath(filePath);
|
||||
return excludeDirs.some((dir) => normalized.includes(`/${dir}/`));
|
||||
};
|
||||
|
||||
const isExcludeFileExtensions = (
|
||||
filePath: string,
|
||||
excludeFileExtensions: string[],
|
||||
) => excludeFileExtensions.some((ext) => filePath.endsWith(ext));
|
||||
|
||||
const isExcludePattern = (fileName: string, excludePatterns: RegExp[]) =>
|
||||
excludePatterns.some((pattern) => pattern.test(fileName));
|
||||
|
||||
// ----------------------------------------
|
||||
function isValidFile(filePath: string, config: Config): boolean {
|
||||
const fileName = filePath.split(/[\\/]/).pop()!;
|
||||
|
||||
if (isExcludeDir(filePath, config.excludeDirs)) return false;
|
||||
if (isExcludeFileExtensions(filePath, config.excludeFileExtensions))
|
||||
return false;
|
||||
if (isExcludePattern(fileName, config.excludePatterns)) return false;
|
||||
|
||||
const ext = path.extname(filePath);
|
||||
return config.includeExtensions.includes(ext);
|
||||
}
|
||||
// -----------------------------------------
|
||||
function generateIndexFile(config: Config) {
|
||||
const currentPath = process.cwd();
|
||||
const outputPath = path.resolve(currentPath, config.outputDir);
|
||||
let exportStatements: string[] = [];
|
||||
|
||||
// ------ scanDirs forEach start ------------------------
|
||||
config.scanDirs.forEach((dir) => {
|
||||
// 2. 路径模式保持不变,tinyglobby 能够正确处理
|
||||
const scanPattern = path.resolve(currentPath, dir, "**", "*.*");
|
||||
|
||||
const allFilePath = globSync(scanPattern, {
|
||||
absolute: true,
|
||||
// 3. 移除了 windowsPathsNoEscape,tinyglobby 默认处理路径更智能
|
||||
});
|
||||
|
||||
const validFiles = allFilePath.filter((filePath) => {
|
||||
return isValidFile(filePath, config);
|
||||
});
|
||||
|
||||
if (validFiles.length === 0) {
|
||||
console.log(
|
||||
`⚠️ 未找到符合条件的文件,跳过生成 ${config.outputFilenameWithExt}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
validFiles.sort();
|
||||
|
||||
validFiles.forEach((file) => {
|
||||
const relativePath = path.relative(outputPath, file);
|
||||
const importPath = `./${relativePath.replace(/\\/g, "/")}`;
|
||||
exportStatements.push(`${config.importPrefix} '${importPath}';`);
|
||||
});
|
||||
});
|
||||
|
||||
// --------- scanDirs forEach end ----------------
|
||||
|
||||
const indexFileContent = `
|
||||
${config.predefineStatements.join("\n")}
|
||||
${exportStatements.join("\n")}
|
||||
`.trim();
|
||||
|
||||
const indexFilePath = path.resolve(
|
||||
currentPath,
|
||||
config.outputDir,
|
||||
config.outputFilenameWithExt,
|
||||
);
|
||||
|
||||
// ✅ 内容比对,避免重复写入
|
||||
if (fs.existsSync(indexFilePath)) {
|
||||
const old = fs.readFileSync(indexFilePath, "utf8");
|
||||
if (old === indexFileContent) {
|
||||
console.log(
|
||||
`✅ ${config.outputFilenameWithExt} 内容无变化,无需重新生成`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(indexFilePath, indexFileContent, "utf8");
|
||||
console.log(`✅ 成功生成 ${config.outputFilenameWithExt}: ${indexFilePath}`);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
|
||||
try {
|
||||
console.log(`🚀 [gen-index] 开始扫描`);
|
||||
generateIndexFile(cssConfig);
|
||||
generateIndexFile(tsConfig);
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.error(`❌ [gen-index] 执行失败: ${msg}`);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -6,58 +6,28 @@ export const ButtonGallery = () => {
|
||||
return (
|
||||
<OW>
|
||||
<IW>
|
||||
<Button.Root size="xs">XSmall</Button.Root>
|
||||
<Button.Root size="xs">
|
||||
<Button.Icon>
|
||||
<DownloadSvg />
|
||||
</Button.Icon>
|
||||
<Button size="xs">XSmall</Button>
|
||||
<Button size="xs" iconSvg={<DownloadSvg />}>
|
||||
XSmall
|
||||
</Button.Root>
|
||||
<Button.Root size="xs" iconOnly>
|
||||
</Button>
|
||||
<Button size="xs" iconOnly={true} iconSvg={<DownloadSvg />}>
|
||||
XSmall
|
||||
</Button.Root>
|
||||
// ==============================================
|
||||
<Button.Root size="sm">Small</Button.Root>
|
||||
<Button.Root size="sm">
|
||||
<Button.Icon>
|
||||
<DownloadSvg />
|
||||
</Button.Icon>
|
||||
</Button>
|
||||
<Button size="xs" loading={true} iconSvg={<DownloadSvg />}>
|
||||
XSmall
|
||||
</Button>
|
||||
</IW>
|
||||
<IW>
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="sm" iconSvg={<DownloadSvg />}>
|
||||
Small
|
||||
</Button.Root>
|
||||
<Button.Root size="sm" iconOnly>
|
||||
<Button.Icon>
|
||||
<DownloadSvg />
|
||||
</Button.Icon>
|
||||
</Button>
|
||||
<Button size="sm" iconOnly={true} iconSvg={<DownloadSvg />}>
|
||||
Small
|
||||
</Button.Root>
|
||||
// ==============================================
|
||||
<Button.Root size="md">Medium</Button.Root>
|
||||
<Button.Root size="md">
|
||||
<Button.Icon>
|
||||
<DownloadSvg />
|
||||
</Button.Icon>
|
||||
Medium
|
||||
</Button.Root>
|
||||
<Button.Root size="md" iconOnly>
|
||||
<Button.Icon>
|
||||
<DownloadSvg />
|
||||
</Button.Icon>
|
||||
Medium
|
||||
</Button.Root>
|
||||
// ==============================================
|
||||
<Button.Root size="lg">Large</Button.Root>
|
||||
<Button.Root size="lg">
|
||||
<Button.Icon>
|
||||
<DownloadSvg />
|
||||
</Button.Icon>
|
||||
Large
|
||||
</Button.Root>
|
||||
<Button.Root size="lg" iconOnly>
|
||||
<Button.Icon>
|
||||
<DownloadSvg />
|
||||
</Button.Icon>
|
||||
Large
|
||||
</Button.Root>
|
||||
</Button>
|
||||
<Button size="sm" loading={true} iconSvg={<DownloadSvg />}>
|
||||
Small
|
||||
</Button>
|
||||
</IW>
|
||||
</OW>
|
||||
);
|
||||
|
||||
Generated
-37
@@ -12,45 +12,8 @@ importers:
|
||||
specifier: ^2.8.0
|
||||
version: 2.9.12
|
||||
|
||||
packages/css:
|
||||
dependencies:
|
||||
react:
|
||||
specifier: ^19
|
||||
version: 19.2.6
|
||||
react-dom:
|
||||
specifier: ^19
|
||||
version: 19.2.6(react@19.2.6)
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^25.6.0
|
||||
version: 25.6.2
|
||||
'@types/react':
|
||||
specifier: ^19.2.14
|
||||
version: 19.2.14
|
||||
'@types/react-dom':
|
||||
specifier: ^19.2.3
|
||||
version: 19.2.3(@types/react@19.2.14)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1(vite@8.0.12(@types/node@25.6.2)(jiti@2.7.0))
|
||||
tinyglobby:
|
||||
specifier: ^0.2.16
|
||||
version: 0.2.16
|
||||
ts-node:
|
||||
specifier: ^10.9.2
|
||||
version: 10.9.2(@types/node@25.6.2)(typescript@6.0.3)
|
||||
typescript:
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
vite:
|
||||
specifier: ^8.0.10
|
||||
version: 8.0.12(@types/node@25.6.2)(jiti@2.7.0)
|
||||
|
||||
packages/ui-react:
|
||||
dependencies:
|
||||
'@dg/css':
|
||||
specifier: workspace:*
|
||||
version: link:../css
|
||||
react:
|
||||
specifier: ^19
|
||||
version: 19.2.6
|
||||
|
||||
Reference in New Issue
Block a user