Initial commit: Re-connected to Gitea
This commit is contained in:
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# 基础忽略规则
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# 根目录依赖/构建产物
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# Monorepo 子包专属忽略(核心)
|
||||||
|
packages/**/dist/
|
||||||
|
packages/**/node_modules/
|
||||||
|
packages/**/build/
|
||||||
|
packages/**/coverage/
|
||||||
|
packages/**/.env*
|
||||||
|
packages/**/npm-debug.log*
|
||||||
|
packages/**/yarn-debug.log*
|
||||||
|
packages/**/yarn-error.log*
|
||||||
|
packages/**/pnpm-debug.log*
|
||||||
0
.turbo/cookies/1.cookie
Normal file
0
.turbo/cookies/1.cookie
Normal file
2
.turbo/daemon/934a1a5ec62dbadc-turbo.log.2026-02-24
Normal file
2
.turbo/daemon/934a1a5ec62dbadc-turbo.log.2026-02-24
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
2026-02-24T21:27:52.486904Z WARN daemon_server: turborepo_lib::commands::daemon: daemon already running
|
||||||
|
2026-02-24T21:36:52.418729Z WARN daemon_server: turborepo_lib::commands::daemon: daemon already running
|
||||||
0
.turbo/daemon/934a1a5ec62dbadc-turbo.log.2026-02-25
Normal file
0
.turbo/daemon/934a1a5ec62dbadc-turbo.log.2026-02-25
Normal file
1
.turbo/daemon/934a1a5ec62dbadc-turbo.log.2026-02-26
Normal file
1
.turbo/daemon/934a1a5ec62dbadc-turbo.log.2026-02-26
Normal file
@@ -0,0 +1 @@
|
|||||||
|
2026-02-26T00:32:05.147541Z WARN daemon_server: turborepo_lib::commands::daemon: daemon already running
|
||||||
24
apps/ui-site/.gitignore
vendored
Normal file
24
apps/ui-site/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
73
apps/ui-site/README.md
Normal file
73
apps/ui-site/README.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# React + TypeScript + Vite
|
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||||
|
|
||||||
|
Currently, two official plugins are available:
|
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||||
|
|
||||||
|
## React Compiler
|
||||||
|
|
||||||
|
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||||
|
|
||||||
|
## Expanding the ESLint configuration
|
||||||
|
|
||||||
|
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
// Other configs...
|
||||||
|
|
||||||
|
// Remove tseslint.configs.recommended and replace with this
|
||||||
|
tseslint.configs.recommendedTypeChecked,
|
||||||
|
// Alternatively, use this for stricter rules
|
||||||
|
tseslint.configs.strictTypeChecked,
|
||||||
|
// Optionally, add this for stylistic rules
|
||||||
|
tseslint.configs.stylisticTypeChecked,
|
||||||
|
|
||||||
|
// Other configs...
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
// other options...
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// eslint.config.js
|
||||||
|
import reactX from 'eslint-plugin-react-x'
|
||||||
|
import reactDom from 'eslint-plugin-react-dom'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
// Other configs...
|
||||||
|
// Enable lint rules for React
|
||||||
|
reactX.configs['recommended-typescript'],
|
||||||
|
// Enable lint rules for React DOM
|
||||||
|
reactDom.configs.recommended,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
// other options...
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
```
|
||||||
23
apps/ui-site/eslint.config.js
Normal file
23
apps/ui-site/eslint.config.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
js.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
reactHooks.configs.flat.recommended,
|
||||||
|
reactRefresh.configs.vite,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
13
apps/ui-site/index.html
Normal file
13
apps/ui-site/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>ui-site</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
31
apps/ui-site/package.json
Normal file
31
apps/ui-site/package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "ui-site",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0",
|
||||||
|
"@defgov/ui-headless": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.1",
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
|
"@types/react": "^19.2.7",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
|
"eslint": "^9.39.1",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
|
"globals": "^16.5.0",
|
||||||
|
"typescript": "~5.9.3",
|
||||||
|
"typescript-eslint": "^8.48.0",
|
||||||
|
"vite": "^7.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
apps/ui-site/public/vite.svg
Normal file
1
apps/ui-site/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
18
apps/ui-site/src/App.tsx
Normal file
18
apps/ui-site/src/App.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { ThemeProvider } from "@defgov/ui";
|
||||||
|
import { ButtonGallery } from "./gallery/ButtonGallery";
|
||||||
|
import { CheckboxGallery } from "./gallery/CheckboxGallery";
|
||||||
|
import { RadioGallery } from "./gallery/RadioGallery";
|
||||||
|
import { TooltipGallery } from "./gallery/TootipGallery";
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<ThemeProvider>
|
||||||
|
<ButtonGallery />
|
||||||
|
<CheckboxGallery />
|
||||||
|
<RadioGallery />
|
||||||
|
<TooltipGallery />
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
1
apps/ui-site/src/assets/react.svg
Normal file
1
apps/ui-site/src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
21
apps/ui-site/src/common/InnerWrap.tsx
Normal file
21
apps/ui-site/src/common/InnerWrap.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { type ReactNode } from "react";
|
||||||
|
|
||||||
|
interface WrapProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InnerWrap({ children }: WrapProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
margin: "20px",
|
||||||
|
gap: "20px",
|
||||||
|
alignItems: "center",
|
||||||
|
flexDirection: "row",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
apps/ui-site/src/common/OuterWrap.tsx
Normal file
20
apps/ui-site/src/common/OuterWrap.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { type ReactNode } from "react";
|
||||||
|
|
||||||
|
interface WrapProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OuterWrap({ children }: WrapProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
margin: "20px",
|
||||||
|
gap: "20px",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
52
apps/ui-site/src/gallery/ButtonGallery.tsx
Normal file
52
apps/ui-site/src/gallery/ButtonGallery.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { Button, DownloadSvg } from "@defgov/ui";
|
||||||
|
import { OuterWrap } from "../common/OuterWrap";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { InnerWrap } from "../common/InnerWrap";
|
||||||
|
|
||||||
|
export const ButtonGallery = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
return (
|
||||||
|
<OuterWrap>
|
||||||
|
<InnerWrap>
|
||||||
|
<Button size="xs" iconSvg={<DownloadSvg />}>
|
||||||
|
Ag
|
||||||
|
</Button>
|
||||||
|
<Button size="xs" iconSvg={<DownloadSvg />} iconOnly={true}>
|
||||||
|
Buttun
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" iconSvg={<DownloadSvg />}>
|
||||||
|
Buttun
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" iconSvg={<DownloadSvg />} iconOnly={true}>
|
||||||
|
Buttun
|
||||||
|
</Button>
|
||||||
|
<Button size="md" iconSvg={<DownloadSvg />}>
|
||||||
|
Buttun
|
||||||
|
</Button>
|
||||||
|
<Button size="md" iconSvg={<DownloadSvg />} iconOnly={true}>
|
||||||
|
Buttun
|
||||||
|
</Button>
|
||||||
|
<Button size="lg" iconSvg={<DownloadSvg />}>
|
||||||
|
Buttun
|
||||||
|
</Button>
|
||||||
|
<Button size="lg" iconSvg={<DownloadSvg />} iconOnly={true}>
|
||||||
|
Buttun
|
||||||
|
</Button>
|
||||||
|
</InnerWrap>
|
||||||
|
|
||||||
|
<InnerWrap>
|
||||||
|
<Button variant="filled">Buttun</Button>
|
||||||
|
<Button variant="outline">Buttun</Button>
|
||||||
|
<Button variant="subtle">Buttun</Button>
|
||||||
|
<Button
|
||||||
|
isLoading={loading}
|
||||||
|
onClick={() => {
|
||||||
|
setLoading(!loading);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Buttun
|
||||||
|
</Button>
|
||||||
|
</InnerWrap>
|
||||||
|
</OuterWrap>
|
||||||
|
);
|
||||||
|
};
|
||||||
14
apps/ui-site/src/gallery/CheckboxGallery.tsx
Normal file
14
apps/ui-site/src/gallery/CheckboxGallery.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Checkbox } from "@defgov/ui";
|
||||||
|
import { InnerWrap } from "../common/InnerWrap";
|
||||||
|
import { OuterWrap } from "../common/OuterWrap";
|
||||||
|
|
||||||
|
export const CheckboxGallery = () => {
|
||||||
|
return (
|
||||||
|
<OuterWrap>
|
||||||
|
<InnerWrap>
|
||||||
|
<Checkbox size="xs">Apple</Checkbox>
|
||||||
|
<Checkbox>Apple</Checkbox>
|
||||||
|
</InnerWrap>
|
||||||
|
</OuterWrap>
|
||||||
|
);
|
||||||
|
};
|
||||||
22
apps/ui-site/src/gallery/RadioGallery.tsx
Normal file
22
apps/ui-site/src/gallery/RadioGallery.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Radio, RadioGroup } from "@defgov/ui";
|
||||||
|
import { InnerWrap } from "../common/InnerWrap";
|
||||||
|
import { OuterWrap } from "../common/OuterWrap";
|
||||||
|
|
||||||
|
export const RadioGallery = () => {
|
||||||
|
return (
|
||||||
|
<OuterWrap>
|
||||||
|
<InnerWrap>
|
||||||
|
<RadioGroup name="fruit" size="xs">
|
||||||
|
<Radio value="apple">Apple</Radio>
|
||||||
|
<Radio value="orange">Orange</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</InnerWrap>
|
||||||
|
<InnerWrap>
|
||||||
|
<RadioGroup name="fruit" direction="vertical" label="Fruit">
|
||||||
|
<Radio value="apple">Apple</Radio>
|
||||||
|
<Radio value="orange">Orange</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</InnerWrap>
|
||||||
|
</OuterWrap>
|
||||||
|
);
|
||||||
|
};
|
||||||
15
apps/ui-site/src/gallery/TootipGallery.tsx
Normal file
15
apps/ui-site/src/gallery/TootipGallery.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Button, CutSvg, Tooltip } from "@defgov/ui";
|
||||||
|
import { OuterWrap } from "../common/OuterWrap";
|
||||||
|
import { InnerWrap } from "../common/InnerWrap";
|
||||||
|
|
||||||
|
export const TooltipGallery = () => {
|
||||||
|
return (
|
||||||
|
<OuterWrap>
|
||||||
|
<InnerWrap>
|
||||||
|
<Tooltip title="cut">
|
||||||
|
<Button iconSvg={<CutSvg />} iconOnly={true}></Button>
|
||||||
|
</Tooltip>
|
||||||
|
</InnerWrap>
|
||||||
|
</OuterWrap>
|
||||||
|
);
|
||||||
|
};
|
||||||
10
apps/ui-site/src/main.tsx
Normal file
10
apps/ui-site/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import "@defgov/ui/index.css";
|
||||||
|
import { StrictMode } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import App from "./App.tsx";
|
||||||
|
|
||||||
|
createRoot(document.getElementById("root")!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
);
|
||||||
28
apps/ui-site/tsconfig.app.json
Normal file
28
apps/ui-site/tsconfig.app.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
7
apps/ui-site/tsconfig.json
Normal file
7
apps/ui-site/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
26
apps/ui-site/tsconfig.node.json
Normal file
26
apps/ui-site/tsconfig.node.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
7
apps/ui-site/vite.config.ts
Normal file
7
apps/ui-site/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
})
|
||||||
16
package.json
Normal file
16
package.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "defgov",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo run build",
|
||||||
|
"dev": "turbo run dev"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"turbo": "^2.8.0"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
}
|
||||||
51
packages/ui/package.json
Normal file
51
packages/ui/package.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "@defgov/ui",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"sideEffects": [
|
||||||
|
"*.css"
|
||||||
|
],
|
||||||
|
"main": "./dist/index.es.js",
|
||||||
|
"module": "./dist/index.es.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"style": "./dist/index.css",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/index.es.js",
|
||||||
|
"types": "./dist/index.d.ts"
|
||||||
|
},
|
||||||
|
"./index.css": "./dist/index.css"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"dev": "pnpm gen-index && tsc && vite build -w",
|
||||||
|
"build": "pnpm gen-index && tsc && vite build",
|
||||||
|
"gen-index": "ts-node scripts/generate-index.ts && ts-node scripts/generate-index-css.ts"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tsconfig/vite-react": "^7.0.2",
|
||||||
|
"@types/node": "^25.1.0",
|
||||||
|
"@types/react": "^19.2.10",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^5.1.2",
|
||||||
|
"autoprefixer": "^10.4.24",
|
||||||
|
"glob": "^13.0.3",
|
||||||
|
"prettier": "^3.8.1",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"vite": "^7.3.1",
|
||||||
|
"vite-plugin-dts": "^4.5.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/react": "^0.27.18",
|
||||||
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
|
"react": "^19.2.4",
|
||||||
|
"react-dom": "^19.2.4",
|
||||||
|
"tailwind-merge": "^3.5.0",
|
||||||
|
"tailwind-variants": "^3.2.2",
|
||||||
|
"tailwindcss": "^4.2.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
157
packages/ui/scripts/generate-index-css.ts
Normal file
157
packages/ui/scripts/generate-index-css.ts
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
// scripts/generate-index-use.ts
|
||||||
|
import { writeFileSync, readFileSync } from "fs";
|
||||||
|
import { extname, relative, resolve, basename } from "path";
|
||||||
|
import { globSync } from "glob";
|
||||||
|
|
||||||
|
interface Config {
|
||||||
|
targetDirs: string[];
|
||||||
|
includeExtensions: string[];
|
||||||
|
importSyntax: "@import";
|
||||||
|
importSyntaxTail?: string;
|
||||||
|
excludeFilePattern: RegExp[];
|
||||||
|
excludeDirs: string[];
|
||||||
|
warnDuplicateTailwindImport: boolean;
|
||||||
|
indexFileName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置项
|
||||||
|
const srcDirConfig: Config = {
|
||||||
|
targetDirs: ["src"], // 包含的文件夹
|
||||||
|
includeExtensions: [".css"], // 目标文件后缀
|
||||||
|
importSyntax: "@import", // 导入时使用的语法
|
||||||
|
excludeFilePattern: [/index\.css/, /index\.scss/, /\.(test|spec)\./], // 正则若匹配则排除
|
||||||
|
// 排除的文件夹
|
||||||
|
excludeDirs: [
|
||||||
|
"__tests__",
|
||||||
|
"tests",
|
||||||
|
"story",
|
||||||
|
"stories",
|
||||||
|
"types",
|
||||||
|
"node_modules",
|
||||||
|
"dist",
|
||||||
|
"build",
|
||||||
|
],
|
||||||
|
warnDuplicateTailwindImport: true,
|
||||||
|
indexFileName: "index.css", // 生成的入口文件名称
|
||||||
|
};
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是有效 CSS 文件
|
||||||
|
* @param filePath 文件绝对路径
|
||||||
|
* @returns 是否为有效文件
|
||||||
|
*/
|
||||||
|
function isValidFile(filePath: string, config: Config): boolean {
|
||||||
|
// 过滤排除的文件
|
||||||
|
const filenameWithExt = filePath.split(/[\\/]/).pop()!; // “\\” 匹配反斜杠 “\”,“/” 匹配正斜杠 “/”,pop 返回最后一个 “/” 后面的内容
|
||||||
|
const shouldExcludeFile = config.excludeFilePattern.some((pattern) =>
|
||||||
|
pattern.test(filenameWithExt),
|
||||||
|
);
|
||||||
|
if (shouldExcludeFile) {
|
||||||
|
return false; // 如果匹配到排除规则,跳过当前文件
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤排除的文件夹
|
||||||
|
const normalizedFilePath = filePath.replace(/\\/g, "/"); // “\\” 匹配反斜杠 “\”,“/” 匹配正斜杠 “/”
|
||||||
|
const shouldExcludeDir = config.excludeDirs.some(
|
||||||
|
(dir) => normalizedFilePath.includes(`/${dir}/`), // 前后都有“/” 是为了避免误匹配到文件名(如 test.js 不会被排除)
|
||||||
|
);
|
||||||
|
if (shouldExcludeDir) return false; // 如果匹配到排除规则,跳过当前文件
|
||||||
|
|
||||||
|
// 过滤非目标扩展名,只有指定的才通过
|
||||||
|
const ext = extname(filePath);
|
||||||
|
const isValidExt = config.includeExtensions.includes(ext);
|
||||||
|
|
||||||
|
if (!isValidExt) {
|
||||||
|
return false; // 如果不是指定扩展名,跳过当前文件
|
||||||
|
}
|
||||||
|
|
||||||
|
// @import "tailwindcss" 重复警告,生成的入口文件会自动加一条,其他文件无需重复导入
|
||||||
|
if (config.warnDuplicateTailwindImport) {
|
||||||
|
try {
|
||||||
|
const fileContent = readFileSync(filePath, "utf-8");
|
||||||
|
if (
|
||||||
|
fileContent.includes('@import "tailwindcss"') ||
|
||||||
|
fileContent.includes("@import 'tailwindcss'")
|
||||||
|
) {
|
||||||
|
console.warn(
|
||||||
|
`${filePath} 中含有重复的 @import "tailwindcss" 导入,应手动删除`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
const errorMsg = e instanceof Error ? e.message : String(e);
|
||||||
|
console.warn(`⚠️ 读取文件内容失败(跳过过滤): ${filePath}`, errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 “@import” 导入语句
|
||||||
|
*/
|
||||||
|
function generateIndexFile(config: Config) {
|
||||||
|
const [targetDir] = config.targetDirs;
|
||||||
|
const dirPath = resolve(process.cwd(), targetDir); //目标文件夹绝对路径, 通常是 /project/src
|
||||||
|
|
||||||
|
const searchPattern = resolve(dirPath, "**", "*.*"); // 搜索路径 “dirPath/**/*”
|
||||||
|
|
||||||
|
const allFiles = globSync(searchPattern, {
|
||||||
|
nodir: true, // 不匹配文件夹,只匹配文件
|
||||||
|
absolute: true, // 返回从根目录开始的绝对路径
|
||||||
|
windowsPathsNoEscape: true, // 禁用 Windows 路径的反斜杠转义
|
||||||
|
dot: false, // 不配隐藏文件或目录(以 . 开头的文件)
|
||||||
|
follow: true, // 跟踪符号链接(symlinks),继续解析并返回链接的目标文件或目录
|
||||||
|
});
|
||||||
|
|
||||||
|
const validFiles = allFiles.filter((value) => isValidFile(value, config)); // 过滤出要导入的有效 CSS 文件
|
||||||
|
console.log(`✅ 有效 CSS 文件数量: ${validFiles.length}`);
|
||||||
|
validFiles.sort(); // 排序
|
||||||
|
|
||||||
|
const importStatements = validFiles.map((file) => {
|
||||||
|
const relPath = relative(dirPath, file); // 算出 src 与 css文件 的相对路径
|
||||||
|
let importPath = "./" + relPath.replace(/\\/g, "/"); // 给相对路径前面加上 “./”,并将 “\\” 替换成 “/”
|
||||||
|
// 拼接 @import 语句
|
||||||
|
if (config.importSyntaxTail !== undefined) {
|
||||||
|
return `${config.importSyntax} '${importPath}' ${config.importSyntaxTail};`;
|
||||||
|
} else {
|
||||||
|
return `${config.importSyntax} '${importPath}';`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// index.css 文件的顶部内容
|
||||||
|
const indexContent = `
|
||||||
|
@import "tailwindcss";
|
||||||
|
${importStatements.join("\n")}
|
||||||
|
|
||||||
|
`.trim(); // 使用 trim 移除开头多余的换行
|
||||||
|
|
||||||
|
const indexFilePath = resolve(dirPath, config.indexFileName);
|
||||||
|
|
||||||
|
// 创建和写入 index.css 文件
|
||||||
|
writeFileSync(indexFilePath, indexContent, "utf-8");
|
||||||
|
console.log(`✅ 成功生成 ${config.indexFileName}: ${indexFilePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主函数
|
||||||
|
*/
|
||||||
|
function main() {
|
||||||
|
try {
|
||||||
|
generateIndexFile(srcDirConfig);
|
||||||
|
} catch (e) {
|
||||||
|
const errorMsg = e instanceof Error ? e.message : String(e);
|
||||||
|
console.error(
|
||||||
|
`❌ 文件夹 ${srcDirConfig.targetDirs} 扫描和生成 ${srcDirConfig.indexFileName} 失败:`,
|
||||||
|
errorMsg,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
148
packages/ui/scripts/generate-index.ts
Normal file
148
packages/ui/scripts/generate-index.ts
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
// scripts/generate-index.ts
|
||||||
|
import { writeFileSync } from "fs";
|
||||||
|
import { extname, relative, resolve } from "path";
|
||||||
|
import { globSync } from "glob";
|
||||||
|
|
||||||
|
interface Config {
|
||||||
|
targetDirs: string[];
|
||||||
|
includeExtensions: string[];
|
||||||
|
excludeKeywords: {
|
||||||
|
dirs: string[];
|
||||||
|
fileSuffixes: string[];
|
||||||
|
filePatterns: RegExp[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONFIG: Config = {
|
||||||
|
targetDirs: ["src"],
|
||||||
|
includeExtensions: [".ts", ".tsx", ".vue"],
|
||||||
|
excludeKeywords: {
|
||||||
|
dirs: ["__tests__", "tests", "story", "stories", "types"],
|
||||||
|
fileSuffixes: [".d.ts"],
|
||||||
|
filePatterns: [
|
||||||
|
/^index\.(ts|tsx|js|jsx)$/, // index文件
|
||||||
|
/\.(test|spec)\./, // 测试文件 .test.xxx / .spec.xxx
|
||||||
|
/\.(story|stories)\./, // Storybook文件 .story.xxx / .stories.xxx
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一路径分隔符(兼容 Windows/Linux)
|
||||||
|
* @param path 原始路径
|
||||||
|
* @returns 标准化路径(全部转为 /)
|
||||||
|
*/
|
||||||
|
const normalizePath = (path: string): string => path.replace(/\\/g, "/");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文件是否在排除目录中
|
||||||
|
* @param filePath 文件路径
|
||||||
|
* @returns 是否在排除目录
|
||||||
|
*/
|
||||||
|
const isInExcludeDir = (filePath: string): boolean => {
|
||||||
|
const normalizedPath = normalizePath(filePath);
|
||||||
|
return CONFIG.excludeKeywords.dirs.some((dir) =>
|
||||||
|
normalizedPath.includes(`/${dir}/`),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文件是否匹配排除正则
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @returns 是否匹配排除规则
|
||||||
|
*/
|
||||||
|
const isMatchExcludePattern = (fileName: string): boolean => {
|
||||||
|
return CONFIG.excludeKeywords.filePatterns.some((pattern) =>
|
||||||
|
pattern.test(fileName),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文件是否为排除后缀(如 .d.ts)
|
||||||
|
* @param filePath 文件路径
|
||||||
|
* @returns 是否为排除后缀
|
||||||
|
*/
|
||||||
|
const isExcludeSuffix = (filePath: string): boolean => {
|
||||||
|
return CONFIG.excludeKeywords.fileSuffixes.some((suffix) =>
|
||||||
|
filePath.endsWith(suffix),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function isValidFile(filePath: string): boolean {
|
||||||
|
const fileName = filePath.split(/[\\/]/).pop()!;
|
||||||
|
|
||||||
|
// 按优先级过滤:目录 > 后缀 > 文件名规则 > 扩展名
|
||||||
|
if (isInExcludeDir(filePath)) return false;
|
||||||
|
if (isExcludeSuffix(filePath)) return false;
|
||||||
|
if (isMatchExcludePattern(fileName)) return false;
|
||||||
|
|
||||||
|
const ext = extname(filePath);
|
||||||
|
const isValidExt = CONFIG.includeExtensions.includes(ext);
|
||||||
|
|
||||||
|
return isValidExt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 5. 生成索引文件(错误处理 + 语法规范) ==========
|
||||||
|
function generateIndexFile(dirPath: string): void {
|
||||||
|
const searchPattern = resolve(dirPath, "**", "*.*");
|
||||||
|
|
||||||
|
const allFiles = globSync(searchPattern, {
|
||||||
|
nodir: true,
|
||||||
|
absolute: true,
|
||||||
|
windowsPathsNoEscape: true,
|
||||||
|
dot: false,
|
||||||
|
follow: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (allFiles.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validFiles = allFiles.filter(isValidFile);
|
||||||
|
if (validFiles.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
validFiles.sort();
|
||||||
|
|
||||||
|
// 生成导出语句
|
||||||
|
const exportStatements = validFiles.map((file) => {
|
||||||
|
const relPath = relative(dirPath, file);
|
||||||
|
const importPath = `./${relPath.replace(/\.[^.]+$/, "").replace(/\\/g, "/")}`;
|
||||||
|
return `export * from '${importPath}';`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成索引文件内容(模板字符串格式化)
|
||||||
|
const indexContent = `
|
||||||
|
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
${exportStatements.join("\n")}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const indexFilePath = resolve(dirPath, "index.ts");
|
||||||
|
writeFileSync(indexFilePath, indexContent, "utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 6. 主函数(完善错误处理 + 类型安全) ==========
|
||||||
|
function main(): void {
|
||||||
|
const [targetDir] = CONFIG.targetDirs;
|
||||||
|
if (!targetDir) {
|
||||||
|
console.error(`❌ 未配置目标扫描目录,请检查 CONFIG.targetDirs`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const absTargetDir = resolve(process.cwd(), targetDir);
|
||||||
|
|
||||||
|
try {
|
||||||
|
generateIndexFile(absTargetDir);
|
||||||
|
} catch (error) {
|
||||||
|
// 处理 unknown 类型错误(TypeScript 最佳实践)
|
||||||
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||||
|
console.error(`❌ 生成 index.ts 失败: ${errorMsg}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行主函数
|
||||||
|
main();
|
||||||
16
packages/ui/src/assets/svg/CheckIndicatorOutlineSvg.tsx
Normal file
16
packages/ui/src/assets/svg/CheckIndicatorOutlineSvg.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export const CheckIndicatorOutlineSvg = (
|
||||||
|
props: React.SVGProps<SVGSVGElement>,
|
||||||
|
) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M19 5v14H5V5zm0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
14
packages/ui/src/assets/svg/CheckIndicatorSvg.tsx
Normal file
14
packages/ui/src/assets/svg/CheckIndicatorSvg.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export const CheckIndicatorSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2m0 16H5V5h14zM17.99 9l-1.41-1.42l-6.59 6.59l-2.58-2.57l-1.42 1.41l4 3.99z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
19
packages/ui/src/assets/svg/ChevronRightSvg.tsx
Normal file
19
packages/ui/src/assets/svg/ChevronRightSvg.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
export const ChevronRightSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="m9 6l6 6l-6 6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
14
packages/ui/src/assets/svg/CutSvg.tsx
Normal file
14
packages/ui/src/assets/svg/CutSvg.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export const CutSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M12.14 9.342L7.37 2.329a.75.75 0 1 0-1.24.844l5.13 7.545l-2.395 3.743a4 4 0 1 0 1.178.943l2.135-3.337l2.065 3.036a4 4 0 1 0 1.261-.813l-2.447-3.597l.002-.002zM4.5 18a2.5 2.5 0 1 1 5 0a2.5 2.5 0 0 1-5 0m10 0a2.5 2.5 0 1 1 5 0a2.5 2.5 0 0 1-5 0m-.562-8.684l3.943-6.162a.75.75 0 1 0-1.263-.808L13.02 7.968z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
14
packages/ui/src/assets/svg/DownloadSvg.tsx
Normal file
14
packages/ui/src/assets/svg/DownloadSvg.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export const DownloadSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M18 15v3H6v-3H4v3c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-3zm-1-4l-1.41-1.41L13 12.17V4h-2v8.17L8.41 9.59L7 11l5 5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
20
packages/ui/src/assets/svg/FileSvg.tsx
Normal file
20
packages/ui/src/assets/svg/FileSvg.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export const FileSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path d="M14 3v4a1 1 0 0 0 1 1h4" />
|
||||||
|
<path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
14
packages/ui/src/assets/svg/KeySvg.tsx
Normal file
14
packages/ui/src/assets/svg/KeySvg.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export const KeySvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M7 14q-.825 0-1.412-.587T5 12t.588-1.412T7 10t1.413.588T9 12t-.587 1.413T7 14m0 4q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q1.675 0 3.038.825T12.2 9h8.375q.2 0 .388.075t.337.225l2 2q.15.15.212.325t.063.375t-.063.375t-.212.325l-3.175 3.175q-.125.125-.3.2t-.35.1t-.35-.025t-.325-.175L17.5 15l-1.425 1.075q-.125.1-.275.15t-.3.05t-.313-.05t-.287-.15L13.375 15H12.2q-.8 1.35-2.163 2.175T7 18m0-2q1.4 0 2.463-.85T10.875 13H14l1.45 1.025v.013v-.013L17.5 12.5l1.775 1.375L21.15 12h-.012h.012l-1-1v-.012V11h-9.275q-.35-1.3-1.412-2.15T7 8Q5.35 8 4.175 9.175T3 12t1.175 2.825T7 16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
18
packages/ui/src/assets/svg/MoonSvg.tsx
Normal file
18
packages/ui/src/assets/svg/MoonSvg.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export const MoonSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M12 3h.393a7.5 7.5 0 0 0 7.92 12.446A9 9 0 1 1 12 2.992z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
14
packages/ui/src/assets/svg/PasteSvg.tsx
Normal file
14
packages/ui/src/assets/svg/PasteSvg.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export const PasteSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M12.753 2c1.158 0 2.111.875 2.234 2h1.763a2.25 2.25 0 0 1 2.245 2.096L19 6.25a.75.75 0 0 1-.647.742L18.249 7a.75.75 0 0 1-.742-.647L17.5 6.25a.75.75 0 0 0-.648-.743L16.75 5.5h-2.132a2.24 2.24 0 0 1-1.865.993H9.247a2.24 2.24 0 0 1-1.865-.992L5.25 5.5a.75.75 0 0 0-.743.648L4.5 6.25v13.505c0 .38.282.693.648.743l.102.007h3a.75.75 0 0 1 .743.647l.007.102a.75.75 0 0 1-.75.75h-3a2.25 2.25 0 0 1-2.245-2.095L3 19.755V6.25a2.25 2.25 0 0 1 2.096-2.245L5.25 4h1.763a2.247 2.247 0 0 1 2.234-2zm5.997 6a2.25 2.25 0 0 1 2.245 2.096l.005.154v9.5a2.25 2.25 0 0 1-2.096 2.245L18.75 22h-6.5a2.25 2.25 0 0 1-2.245-2.096L10 19.75v-9.5a2.25 2.25 0 0 1 2.096-2.245L12.25 8zm0 1.5h-6.5a.75.75 0 0 0-.743.648l-.007.102v9.5c0 .38.282.694.648.743l.102.007h6.5a.75.75 0 0 0 .743-.648l.007-.102v-9.5a.75.75 0 0 0-.648-.743zm-5.997-6H9.247a.747.747 0 0 0 0 1.493h3.506a.747.747 0 1 0 0-1.493"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
16
packages/ui/src/assets/svg/RadioIndicatorOutlineSvg.tsx
Normal file
16
packages/ui/src/assets/svg/RadioIndicatorOutlineSvg.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export const RadioIndicatorOutlineSvg = (
|
||||||
|
props: React.SVGProps<SVGSVGElement>,
|
||||||
|
) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2m0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8s8 3.58 8 8s-3.58 8-8 8"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
15
packages/ui/src/assets/svg/RadioIndicatorSvg.tsx
Normal file
15
packages/ui/src/assets/svg/RadioIndicatorSvg.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export const RadioIndicatorSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2m0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8s8 3.58 8 8s-3.58 8-8 8"
|
||||||
|
/>
|
||||||
|
<circle cx="12" cy="12" r="5" fill="currentColor" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
14
packages/ui/src/assets/svg/SearchSvg.tsx
Normal file
14
packages/ui/src/assets/svg/SearchSvg.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export const SearchSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l5.6 5.6q.275.275.275.7t-.275.7t-.7.275t-.7-.275l-5.6-5.6q-.75.6-1.725.95T9.5 16m0-2q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
15
packages/ui/src/assets/svg/SettingSvg.tsx
Normal file
15
packages/ui/src/assets/svg/SettingSvg.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export const SettingSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M12.563 3.2h-1.126l-.645 2.578l-.647.2a6.3 6.3 0 0 0-1.091.452l-.599.317l-2.28-1.368l-.796.797l1.368 2.28l-.317.598a6.3 6.3 0 0 0-.453 1.091l-.199.647l-2.578.645v1.126l2.578.645l.2.647q.173.568.452 1.091l.317.599l-1.368 2.28l.797.796l2.28-1.368l.598.317q.523.278 1.091.453l.647.199l.645 2.578h1.126l.645-2.578l.647-.2a6.3 6.3 0 0 0 1.091-.452l.599-.317l2.28 1.368l.796-.797l-1.368-2.28l.317-.598q.278-.523.453-1.091l.199-.647l2.578-.645v-1.126l-2.578-.645l-.2-.647a6.3 6.3 0 0 0-.452-1.091l-.317-.599l1.368-2.28l-.797-.796l-2.28 1.368l-.598-.317a6.3 6.3 0 0 0-1.091-.453l-.647-.199zm2.945 2.17l1.833-1.1a1 1 0 0 1 1.221.15l1.018 1.018a1 1 0 0 1 .15 1.221l-1.1 1.833q.33.62.54 1.3l2.073.519a1 1 0 0 1 .757.97v1.438a1 1 0 0 1-.757.97l-2.073.519q-.21.68-.54 1.3l1.1 1.833a1 1 0 0 1-.15 1.221l-1.018 1.018a1 1 0 0 1-1.221.15l-1.833-1.1q-.62.33-1.3.54l-.519 2.073a1 1 0 0 1-.97.757h-1.438a1 1 0 0 1-.97-.757l-.519-2.073a7.5 7.5 0 0 1-1.3-.54l-1.833 1.1a1 1 0 0 1-1.221-.15L4.42 18.562a1 1 0 0 1-.15-1.221l1.1-1.833a7.5 7.5 0 0 1-.54-1.3l-2.073-.519A1 1 0 0 1 2 12.72v-1.438a1 1 0 0 1 .757-.97l2.073-.519q.21-.68.54-1.3L4.27 6.66a1 1 0 0 1 .15-1.221L5.438 4.42a1 1 0 0 1 1.221-.15l1.833 1.1q.62-.33 1.3-.54l.519-2.073A1 1 0 0 1 11.28 2h1.438a1 1 0 0 1 .97.757l.519 2.073q.68.21 1.3.54zM12 14.8a2.8 2.8 0 1 0 0-5.6a2.8 2.8 0 0 0 0 5.6m0 1.2a4 4 0 1 1 0-8a4 4 0 0 1 0 8"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
29
packages/ui/src/assets/svg/SpinnerSvg.tsx
Normal file
29
packages/ui/src/assets/svg/SpinnerSvg.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
export const SpinnerSvg = (props: React.SVGProps<SVGSVGElement>) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||||
|
opacity="0.25"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||||
|
>
|
||||||
|
<animateTransform
|
||||||
|
attributeName="transform"
|
||||||
|
dur="0.75s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
type="rotate"
|
||||||
|
values="0 12 12;360 12 12"
|
||||||
|
/>
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
18
packages/ui/src/assets/svg/SunSvg.tsx
Normal file
18
packages/ui/src/assets/svg/SunSvg.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export const SunSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M8 12a4 4 0 1 0 8 0a4 4 0 1 0-8 0m-5 0h1m8-9v1m8 8h1m-9 8v1M5.6 5.6l.7.7m12.1-.7l-.7.7m0 11.4l.7.7m-12.1-.7l-.7.7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
20
packages/ui/src/assets/svg/UserSvg.tsx
Normal file
20
packages/ui/src/assets/svg/UserSvg.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export const UserSvg = (props: React.SVGProps<SVGSVGElement>) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
d="M19.618 21.25c0-3.602-4.016-6.53-7.618-6.53s-7.618 2.928-7.618 6.53M12 11.456a4.353 4.353 0 1 0 0-8.706a4.353 4.353 0 0 0 0 8.706"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
19
packages/ui/src/assets/svg/VolumeHighSvg.tsx
Normal file
19
packages/ui/src/assets/svg/VolumeHighSvg.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export const VolumeHighSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
id="a"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5.91,9h-3.29c-.21,0-.38.17-.38.38v5.25c0,.21.17.38.38.38h3.26c.17,0,.34.06.48.17l4.29,3.51c.17.12.4.09.52-.08.05-.06.07-.14.07-.22V5.63c0-.21-.17-.37-.38-.37-.08,0-.16.03-.22.07l-4.29,3.51c-.13.11-.29.17-.46.17M15,15c.46-.91.75-1.91.75-3s-.28-2.08-.75-3M17.25,17.25c.91-1.59,1.5-3,1.5-5.25s-.56-3.64-1.5-5.25M19.5,19.5c1.41-2.16,2.25-4.29,2.25-7.5s-.84-5.3-2.25-7.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
45
packages/ui/src/assets/svg/VolumeLowSvg.tsx
Normal file
45
packages/ui/src/assets/svg/VolumeLowSvg.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
export const VolumeLowSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
id="a"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5.91,9h-3.29c-.21,0-.38.17-.38.38v5.25c0,.21.17.38.38.38h3.26c.17,0,.34.06.48.17l4.29,3.51c.17.12.4.09.52-.08.05-.06.07-.14.07-.22V5.63c0-.21-.17-.37-.38-.37-.08,0-.16.03-.22.07l-4.29,3.51c-.13.11-.29.17-.46.17"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15,15c.46-.91.75-1.91.75-3s-.28-2.08-.75-3"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M17.25,17.25c.91-1.59,1.5-3,1.5-5.25s-.56-3.64-1.5-5.25"
|
||||||
|
fill="none"
|
||||||
|
opacity=".2"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M19.5,19.5c1.41-2.16,2.25-4.29,2.25-7.5s-.84-5.3-2.25-7.5"
|
||||||
|
fill="none"
|
||||||
|
opacity=".2"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
44
packages/ui/src/assets/svg/VolumeMediumSvg.tsx
Normal file
44
packages/ui/src/assets/svg/VolumeMediumSvg.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
export const VolumeMediumSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
id="a"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5.91,9h-3.29c-.21,0-.38.17-.38.38v5.25c0,.21.17.38.38.38h3.26c.17,0,.34.06.48.17l4.29,3.51c.17.12.4.09.52-.08.05-.06.07-.14.07-.22V5.63c0-.21-.17-.37-.38-.37-.08,0-.16.03-.22.07l-4.29,3.51c-.13.11-.29.17-.46.17"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15,15c.46-.91.75-1.91.75-3s-.28-2.08-.75-3"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M17.25,17.25c.91-1.59,1.5-3,1.5-5.25s-.56-3.64-1.5-5.25"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M19.5,19.5c1.41-2.16,2.25-4.29,2.25-7.5s-.84-5.3-2.25-7.5"
|
||||||
|
fill="none"
|
||||||
|
opacity=".2"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
41
packages/ui/src/assets/svg/VolumeMuteSvg.tsx
Normal file
41
packages/ui/src/assets/svg/VolumeMuteSvg.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
export const VolumeMuteSvg = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
id="a"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5.91,9h-3.29c-.21,0-.38.17-.38.38v5.25c0,.21.17.38.38.38h3.26c.17,0,.34.06.48.17l4.29,3.51c.17.12.4.09.52-.08.05-.06.07-.14.07-.22V5.63c0-.21-.17-.37-.38-.37-.08,0-.16.03-.22.07l-4.29,3.51c-.13.11-.29.17-.46.17"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="14.8"
|
||||||
|
y1="8.57"
|
||||||
|
x2="21.2"
|
||||||
|
y2="15.46"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="14.79"
|
||||||
|
y1="15.45"
|
||||||
|
x2="21.19"
|
||||||
|
y2="8.55"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
9
packages/ui/src/common/CommonProps.ts
Normal file
9
packages/ui/src/common/CommonProps.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { CSSProperties } from "react";
|
||||||
|
|
||||||
|
export type CommonProps = {
|
||||||
|
className?: string;
|
||||||
|
style?: CSSProperties;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
key?: string | number;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
14
packages/ui/src/index.css
Normal file
14
packages/ui/src/index.css
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
@import './styles/theme/theme-base.css';
|
||||||
|
@import './styles/theme/theme-variant.css';
|
||||||
|
@import './styles/utility/brand.css';
|
||||||
|
@import './styles/utility/gap.css';
|
||||||
|
@import './styles/utility/height-inline.css';
|
||||||
|
@import './styles/utility/height.css';
|
||||||
|
@import './styles/utility/loading.css';
|
||||||
|
@import './styles/utility/margin-right.css';
|
||||||
|
@import './styles/utility/padding.css';
|
||||||
|
@import './styles/utility/skin.css';
|
||||||
|
@import './styles/utility/variant.css';
|
||||||
|
@import './styles/utility/width-inline.css';
|
||||||
|
@import './styles/utility/width.css';
|
||||||
52
packages/ui/src/index.ts
Normal file
52
packages/ui/src/index.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
export * from './assets/svg/CheckIndicatorOutlineSvg.tsx';
|
||||||
|
export * from './assets/svg/CheckIndicatorSvg.tsx';
|
||||||
|
export * from './assets/svg/ChevronRightSvg.tsx';
|
||||||
|
export * from './assets/svg/CutSvg.tsx';
|
||||||
|
export * from './assets/svg/DownloadSvg.tsx';
|
||||||
|
export * from './assets/svg/FileSvg.tsx';
|
||||||
|
export * from './assets/svg/KeySvg.tsx';
|
||||||
|
export * from './assets/svg/MoonSvg.tsx';
|
||||||
|
export * from './assets/svg/PasteSvg.tsx';
|
||||||
|
export * from './assets/svg/RadioIndicatorOutlineSvg.tsx';
|
||||||
|
export * from './assets/svg/RadioIndicatorSvg.tsx';
|
||||||
|
export * from './assets/svg/SearchSvg.tsx';
|
||||||
|
export * from './assets/svg/SettingSvg.tsx';
|
||||||
|
export * from './assets/svg/SpinnerSvg.tsx';
|
||||||
|
export * from './assets/svg/SunSvg.tsx';
|
||||||
|
export * from './assets/svg/UserSvg.tsx';
|
||||||
|
export * from './assets/svg/VolumeHighSvg.tsx';
|
||||||
|
export * from './assets/svg/VolumeLowSvg.tsx';
|
||||||
|
export * from './assets/svg/VolumeMediumSvg.tsx';
|
||||||
|
export * from './assets/svg/VolumeMuteSvg.tsx';
|
||||||
|
export * from './common/CommonProps.ts';
|
||||||
|
export * from './lv1-fundamental/Box/Box.tsx';
|
||||||
|
export * from './lv1-fundamental/Slot/Slot.tsx';
|
||||||
|
export * from './lv1-fundamental/ThemeProvider/ThemeContext.ts';
|
||||||
|
export * from './lv1-fundamental/ThemeProvider/ThemeProvider.tsx';
|
||||||
|
export * from './lv1-fundamental/ThemeProvider/useThemeContext.ts';
|
||||||
|
export * from './lv2-sized/Root/RootInline.recipe';
|
||||||
|
export * from './lv2-sized/Root/RootInline.ts';
|
||||||
|
export * from './lv2-sized/ItemRoot/ItemRoot.recipe.ts';
|
||||||
|
export * from './lv2-sized/ItemRoot/ItemRoot.tsx';
|
||||||
|
export * from './lv2-sized/Section/Section.recipe.ts';
|
||||||
|
export * from './lv2-sized/Section/Section.tsx';
|
||||||
|
export * from './lv3-partial/Icon/Icon.recipe.ts';
|
||||||
|
export * from './lv3-partial/Icon/Icon.tsx';
|
||||||
|
export * from './lv3-partial/Indicator/Indicator.recipe.ts';
|
||||||
|
export * from './lv3-partial/Indicator/Indicator.tsx';
|
||||||
|
export * from './lv3-partial/Label/Label.style.ts';
|
||||||
|
export * from './lv3-partial/Label/Label.tsx';
|
||||||
|
export * from './lv3-partial/Tooltip/Tooltip.tsx';
|
||||||
|
export * from './lv4-normal/Button/Button.recipe.ts';
|
||||||
|
export * from './lv4-normal/Button/Button.tsx';
|
||||||
|
export * from './lv4-normal/Checkbox/Checkbox.recipe.ts';
|
||||||
|
export * from './lv4-normal/Checkbox/Checkbox.tsx';
|
||||||
|
export * from './lv4-normal/Radio/Radio.recipe.ts';
|
||||||
|
export * from './lv4-normal/Radio/Radio.tsx';
|
||||||
|
export * from './lv4-normal/Radio/RadioGroup.recipe.ts';
|
||||||
|
export * from './lv4-normal/Radio/RadioGroup.tsx';
|
||||||
|
export * from './lv4-normal/Radio/RadioGroupContext.ts';
|
||||||
5
packages/ui/src/lv1-fundamental/Box/Box.recipe.ts
Normal file
5
packages/ui/src/lv1-fundamental/Box/Box.recipe.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
export const boxRoot = tv({
|
||||||
|
base: "flex justify-center items-center",
|
||||||
|
});
|
||||||
47
packages/ui/src/lv1-fundamental/Box/Box.tsx
Normal file
47
packages/ui/src/lv1-fundamental/Box/Box.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useThemeContext } from "../ThemeProvider/useThemeContext";
|
||||||
|
import { cn } from "tailwind-variants";
|
||||||
|
import type { CommonProps } from "@/common/CommonProps";
|
||||||
|
import { boxRoot } from "./Box.recipe";
|
||||||
|
|
||||||
|
// 别名<约束>=值
|
||||||
|
// 千万不要 C = As extend React.ElementType,这样子连等号,会切断推导
|
||||||
|
type AsProp<C extends React.ElementType> = { as?: C };
|
||||||
|
|
||||||
|
type PropsToOmit<C extends React.ElementType, P> = keyof (AsProp<C> & P);
|
||||||
|
|
||||||
|
export type PolymorphicProps<C extends React.ElementType, P = {}> = (P &
|
||||||
|
AsProp<C>) &
|
||||||
|
Omit<React.ComponentPropsWithRef<C>, PropsToOmit<C, P>>;
|
||||||
|
|
||||||
|
interface BoxProps<C extends React.ElementType> extends CommonProps {
|
||||||
|
as?: C;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Box = <C extends React.ElementType = "div">(
|
||||||
|
props: PolymorphicProps<C, BoxProps<C>>,
|
||||||
|
ref?: React.ComponentPropsWithRef<C>["ref"],
|
||||||
|
) => {
|
||||||
|
const { as: Component = "div", children, className, ...rest } = props;
|
||||||
|
|
||||||
|
const { themeClass } = useThemeContext();
|
||||||
|
if (!themeClass) {
|
||||||
|
throw new Error("Box must be used within a ThemeProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
const boxRootClass = cn(themeClass, boxRoot(), className);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Component ref={ref} className={boxRootClass} {...(rest as any)}>
|
||||||
|
{children}
|
||||||
|
</Component>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BoxComponent = <C extends React.ElementType = "div">(
|
||||||
|
props: PolymorphicProps<C, BoxProps<C>> & {
|
||||||
|
ref?: React.ComponentPropsWithRef<C>["ref"];
|
||||||
|
},
|
||||||
|
) => React.ReactElement | null;
|
||||||
|
|
||||||
|
export default Box as BoxComponent;
|
||||||
50
packages/ui/src/lv1-fundamental/Slot/Slot.tsx
Normal file
50
packages/ui/src/lv1-fundamental/Slot/Slot.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "tailwind-variants";
|
||||||
|
|
||||||
|
export interface SlotProps extends React.HTMLAttributes<HTMLElement> {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Slot = React.forwardRef<HTMLElement, SlotProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
children,
|
||||||
|
onClick: externalOnClick,
|
||||||
|
style: styleProp,
|
||||||
|
className: classNameProp,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
if (!React.isValidElement(children)) return null;
|
||||||
|
|
||||||
|
const child = children as React.ReactElement<Record<string, unknown>>;
|
||||||
|
|
||||||
|
const mergedClassName = cn(child.props.className as string, classNameProp);
|
||||||
|
|
||||||
|
const mergedStyle = {
|
||||||
|
...(child.props.style ?? {}),
|
||||||
|
...(styleProp ?? {}),
|
||||||
|
} as React.CSSProperties;
|
||||||
|
|
||||||
|
const childOnClick = child.props.onClick as
|
||||||
|
| ((e: React.MouseEvent<HTMLElement>) => void)
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
const mergedOnClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
|
childOnClick?.(e);
|
||||||
|
externalOnClick?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
return React.cloneElement(child, {
|
||||||
|
...child.props,
|
||||||
|
...props,
|
||||||
|
ref,
|
||||||
|
style: mergedStyle,
|
||||||
|
className: mergedClassName,
|
||||||
|
onClick: mergedOnClick,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Slot.displayName = "Slot";
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
export type Theme = "light" | "dark";
|
||||||
|
|
||||||
|
export const ThemeContext = createContext<{
|
||||||
|
theme?: Theme;
|
||||||
|
toggleTheme?: () => void;
|
||||||
|
themeClass?: string;
|
||||||
|
} | null>(null);
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { useState, type ReactNode } from "react";
|
||||||
|
import { type Theme, ThemeContext } from "./ThemeContext";
|
||||||
|
import { cn } from "tailwind-variants";
|
||||||
|
|
||||||
|
type ThemeProviderProps = {
|
||||||
|
children?: ReactNode;
|
||||||
|
defaultTheme?: Theme;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ThemeProvider = ({
|
||||||
|
children,
|
||||||
|
defaultTheme,
|
||||||
|
}: ThemeProviderProps) => {
|
||||||
|
const [theme, setTheme] = useState<Theme>(defaultTheme || "light");
|
||||||
|
const toggleTheme = () => setTheme(theme === "light" ? "dark" : "light");
|
||||||
|
|
||||||
|
const frameworkClass = "dg";
|
||||||
|
const themeClass = cn(frameworkClass, theme);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeContext.Provider value={{ theme, toggleTheme, themeClass }}>
|
||||||
|
<div
|
||||||
|
className={"dg-theme-provider"}
|
||||||
|
style={
|
||||||
|
theme == "light" ? { background: "white" } : { background: "black" }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { ThemeContext } from "./ThemeContext";
|
||||||
|
|
||||||
|
export function useThemeContext() {
|
||||||
|
const context = useContext(ThemeContext);
|
||||||
|
if (!context) throw new Error("useTheme must be used within ThemeProvider");
|
||||||
|
return context;
|
||||||
|
}
|
||||||
119
packages/ui/src/lv2-sized/InlineRoot/IinlineRoot.recipe.ts
Normal file
119
packages/ui/src/lv2-sized/InlineRoot/IinlineRoot.recipe.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import { tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
export const inlineRootRecipe = tv({
|
||||||
|
base: "relative overflow-hidden flex flex-nowrap",
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
xs: "text-xs h-inline-xs",
|
||||||
|
sm: "text-sm h-inline-sm",
|
||||||
|
md: "text-md h-inline-md",
|
||||||
|
lg: "text-lg h-inline-lg",
|
||||||
|
xl: "text-xl h-inline-xl",
|
||||||
|
"2xl": "text-2xl h-inline-2xl",
|
||||||
|
},
|
||||||
|
shape: {
|
||||||
|
square: "rounded-none",
|
||||||
|
rounded: "",
|
||||||
|
circle: "rounded-full",
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
filled: "variant-filled",
|
||||||
|
outline: "variant-outline",
|
||||||
|
subtle: "variant-subtle",
|
||||||
|
},
|
||||||
|
brand: {
|
||||||
|
success: "brand-success",
|
||||||
|
danger: "brand-danger",
|
||||||
|
info: "brand-info",
|
||||||
|
warning: "brand-warning",
|
||||||
|
emphasis: "brand-emphasis",
|
||||||
|
},
|
||||||
|
iconOnly: {
|
||||||
|
true: "",
|
||||||
|
false: "",
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
true: "",
|
||||||
|
false: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compoundVariants: [
|
||||||
|
{
|
||||||
|
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-inline-xs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconOnly: true,
|
||||||
|
size: "sm",
|
||||||
|
class: "w-inline-sm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconOnly: true,
|
||||||
|
size: "md",
|
||||||
|
class: "w-inline-md",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconOnly: true,
|
||||||
|
size: "lg",
|
||||||
|
class: "w-inline-lg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconOnly: true,
|
||||||
|
size: "xl",
|
||||||
|
class: "w-inline-xl",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconOnly: true,
|
||||||
|
size: "2xl",
|
||||||
|
class: "w-inline-2xl",
|
||||||
|
},
|
||||||
|
// --------------------------------------------------
|
||||||
|
{
|
||||||
|
disabled: true,
|
||||||
|
variant: "filled",
|
||||||
|
class: "variant-filled-disabled",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
disabled: true,
|
||||||
|
variant: "outline",
|
||||||
|
class: "variant-outline-disabled",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
disabled: true,
|
||||||
|
variant: "subtle",
|
||||||
|
class: "variant-subtle-disabled",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
75
packages/ui/src/lv2-sized/InlineRoot/InlineRoot.tsx
Normal file
75
packages/ui/src/lv2-sized/InlineRoot/InlineRoot.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import type { CommonProps } from "@/common/CommonProps";
|
||||||
|
import { cn } from "tailwind-variants";
|
||||||
|
import Box, { type PolymorphicProps } from "@/lv1-fundamental/Box/Box";
|
||||||
|
import { inlineRootRecipe } from "./IinlineRoot.recipe";
|
||||||
|
|
||||||
|
interface ItemInlineRootProps<C extends React.ElementType> extends CommonProps {
|
||||||
|
as?: C;
|
||||||
|
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
|
||||||
|
shape?: "square" | "rounded" | "circle";
|
||||||
|
variant?: "filled" | "outline" | "subtle";
|
||||||
|
brand?: "success" | "danger" | "info" | "warning" | "emphasis";
|
||||||
|
iconOnly?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemInlineRoot = <C extends React.ElementType = "span">(
|
||||||
|
props: PolymorphicProps<C, ItemInlineRootProps<C>>,
|
||||||
|
ref?: React.ComponentPropsWithRef<C>["ref"],
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
as = "span",
|
||||||
|
size = "sm",
|
||||||
|
shape = "square",
|
||||||
|
brand,
|
||||||
|
iconOnly = false,
|
||||||
|
variant,
|
||||||
|
onClick,
|
||||||
|
disabled = false,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const handleClick = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||||
|
if (disabled) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onClick?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemInlineRootClass = cn(
|
||||||
|
inlineRootRecipe({
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
shape,
|
||||||
|
iconOnly,
|
||||||
|
disabled,
|
||||||
|
brand,
|
||||||
|
}),
|
||||||
|
className,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as={as}
|
||||||
|
className={itemInlineRootClass}
|
||||||
|
onClick={handleClick}
|
||||||
|
disabled={disabled}
|
||||||
|
ref={ref}
|
||||||
|
{...(rest as any)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ItemInlineRoot.displayName = "ItemInlineRoot";
|
||||||
|
|
||||||
|
export type ItemInlineRootComponent = <C extends React.ElementType = "span">(
|
||||||
|
props: PolymorphicProps<C, ItemInlineRootProps<C>> & {
|
||||||
|
ref?: React.ComponentPropsWithRef<C>["ref"];
|
||||||
|
},
|
||||||
|
) => React.ReactElement | null;
|
||||||
|
|
||||||
|
export default ItemInlineRoot as ItemInlineRootComponent;
|
||||||
123
packages/ui/src/lv2-sized/ItemRoot/ItemRoot.recipe.ts
Normal file
123
packages/ui/src/lv2-sized/ItemRoot/ItemRoot.recipe.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
export const itemRootRecipe = tv({
|
||||||
|
base: "relative select-none overflow-hidden flex flex-nowrap",
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
xs: "text-xs h-xs px-xs gap-xs",
|
||||||
|
sm: "text-sm h-sm px-sm gap-sm",
|
||||||
|
md: "text-md h-md px-md gap-md",
|
||||||
|
lg: "text-lg h-lg px-lg gap-lg",
|
||||||
|
xl: "text-xl h-xl px-xl gap-xl",
|
||||||
|
"2xl": "text-2xl h-2xl px-2xl gap-2xl",
|
||||||
|
},
|
||||||
|
shape: {
|
||||||
|
square: "rounded-none",
|
||||||
|
rounded: "",
|
||||||
|
circle: "rounded-full",
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
filled: "variant-filled",
|
||||||
|
outline: "variant-outline",
|
||||||
|
subtle: "variant-subtle",
|
||||||
|
},
|
||||||
|
brand: {
|
||||||
|
success: "brand-success",
|
||||||
|
danger: "brand-danger",
|
||||||
|
info: "brand-info",
|
||||||
|
warning: "brand-warning",
|
||||||
|
emphasis: "brand-emphasis",
|
||||||
|
},
|
||||||
|
iconOnly: {
|
||||||
|
true: "",
|
||||||
|
false: "",
|
||||||
|
},
|
||||||
|
hasShadow: {
|
||||||
|
true: "shadow-xl",
|
||||||
|
false: "",
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
true: "",
|
||||||
|
false: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compoundVariants: [
|
||||||
|
{
|
||||||
|
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-xs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconOnly: true,
|
||||||
|
size: "sm",
|
||||||
|
class: "w-sm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconOnly: true,
|
||||||
|
size: "md",
|
||||||
|
class: "w-md",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconOnly: true,
|
||||||
|
size: "lg",
|
||||||
|
class: "w-lg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconOnly: true,
|
||||||
|
size: "xl",
|
||||||
|
class: "w-xl",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconOnly: true,
|
||||||
|
size: "2xl",
|
||||||
|
class: "w-2xl",
|
||||||
|
},
|
||||||
|
// --------------------------------------------------
|
||||||
|
{
|
||||||
|
disabled: true,
|
||||||
|
variant: "filled",
|
||||||
|
class: "variant-filled-disabled",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
disabled: true,
|
||||||
|
variant: "outline",
|
||||||
|
class: "variant-outline-disabled",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
disabled: true,
|
||||||
|
variant: "subtle",
|
||||||
|
class: "variant-subtle-disabled",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
81
packages/ui/src/lv2-sized/ItemRoot/ItemRoot.tsx
Normal file
81
packages/ui/src/lv2-sized/ItemRoot/ItemRoot.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import type { CommonProps } from "@/common/CommonProps";
|
||||||
|
import { itemRootRecipe } from "./ItemRoot.recipe";
|
||||||
|
import { cn } from "tailwind-variants";
|
||||||
|
import Box, { type PolymorphicProps } from "@/lv1-fundamental/Box/Box";
|
||||||
|
|
||||||
|
interface ItemRootProps<C extends React.ElementType> extends CommonProps {
|
||||||
|
as?: C;
|
||||||
|
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
|
||||||
|
shape?: "square" | "rounded" | "circle";
|
||||||
|
variant?: "filled" | "outline" | "subtle";
|
||||||
|
brand?: "success" | "danger" | "info" | "warning" | "emphasis";
|
||||||
|
hasShadow?: boolean;
|
||||||
|
iconOnly?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemRoot = <C extends React.ElementType = "div">(
|
||||||
|
props: PolymorphicProps<C, ItemRootProps<C>>,
|
||||||
|
ref?: React.ComponentPropsWithRef<C>["ref"],
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
as = "div",
|
||||||
|
size = "sm",
|
||||||
|
shape = "rounded",
|
||||||
|
brand: propBrand,
|
||||||
|
iconOnly = false,
|
||||||
|
variant,
|
||||||
|
hasShadow = false,
|
||||||
|
onClick,
|
||||||
|
disabled = false,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const brand =
|
||||||
|
variant == "filled" && propBrand == undefined ? "info" : propBrand;
|
||||||
|
|
||||||
|
const handleClick = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||||
|
if (disabled) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onClick?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemRootClass = cn(
|
||||||
|
itemRootRecipe({
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
shape,
|
||||||
|
iconOnly,
|
||||||
|
disabled,
|
||||||
|
hasShadow,
|
||||||
|
brand,
|
||||||
|
}),
|
||||||
|
className,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as={as}
|
||||||
|
className={itemRootClass}
|
||||||
|
onClick={handleClick}
|
||||||
|
disabled={disabled}
|
||||||
|
ref={ref}
|
||||||
|
{...(rest as any)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ItemRoot.displayName = "ItemRoot";
|
||||||
|
|
||||||
|
export type ItemRootComponent = <C extends React.ElementType = "div">(
|
||||||
|
props: PolymorphicProps<C, ItemRootProps<C>> & {
|
||||||
|
ref?: React.ComponentPropsWithRef<C>["ref"];
|
||||||
|
},
|
||||||
|
) => React.ReactElement | null;
|
||||||
|
|
||||||
|
export default ItemRoot as ItemRootComponent;
|
||||||
12
packages/ui/src/lv2-sized/Section/Section.recipe.ts
Normal file
12
packages/ui/src/lv2-sized/Section/Section.recipe.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
export const sectionRoot = tv({
|
||||||
|
base: "",
|
||||||
|
variants: {
|
||||||
|
align: {
|
||||||
|
start: "justify-start",
|
||||||
|
center: "justify-center",
|
||||||
|
end: "justify-end",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
23
packages/ui/src/lv2-sized/Section/Section.tsx
Normal file
23
packages/ui/src/lv2-sized/Section/Section.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { CommonProps } from "@/common/CommonProps";
|
||||||
|
import { forwardRef } from "react";
|
||||||
|
import { sectionRoot } from "./Section.recipe";
|
||||||
|
import Box from "@/lv1-fundamental/Box/Box";
|
||||||
|
import { cn } from "tailwind-variants";
|
||||||
|
|
||||||
|
type SectionProps = CommonProps & {
|
||||||
|
align?: "start" | "center" | "end";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Section = forwardRef<HTMLDivElement, SectionProps>(
|
||||||
|
(props, ref) => {
|
||||||
|
const { className, align, children, ...rest } = props;
|
||||||
|
|
||||||
|
const sectionRootClass = cn(sectionRoot({ align }), className);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={sectionRootClass} ref={ref} {...rest}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
19
packages/ui/src/lv3-partial/Icon/Icon.recipe.ts
Normal file
19
packages/ui/src/lv3-partial/Icon/Icon.recipe.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
export const iconRoot = tv({
|
||||||
|
base: "dg-icon",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const iconSvg = tv({
|
||||||
|
base: "dg-icon-svg",
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
xs: "dg-icon-svg_size--xs",
|
||||||
|
sm: "dg-icon-svg_size--sm",
|
||||||
|
md: "dg-icon-svg_size--md",
|
||||||
|
lg: "dg-icon-svg_size--lg",
|
||||||
|
xl: "dg-icon-svg_size--xl",
|
||||||
|
"2xl": "dg-icon-svg_size--2xl",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
25
packages/ui/src/lv3-partial/Icon/Icon.tsx
Normal file
25
packages/ui/src/lv3-partial/Icon/Icon.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { CommonProps } from "@/common/CommonProps";
|
||||||
|
import { forwardRef } from "react";
|
||||||
|
import { iconRoot, iconSvg } from "./Icon.recipe.ts";
|
||||||
|
import { Slot } from "@/lv1-fundamental/Slot/Slot";
|
||||||
|
import { cn } from "tailwind-variants";
|
||||||
|
import InlineRoot from "@/lv2-sized/InlineRoot/InlineRoot.tsx";
|
||||||
|
|
||||||
|
type IconProps = CommonProps & {
|
||||||
|
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Icon = forwardRef<HTMLSpanElement, IconProps>((props, ref) => {
|
||||||
|
const { size, children, className, ...rest } = props;
|
||||||
|
|
||||||
|
const iconRootClass = cn(iconRoot(), className);
|
||||||
|
const iconSvgClass = iconSvg({ size });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InlineRoot size={size} className={iconRootClass} ref={ref} {...rest}>
|
||||||
|
<Slot className={iconSvgClass}>{children}</Slot>
|
||||||
|
</InlineRoot>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Icon.displayName = "Icon";
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
export const indicatorInput = tv({ base: "pointer-none" });
|
||||||
|
export const indicatorBoxSvg = tv({ base: "absolute z-10" });
|
||||||
|
export const indicatorCheckSvg = tv({ base: "absolute z-20" });
|
||||||
92
packages/ui/src/lv3-partial/Indicator/Indicator.tsx
Normal file
92
packages/ui/src/lv3-partial/Indicator/Indicator.tsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { forwardRef, type ReactNode } from "react";
|
||||||
|
import {
|
||||||
|
// indicatorBoxSvg,
|
||||||
|
// indicatorCheckSvg,
|
||||||
|
indicatorInput,
|
||||||
|
} from "./Indicator.recipe";
|
||||||
|
import type { CommonProps } from "@/common/CommonProps";
|
||||||
|
import InlineRoot from "@/lv2-sized/InlineRoot/InlineRoot";
|
||||||
|
// import { CheckIndicatorOutlineSvg } from "@/assets/svg/CheckIndicatorOutlineSvg";
|
||||||
|
// import { RadioIndicatorOutlineSvg } from "@/assets/svg/RadioIndicatorOutlineSvg";
|
||||||
|
// import { RadioIndicatorSvg } from "@/assets/svg/RadioIndicatorSvg";
|
||||||
|
// import { CheckIndicatorSvg } from "@/assets/svg/CheckIndicatorSvg";
|
||||||
|
|
||||||
|
type IndicatorProps = CommonProps & {
|
||||||
|
size?: "xs" | "sm";
|
||||||
|
type?: "checkbox" | "radio";
|
||||||
|
boxSvg?: ReactNode;
|
||||||
|
checkSvg?: ReactNode;
|
||||||
|
checked?: boolean;
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Indicator = forwardRef<HTMLInputElement, IndicatorProps>(
|
||||||
|
(props, ref) => {
|
||||||
|
const {
|
||||||
|
size = "sm",
|
||||||
|
type = "checkbox",
|
||||||
|
// boxSvg,
|
||||||
|
// checkSvg,
|
||||||
|
checked,
|
||||||
|
id,
|
||||||
|
disabled,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
// const currentBoxSvg = boxSvg ? (
|
||||||
|
// boxSvg
|
||||||
|
// ) : type == "checkbox" ? (
|
||||||
|
// <CheckIndicatorOutlineSvg />
|
||||||
|
// ) : (
|
||||||
|
// <RadioIndicatorOutlineSvg />
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const currentCheckSvg = checkSvg ? (
|
||||||
|
// checkSvg
|
||||||
|
// ) : type == "checkbox" ? (
|
||||||
|
// <CheckIndicatorSvg />
|
||||||
|
// ) : (
|
||||||
|
// <RadioIndicatorSvg />
|
||||||
|
// );
|
||||||
|
|
||||||
|
const indicatorInputClass = indicatorInput();
|
||||||
|
// const indicatorBoxSvgClass = indicatorBoxSvg();
|
||||||
|
// const indicatorCheckSvgClass = indicatorCheckSvg();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InlineRoot size={size} iconOnly={true}>
|
||||||
|
<InlineRoot
|
||||||
|
as="input"
|
||||||
|
id={id}
|
||||||
|
type={type}
|
||||||
|
size={size}
|
||||||
|
ref={ref}
|
||||||
|
iconOnly={true}
|
||||||
|
checked={checked} // 必须显式绑定,但还需要阻止默认行为
|
||||||
|
// 此处仅用于阻止默认行为,不要处理 click 逻辑
|
||||||
|
onClick={(e: React.MouseEvent<HTMLInputElement>) => {
|
||||||
|
e.preventDefault(); // 阻止默认行为,防止闪烁
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
// 此处仅用于阻止默认行为,不要处理 keydown 逻辑
|
||||||
|
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
e.preventDefault(); // 阻止默认行为,防止闪烁
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
disabled={disabled} // 交互类 disabled 需要手动传入,而不是依靠父元素灰色滤镜
|
||||||
|
className={indicatorInputClass}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* <Icon className={indicatorBoxSvgClass} size={size}>
|
||||||
|
{currentBoxSvg}
|
||||||
|
</Icon>
|
||||||
|
|
||||||
|
{checked && (
|
||||||
|
<Icon className={indicatorCheckSvgClass} size={size}>
|
||||||
|
{currentCheckSvg}
|
||||||
|
</Icon>
|
||||||
|
)} */}
|
||||||
|
</InlineRoot>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Indicator.displayName = "Indicator";
|
||||||
30
packages/ui/src/lv3-partial/Label/Label.scss
Normal file
30
packages/ui/src/lv3-partial/Label/Label.scss
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
@use "../../styles/index.scss" as *;
|
||||||
|
|
||||||
|
.dg-label {
|
||||||
|
@include add-specificity(3) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-label_align--start {
|
||||||
|
@include add-specificity(3) {
|
||||||
|
@include justify-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-label_align--center {
|
||||||
|
@include add-specificity(3) {
|
||||||
|
@include justify-center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-label_align--end {
|
||||||
|
@include add-specificity(3) {
|
||||||
|
@include justify-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-label_is-subtext--true {
|
||||||
|
@include add-specificity(3) {
|
||||||
|
@include color-subtext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-label_is-subtext--false {
|
||||||
|
@include add-specificity(3) {
|
||||||
|
}
|
||||||
|
}
|
||||||
16
packages/ui/src/lv3-partial/Label/Label.style.ts
Normal file
16
packages/ui/src/lv3-partial/Label/Label.style.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
export const labelRoot = tv({
|
||||||
|
base: "dg-label",
|
||||||
|
variants: {
|
||||||
|
align: {
|
||||||
|
start: "dg-label_align--start",
|
||||||
|
center: "dg-label_align--center",
|
||||||
|
end: "dg-label_align--end",
|
||||||
|
},
|
||||||
|
isSubText: {
|
||||||
|
true: "dg-label_is-subtext--true",
|
||||||
|
false: "dg-label_is-subtext--false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
39
packages/ui/src/lv3-partial/Label/Label.tsx
Normal file
39
packages/ui/src/lv3-partial/Label/Label.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import type { CommonProps } from "@/common/CommonProps";
|
||||||
|
import { forwardRef } from "react";
|
||||||
|
import { cn } from "tailwind-variants";
|
||||||
|
import { labelRoot } from "./Label.style";
|
||||||
|
import { RootInline } from "@/lv2-sized/Root/RootInline";
|
||||||
|
|
||||||
|
type LabelProps = CommonProps & {
|
||||||
|
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
|
||||||
|
align?: "start" | "center" | "end";
|
||||||
|
isSubText?: boolean;
|
||||||
|
for?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Label = forwardRef<HTMLLabelElement, LabelProps>((props, ref) => {
|
||||||
|
const {
|
||||||
|
size,
|
||||||
|
isSubText = false,
|
||||||
|
align,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const labelRootClass = cn(labelRoot({ align, isSubText }), className);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RootInline
|
||||||
|
as="label"
|
||||||
|
size={size}
|
||||||
|
className={labelRootClass}
|
||||||
|
ref={ref}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RootInline>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Label.displayName = "Label";
|
||||||
7
packages/ui/src/lv3-partial/Tooltip/Tooltip.scss
Normal file
7
packages/ui/src/lv3-partial/Tooltip/Tooltip.scss
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
@use "../../styles/index.scss" as *;
|
||||||
|
|
||||||
|
.dg-tooltip-dropdown {
|
||||||
|
@include add-specificity(3) {
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
70
packages/ui/src/lv3-partial/Tooltip/Tooltip.tsx
Normal file
70
packages/ui/src/lv3-partial/Tooltip/Tooltip.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { Slot } from "@/lv1-fundamental/Slot/Slot";
|
||||||
|
import Root from "@/lv2-sized/ItemRoot/ItemRoot";
|
||||||
|
import type { CommonProps } from "@/common/CommonProps";
|
||||||
|
import {
|
||||||
|
autoUpdate,
|
||||||
|
flip,
|
||||||
|
FloatingPortal,
|
||||||
|
offset,
|
||||||
|
shift,
|
||||||
|
useDismiss,
|
||||||
|
useFloating,
|
||||||
|
useFocus,
|
||||||
|
useHover,
|
||||||
|
useInteractions,
|
||||||
|
useRole,
|
||||||
|
type Placement,
|
||||||
|
} from "@floating-ui/react";
|
||||||
|
import { useState, type ReactNode } from "react";
|
||||||
|
|
||||||
|
type TooltipProps = CommonProps & {
|
||||||
|
title?: ReactNode;
|
||||||
|
placement?: Placement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Tooltip = (props: TooltipProps) => {
|
||||||
|
const { children, placement = "top", title } = props;
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const { refs, floatingStyles, context } = useFloating({
|
||||||
|
open: open,
|
||||||
|
onOpenChange: setOpen,
|
||||||
|
placement: placement,
|
||||||
|
middleware: [offset(4), flip(), shift()],
|
||||||
|
whileElementsMounted: autoUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hover = useHover(context, {
|
||||||
|
delay: { open: 0, close: 0 },
|
||||||
|
});
|
||||||
|
const focus = useFocus(context);
|
||||||
|
const dismiss = useDismiss(context);
|
||||||
|
const role = useRole(context, { role: "tooltip" });
|
||||||
|
const { getReferenceProps, getFloatingProps } = useInteractions([
|
||||||
|
hover,
|
||||||
|
focus,
|
||||||
|
dismiss,
|
||||||
|
role,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Slot ref={refs.setReference} {...getReferenceProps()}>
|
||||||
|
{children}
|
||||||
|
</Slot>
|
||||||
|
{open && (
|
||||||
|
<FloatingPortal>
|
||||||
|
<Root
|
||||||
|
size="fit"
|
||||||
|
ref={refs.setFloating}
|
||||||
|
style={floatingStyles}
|
||||||
|
hasShadow={true}
|
||||||
|
{...getFloatingProps()}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Root>
|
||||||
|
</FloatingPortal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
0
packages/ui/src/lv4-normal/Button/Button.recipe.ts
Normal file
0
packages/ui/src/lv4-normal/Button/Button.recipe.ts
Normal file
31
packages/ui/src/lv4-normal/Button/Button.scss
Normal file
31
packages/ui/src/lv4-normal/Button/Button.scss
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
@use "../../styles/index.scss" as *;
|
||||||
|
|
||||||
|
.dg-button {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
padding-block: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-button-content {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dg-button_isloading--true {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
@include loading-true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-button_isloading--false {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
@include loading-false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-button-loading-icon {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
@include absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-button-icon {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
}
|
||||||
|
}
|
||||||
93
packages/ui/src/lv4-normal/Button/Button.tsx
Normal file
93
packages/ui/src/lv4-normal/Button/Button.tsx
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import Box from "@/lv1-fundamental/Box/Box";
|
||||||
|
import Root from "@/lv2-sized/ItemRoot/ItemRoot";
|
||||||
|
import { Icon } from "@/lv3-partial/Icon/Icon";
|
||||||
|
import { Label } from "@/lv3-partial/Label/Label";
|
||||||
|
|
||||||
|
import type { CommonProps } from "@/common/CommonProps";
|
||||||
|
import { forwardRef } from "react";
|
||||||
|
import {
|
||||||
|
buttonContent,
|
||||||
|
buttonIcon,
|
||||||
|
buttonLoadingIcon,
|
||||||
|
buttonRoot,
|
||||||
|
} from "./Button.recipe";
|
||||||
|
import { cn } from "tailwind-variants";
|
||||||
|
import { SpinnerSvg } from "@/assets/svg/SpinnerSvg";
|
||||||
|
|
||||||
|
type ButtonProps = CommonProps & {
|
||||||
|
size?: "xs" | "sm" | "md" | "lg";
|
||||||
|
shape?: "circle" | "rounded" | "square";
|
||||||
|
variant?: "filled" | "outline" | "subtle";
|
||||||
|
isLoading?: boolean;
|
||||||
|
loadingIcon?: React.ReactNode;
|
||||||
|
iconSvg?: React.ReactNode;
|
||||||
|
iconOnly?: boolean;
|
||||||
|
hideIcon?: boolean; // not a state just a attribute
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
(props, ref) => {
|
||||||
|
const {
|
||||||
|
size = "md",
|
||||||
|
shape = "rounded",
|
||||||
|
variant = "filled",
|
||||||
|
iconOnly = false,
|
||||||
|
iconSvg,
|
||||||
|
hideIcon = false,
|
||||||
|
isLoading,
|
||||||
|
loadingIcon,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
disabled,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const buttonRootClass = cn(buttonRoot({ iconOnly }), className);
|
||||||
|
const buttonIconClass = buttonIcon();
|
||||||
|
const buttonContentClass =
|
||||||
|
(isLoading != undefined && buttonContent({ isLoading })) || "";
|
||||||
|
const buttonLoadingIconClass = buttonLoadingIcon({
|
||||||
|
isLoading,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Root
|
||||||
|
ref={ref}
|
||||||
|
as="button"
|
||||||
|
size={size}
|
||||||
|
variant={variant}
|
||||||
|
shape={shape}
|
||||||
|
iconOnly={iconOnly}
|
||||||
|
className={buttonRootClass}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<Box className={buttonContentClass}>
|
||||||
|
{iconOnly && (
|
||||||
|
// if iconOnly
|
||||||
|
<Icon size={size} className={buttonIconClass}>
|
||||||
|
{iconSvg && !hideIcon ? iconSvg : null}
|
||||||
|
</Icon>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!iconOnly &&
|
||||||
|
// if not iconOnly
|
||||||
|
iconSvg &&
|
||||||
|
!hideIcon ? (
|
||||||
|
<Icon size={size}>{iconSvg}</Icon>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{!iconOnly ? <Label size={size}>{children}</Label> : null}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{isLoading != undefined && (
|
||||||
|
<Icon size={size} className={buttonLoadingIconClass}>
|
||||||
|
<SpinnerSvg />
|
||||||
|
</Icon>
|
||||||
|
)}
|
||||||
|
</Root>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Button.displayName = "Button";
|
||||||
8
packages/ui/src/lv4-normal/Checkbox/Checkbox.recipe.ts
Normal file
8
packages/ui/src/lv4-normal/Checkbox/Checkbox.recipe.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
export const checkboxRoot = tv({
|
||||||
|
base: "dg-checkbox",
|
||||||
|
});
|
||||||
|
export const checkboxLabel = tv({
|
||||||
|
base: "dg-checkbox-label",
|
||||||
|
});
|
||||||
13
packages/ui/src/lv4-normal/Checkbox/Checkbox.scss
Normal file
13
packages/ui/src/lv4-normal/Checkbox/Checkbox.scss
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@use "../../styles/index.scss" as *;
|
||||||
|
|
||||||
|
.dg-checkbox {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
cursor: pointer;
|
||||||
|
padding-block: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-checkbox-label {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
103
packages/ui/src/lv4-normal/Checkbox/Checkbox.tsx
Normal file
103
packages/ui/src/lv4-normal/Checkbox/Checkbox.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { RootInline } from "@/lv2-sized/Root/RootInline";
|
||||||
|
import { Icon } from "@/lv3-partial/Icon/Icon";
|
||||||
|
import { Label } from "@/lv3-partial/Label/Label";
|
||||||
|
import type { CommonProps } from "@/common/CommonProps";
|
||||||
|
import { forwardRef, useState, type ReactNode } from "react";
|
||||||
|
import { cn } from "tailwind-variants";
|
||||||
|
import { checkboxLabel, checkboxRoot } from "./Checkbox.recipe";
|
||||||
|
import Root from "@/lv2-sized/ItemRoot/ItemRoot";
|
||||||
|
import { Indicator } from "@/lv3-partial/Indicator/Indicator";
|
||||||
|
|
||||||
|
type CheckboxProps = CommonProps & {
|
||||||
|
id?: string;
|
||||||
|
checked?: boolean;
|
||||||
|
defaultChecked?: boolean;
|
||||||
|
onChange?: (v: boolean) => void;
|
||||||
|
size?: "xs" | "sm";
|
||||||
|
isPlaceholder?: boolean;
|
||||||
|
indicatorBoxSvg?: ReactNode;
|
||||||
|
indicatorCheckSvg?: ReactNode;
|
||||||
|
iconSvg?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
||||||
|
(props, ref) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
checked: controllerChecked,
|
||||||
|
defaultChecked = false,
|
||||||
|
onChange,
|
||||||
|
size = "sm",
|
||||||
|
isPlaceholder = false,
|
||||||
|
indicatorBoxSvg,
|
||||||
|
indicatorCheckSvg,
|
||||||
|
iconSvg,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
disabled = false,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const isControlled = controllerChecked !== undefined;
|
||||||
|
const [innerChecked, setInnerChecked] = useState(defaultChecked ?? false);
|
||||||
|
const currentChecked = isControlled ? controllerChecked : innerChecked;
|
||||||
|
|
||||||
|
const handleClick = (_e: React.MouseEvent<HTMLInputElement>) => {
|
||||||
|
// 若受控,点击不变
|
||||||
|
if (isControlled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 若不受控,点击变更
|
||||||
|
const next = !currentChecked;
|
||||||
|
setInnerChecked(next);
|
||||||
|
onChange?.(next);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
// 若受控,点击不变
|
||||||
|
if (isControlled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 若不受控,点击变更
|
||||||
|
const next = !currentChecked;
|
||||||
|
if (e.key === " " || e.key === "Enter") {
|
||||||
|
setInnerChecked(next);
|
||||||
|
}
|
||||||
|
onChange?.(next);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkboxRootClass = cn(checkboxRoot(), className);
|
||||||
|
const checkboxLabelClass = checkboxLabel();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Root
|
||||||
|
size={size}
|
||||||
|
className={checkboxRootClass}
|
||||||
|
onClick={handleClick}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
disabled={disabled} // 不同于 input 的 disabled,此处仅提供灰色滤镜
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{isPlaceholder ? (
|
||||||
|
<RootInline size={size} iconOnly={true} />
|
||||||
|
) : (
|
||||||
|
<Indicator
|
||||||
|
size={size}
|
||||||
|
disabled={disabled}
|
||||||
|
type="checkbox"
|
||||||
|
checked={currentChecked}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{iconSvg ? <Icon size={size}>{iconSvg}</Icon> : null}
|
||||||
|
|
||||||
|
<Label size={size} for={id} className={checkboxLabelClass}>
|
||||||
|
{children}
|
||||||
|
</Label>
|
||||||
|
</Root>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Checkbox.displayName = "Checkbox";
|
||||||
9
packages/ui/src/lv4-normal/Radio/Radio.recipe.ts
Normal file
9
packages/ui/src/lv4-normal/Radio/Radio.recipe.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
export const radioRoot = tv({
|
||||||
|
base: "dg-radio",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const radioLabel = tv({
|
||||||
|
base: "dg-radio-label",
|
||||||
|
});
|
||||||
13
packages/ui/src/lv4-normal/Radio/Radio.scss
Normal file
13
packages/ui/src/lv4-normal/Radio/Radio.scss
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@use "../../styles/index.scss" as *;
|
||||||
|
|
||||||
|
.dg-radio {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
cursor: pointer;
|
||||||
|
padding-block: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-radio-label {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
114
packages/ui/src/lv4-normal/Radio/Radio.tsx
Normal file
114
packages/ui/src/lv4-normal/Radio/Radio.tsx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { RootInline } from "@/lv2-sized/Root/RootInline";
|
||||||
|
import { Icon } from "@/lv3-partial/Icon/Icon";
|
||||||
|
import { Label } from "@/lv3-partial/Label/Label";
|
||||||
|
import type { CommonProps } from "@/common/CommonProps";
|
||||||
|
import { forwardRef, useContext, useState, type ReactNode } from "react";
|
||||||
|
import { cn } from "tailwind-variants";
|
||||||
|
import { radioLabel, radioRoot } from "./Radio.recipe";
|
||||||
|
import { RadioGroupContext } from "./RadioGroupContext";
|
||||||
|
import { Indicator } from "@/lv3-partial/Indicator/Indicator";
|
||||||
|
import Root from "@/lv2-sized/ItemRoot/ItemRoot";
|
||||||
|
|
||||||
|
type RadioProps = CommonProps & {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
value: string;
|
||||||
|
checked?: boolean;
|
||||||
|
defaultChecked?: boolean;
|
||||||
|
onChange?: (name: string, value: boolean) => void;
|
||||||
|
size?: "xs" | "sm";
|
||||||
|
isPlaceholder?: boolean;
|
||||||
|
indicatorBoxSvg?: ReactNode;
|
||||||
|
indicatorCheckSvg?: ReactNode;
|
||||||
|
iconSvg?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Radio = forwardRef<HTMLInputElement, RadioProps>((props, ref) => {
|
||||||
|
const {
|
||||||
|
checked: controllerChecked,
|
||||||
|
defaultChecked = false,
|
||||||
|
onChange,
|
||||||
|
size = "sm",
|
||||||
|
isPlaceholder = false,
|
||||||
|
indicatorBoxSvg,
|
||||||
|
indicatorCheckSvg,
|
||||||
|
iconSvg,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
disabled = false,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const ctx = useContext(RadioGroupContext);
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error("Radio must be used within a RadioGroup");
|
||||||
|
}
|
||||||
|
|
||||||
|
const isControlled = controllerChecked !== undefined;
|
||||||
|
const [innerChecked, setInnerChecked] = useState(defaultChecked ?? false);
|
||||||
|
const currentChecked = isControlled ? controllerChecked : innerChecked;
|
||||||
|
|
||||||
|
const currentSize = ctx.size ?? size;
|
||||||
|
const currentName = ctx.name ?? name;
|
||||||
|
|
||||||
|
const handleClick = (_e: React.MouseEvent<HTMLInputElement>) => {
|
||||||
|
// 若受控,点击不变
|
||||||
|
if (isControlled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 若不受控,点击变更
|
||||||
|
const next = !currentChecked;
|
||||||
|
setInnerChecked(next);
|
||||||
|
onChange?.(currentName, next);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
// 若受控,点击不变
|
||||||
|
if (isControlled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 若不受控,点击变更
|
||||||
|
const next = !currentChecked;
|
||||||
|
if (e.key === " " || e.key === "Enter") {
|
||||||
|
setInnerChecked(next);
|
||||||
|
}
|
||||||
|
onChange?.(currentName, next);
|
||||||
|
};
|
||||||
|
|
||||||
|
const radioRootClass = cn(radioRoot(), className);
|
||||||
|
const radioLabelClass = radioLabel();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Root
|
||||||
|
size={currentSize}
|
||||||
|
className={radioRootClass}
|
||||||
|
onClick={handleClick}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
disabled={disabled} // 不同于 input 的 disabled,此处仅提供灰色滤镜
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{isPlaceholder ? (
|
||||||
|
<RootInline size={currentSize} iconOnly={true} />
|
||||||
|
) : (
|
||||||
|
<Indicator
|
||||||
|
size={currentSize}
|
||||||
|
disabled={disabled}
|
||||||
|
type="radio"
|
||||||
|
checked={currentChecked}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{iconSvg ? <Icon size={currentSize}>{iconSvg}</Icon> : null}
|
||||||
|
|
||||||
|
<Label size={currentSize} for={id} className={radioLabelClass}>
|
||||||
|
{children}
|
||||||
|
</Label>
|
||||||
|
</Root>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Radio.displayName = "Radio";
|
||||||
15
packages/ui/src/lv4-normal/Radio/RadioGroup.recipe.ts
Normal file
15
packages/ui/src/lv4-normal/Radio/RadioGroup.recipe.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
export const radioGroupRoot = tv({
|
||||||
|
base: "dg-radio-group",
|
||||||
|
variants: {
|
||||||
|
direction: {
|
||||||
|
horizontal: "dg-radio-group_direction--horizontal",
|
||||||
|
vertical: "dg-radio-group_direction--vertical",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const radioGroupList = tv({
|
||||||
|
base: "dg-radio-group-list",
|
||||||
|
});
|
||||||
24
packages/ui/src/lv4-normal/Radio/RadioGroup.scss
Normal file
24
packages/ui/src/lv4-normal/Radio/RadioGroup.scss
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
@use "../../styles/index.scss" as *;
|
||||||
|
|
||||||
|
.dg-radio-group {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
@include flex-col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-radio-group_direction--horizontal {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
@include flex-row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-radio-group_direction--vertical {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
@include flex-col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dg-radio-group-list {
|
||||||
|
@include add-specificity(4) {
|
||||||
|
@include flex-col;
|
||||||
|
@include justify-start;
|
||||||
|
@include items-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
59
packages/ui/src/lv4-normal/Radio/RadioGroup.tsx
Normal file
59
packages/ui/src/lv4-normal/Radio/RadioGroup.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import type { CommonProps } from "@/common/CommonProps";
|
||||||
|
import { RadioGroupContext } from "./RadioGroupContext";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Label } from "@/lv3-partial/Label/Label";
|
||||||
|
import Root from "@/lv2-sized/ItemRoot/ItemRoot";
|
||||||
|
import { radioGroupList, radioGroupRoot } from "./RadioGroup.recipe";
|
||||||
|
import Box from "@/lv1-fundamental/Box/Box";
|
||||||
|
|
||||||
|
type RadioGroupProps = CommonProps & {
|
||||||
|
name: string;
|
||||||
|
label?: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
size?: "xs" | "sm";
|
||||||
|
direction?: "vertical" | "horizontal";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RadioGroup = (props: RadioGroupProps) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
defaultValue,
|
||||||
|
onChange,
|
||||||
|
label,
|
||||||
|
size = "sm",
|
||||||
|
direction = "horizontal",
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [value, setValue] = useState(defaultValue ?? "");
|
||||||
|
|
||||||
|
const handleChange = (v: string) => {
|
||||||
|
setValue(v);
|
||||||
|
onChange?.(v);
|
||||||
|
};
|
||||||
|
|
||||||
|
const radioGroupRootClass = radioGroupRoot({ direction });
|
||||||
|
const radioGroupListClass = radioGroupList();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadioGroupContext.Provider
|
||||||
|
value={{ value: value, onChange: handleChange, name, size }}
|
||||||
|
>
|
||||||
|
<Root className={radioGroupRootClass} {...rest}>
|
||||||
|
<Box className={radioGroupListClass}>
|
||||||
|
{label && (
|
||||||
|
<Root size={size}>
|
||||||
|
<Label size={size}>
|
||||||
|
{label}
|
||||||
|
</Label>
|
||||||
|
</Root>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</Root>
|
||||||
|
</RadioGroupContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
12
packages/ui/src/lv4-normal/Radio/RadioGroupContext.ts
Normal file
12
packages/ui/src/lv4-normal/Radio/RadioGroupContext.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
export type RadioGroupContextType = {
|
||||||
|
name: string;
|
||||||
|
onChange: (v: string) => void;
|
||||||
|
value: string;
|
||||||
|
size: "xs" | "sm";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RadioGroupContext = createContext<RadioGroupContextType | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
36
packages/ui/src/styles/theme/theme-base.css
Normal file
36
packages/ui/src/styles/theme/theme-base.css
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
@theme {
|
||||||
|
--danger-fg: var(--base-fg);
|
||||||
|
--danger-bg-low-hover: var(--color-red-100);
|
||||||
|
--danger-bg-low-active: var(--color-red-200);
|
||||||
|
--danger-bg: var(--color-red-600);
|
||||||
|
--danger-bg-high-hover: var(--color-red-700);
|
||||||
|
--danger-bg-high-active: var(--color-red-800);
|
||||||
|
--danger-border-color: var(--color-transparent);
|
||||||
|
--success-fg: var(--base-fg);
|
||||||
|
--success-bg-low-hover: var(--color-green-100);
|
||||||
|
--success-bg-low-active: var(--color-green-200);
|
||||||
|
--success-bg: var(--color-green-600);
|
||||||
|
--success-bg-high-hover: var(--color-green-700);
|
||||||
|
--success-bg-high-active: var(--color-green-800);
|
||||||
|
--info-fg: var(--base-fg);
|
||||||
|
--info-bg-low-hover: var(--color-sky-100);
|
||||||
|
--info-bg-low-active: var(--color-sky-200);
|
||||||
|
--info-bg: var(--color-sky-600);
|
||||||
|
--info-bg-high-hover: var(--color-sky-700);
|
||||||
|
--info-bg-high-active: var(--color-sky-800);
|
||||||
|
--info-border-color: var(--color-transparent);
|
||||||
|
--warning-fg: var(--base-fg);
|
||||||
|
--warning-bg-low-hover: var(--color-amber-100);
|
||||||
|
--warning-bg-low-active: var(--color-amber-200);
|
||||||
|
--warning-bg: var(--color-amber-600);
|
||||||
|
--warning-bg-high-hover: var(--color-amber-700);
|
||||||
|
--warning-bg-high-active: var(--color-amber-800);
|
||||||
|
--warning-border-color: var(--color-transparent);
|
||||||
|
--emphasis-fg: var(--base-fg);
|
||||||
|
--emphasis-bg-low-hover: var(--color-gray-100);
|
||||||
|
--emphasis-bg-low-active: var(--color-gray-200);
|
||||||
|
--emphasis-bg: var(--color-gray-600);
|
||||||
|
--emphasis-bgr-high-hover: var(--color-gray-700);
|
||||||
|
--emphasis-bg-high-active: var(--color-gray-800);
|
||||||
|
--emphasis-border-color: var(--color-transparent);
|
||||||
|
}
|
||||||
25
packages/ui/src/styles/theme/theme-variant.css
Normal file
25
packages/ui/src/styles/theme/theme-variant.css
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
@theme {
|
||||||
|
--filled-fg: var(--color-white);
|
||||||
|
--filled-fg-hover: var(--brand-fg);
|
||||||
|
--filled-fg-active: var(--brand-fg);
|
||||||
|
--filled-bg: var(--brand-bg);
|
||||||
|
--filled-bg-hover: var(--brand-bg-high-hover);
|
||||||
|
--filled-bg-active: var(--brand-bg-high-active);
|
||||||
|
--filled-border-color: var(--brand-border-color);
|
||||||
|
|
||||||
|
--outline-fg: var(--brand-bg);
|
||||||
|
--outline-fg-hover: var(--brand-bg);
|
||||||
|
--outline-fg-active: var(--brand-bg);
|
||||||
|
--outline-bg: var(--brand-border-color);
|
||||||
|
--outline-bg-hover: var(--brand-bg-low-hover);
|
||||||
|
--outline-bg-active: var(--brand-bg-low-active);
|
||||||
|
--outline-border-color: var(--brand-bg);
|
||||||
|
|
||||||
|
--subtle-fg: var(--brand-bg);
|
||||||
|
--subtle-fg-hover: var(--brand-bg);
|
||||||
|
--subtle-fg-active: var(--brand-bg);
|
||||||
|
--subtle-bg: var(--brand-border-color);
|
||||||
|
--subtle-bg-hover: var(--brand-bg-low-hover);
|
||||||
|
--subtle-bg-active: var(--brand-bg-low-active);
|
||||||
|
--subtle-border-color: var(--brand-border-color);
|
||||||
|
}
|
||||||
45
packages/ui/src/styles/utility/brand.css
Normal file
45
packages/ui/src/styles/utility/brand.css
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
@utility brand-info {
|
||||||
|
--brand-fg: var(--info-fg);
|
||||||
|
--brand-bg-low-hover: var(--info-bg-low-hover);
|
||||||
|
--brand-bg-low-active: var(--info-bg-low-active);
|
||||||
|
--brand-bg: var(--info-bg);
|
||||||
|
--brand-bg-high-hover: var(--info-bg-high-hover);
|
||||||
|
--brand-bg-high-active: var(--info-bg-high-active);
|
||||||
|
--brand-border-color: var(--info-border-color);
|
||||||
|
}
|
||||||
|
@utility brand-danger {
|
||||||
|
--brand-fg: var(--danger-fg);
|
||||||
|
--brand-bg-low-hover: var(--danger-bg-low-hover);
|
||||||
|
--brand-bg-low-active: var(--danger-bg-low-active);
|
||||||
|
--brand-bg: var(--danger-bg);
|
||||||
|
--brand-bg-high-hover: var(--danger-bg-high-hover);
|
||||||
|
--brand-bg-high-active: var(--danger-bg-high-active);
|
||||||
|
--brand-border-color: var(--danger-border-color);
|
||||||
|
}
|
||||||
|
@utility brand-success {
|
||||||
|
--brand-fg: var(--success-fg);
|
||||||
|
--brand-bg-low-hover: var(--success-bg-low-hover);
|
||||||
|
--brand-bg-low-active: var(--success-bg-low-active);
|
||||||
|
--brand-bg: var(--success-bg);
|
||||||
|
--brand-bg-high-hover: var(--success-bg-high-hover);
|
||||||
|
--brand-bg-high-active: var(--success-bg-high-active);
|
||||||
|
--brand-border-color: var(--success-border-color);
|
||||||
|
}
|
||||||
|
@utility brand-warning {
|
||||||
|
--brand-fg: var(--warning-fg);
|
||||||
|
--brand-bg-low-hover: var(--warning-bg-low-hover);
|
||||||
|
--brand-bg-low-active: var(--warning-bg-low-active);
|
||||||
|
--brand-bg: var(--warning-bg);
|
||||||
|
--brand-bg-high-hover: var(--warning-bg-high-hover);
|
||||||
|
--brand-bg-high-active: var(--warning-bg-high-active);
|
||||||
|
--brand-border-color: var(--warning-border-color);
|
||||||
|
}
|
||||||
|
@utility brand-emphasis {
|
||||||
|
--brand-fg: var(--emphasis-fg);
|
||||||
|
--brand-bg-low-hover: var(--emphasis-bg-low-hover);
|
||||||
|
--brand-bg-low-active: var(--emphasis-bg-low-active);
|
||||||
|
--brand-bg: var(--emphasis-bg);
|
||||||
|
--brand-bg-high-hover: var(--emphasis-bg-high-hover);
|
||||||
|
--brand-bg-high-active: var(--emphasis-bg-high-active);
|
||||||
|
--brand-border-color: var(--emphasis-border-color);
|
||||||
|
}
|
||||||
18
packages/ui/src/styles/utility/gap.css
Normal file
18
packages/ui/src/styles/utility/gap.css
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
@utility gap-xs {
|
||||||
|
gap: calc(var(--spacing) * 0.5);
|
||||||
|
}
|
||||||
|
@utility gap-sm {
|
||||||
|
gap: calc(var(--spacing) * 2);
|
||||||
|
}
|
||||||
|
@utility gap-md {
|
||||||
|
gap: calc(var(--spacing) * 3);
|
||||||
|
}
|
||||||
|
@utility gap-lg {
|
||||||
|
gap: calc(var(--spacing) * 4);
|
||||||
|
}
|
||||||
|
@utility gap-xl {
|
||||||
|
gap: calc(var(--spacing) * 5);
|
||||||
|
}
|
||||||
|
@utility gap-2xl {
|
||||||
|
gap: calc(var(--spacing) * 6);
|
||||||
|
}
|
||||||
36
packages/ui/src/styles/utility/height.css
Normal file
36
packages/ui/src/styles/utility/height.css
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
@utility h-xs {
|
||||||
|
height: calc(var(--spacing) * 5);
|
||||||
|
}
|
||||||
|
@utility h-sm {
|
||||||
|
height: calc(var(--spacing) * 6);
|
||||||
|
}
|
||||||
|
@utility h-md {
|
||||||
|
height: calc(var(--spacing) * 8);
|
||||||
|
}
|
||||||
|
@utility h-lg {
|
||||||
|
height: calc(var(--spacing) * 9);
|
||||||
|
}
|
||||||
|
@utility h-xl {
|
||||||
|
height: calc(var(--spacing) * 11);
|
||||||
|
}
|
||||||
|
@utility h-2xl {
|
||||||
|
height: calc(var(--spacing) * 16);
|
||||||
|
}
|
||||||
|
@utility h-inline-xs {
|
||||||
|
height: var(--text-xs--line-height);
|
||||||
|
}
|
||||||
|
@utility h-inline-sm {
|
||||||
|
height: var(--text-sm--line-height);
|
||||||
|
}
|
||||||
|
@utility h-inline-md {
|
||||||
|
height: var(--text-base--line-height);
|
||||||
|
}
|
||||||
|
@utility h-inline-lg {
|
||||||
|
height: var(--text-lg--line-height);
|
||||||
|
}
|
||||||
|
@utility h-inline-xl {
|
||||||
|
height: var(--text-xl--line-height);
|
||||||
|
}
|
||||||
|
@utility h-inline-2xl {
|
||||||
|
height: var(--text-2xl--line-height);
|
||||||
|
}
|
||||||
16
packages/ui/src/styles/utility/loading.css
Normal file
16
packages/ui/src/styles/utility/loading.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
@utility loading-true {
|
||||||
|
position: "absolute";
|
||||||
|
top: "50%";
|
||||||
|
left: "50%";
|
||||||
|
transform: "translate(-50%, -50%)";
|
||||||
|
opacity: 1;
|
||||||
|
transition: "top 0.15s ease-in, opacity 0.1s ease-in";
|
||||||
|
}
|
||||||
|
@utility loading-false {
|
||||||
|
position: "absolute";
|
||||||
|
top: "-50%";
|
||||||
|
left: "50%";
|
||||||
|
transform: "translate(-50%, -50%)";
|
||||||
|
opacity: 0;
|
||||||
|
transition: "top 0.15s ease-out, opacity 0.1s ease-out";
|
||||||
|
}
|
||||||
21
packages/ui/src/styles/utility/margin.css
Normal file
21
packages/ui/src/styles/utility/margin.css
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
@utility mr-none {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
@utility mr-xs {
|
||||||
|
margin-right: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
|
@utility mr-sm {
|
||||||
|
margin-right: calc(var(--spacing) * 2);
|
||||||
|
}
|
||||||
|
@utility mr-md {
|
||||||
|
margin-right: calc(var(--spacing) * 3);
|
||||||
|
}
|
||||||
|
@utility mr-lg {
|
||||||
|
margin-right: calc(var(--spacing) * 4);
|
||||||
|
}
|
||||||
|
@utility mr-xl {
|
||||||
|
margin-right: calc(var(--spacing) * 2);
|
||||||
|
}
|
||||||
|
@utility mr-2xl {
|
||||||
|
margin-right: calc(var(--spacing) * 2);
|
||||||
|
}
|
||||||
36
packages/ui/src/styles/utility/padding.css
Normal file
36
packages/ui/src/styles/utility/padding.css
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
@utility px-xs {
|
||||||
|
padding-inline: calc(var(--spacing) * 2);
|
||||||
|
}
|
||||||
|
@utility px-sm {
|
||||||
|
padding-inline: calc(var(--spacing) * 3);
|
||||||
|
}
|
||||||
|
@utility px-md {
|
||||||
|
padding-inline: calc(var(--spacing) * 4);
|
||||||
|
}
|
||||||
|
@utility px-lg {
|
||||||
|
padding-inline: calc(var(--spacing) * 5);
|
||||||
|
}
|
||||||
|
@utility px-xl {
|
||||||
|
padding-inline: calc(var(--spacing) * 6);
|
||||||
|
}
|
||||||
|
@utility px-2xl {
|
||||||
|
padding-inline: calc(var(--spacing) * 8);
|
||||||
|
}
|
||||||
|
@utility py-xs {
|
||||||
|
padding-block: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
|
@utility py-sm {
|
||||||
|
padding-block: calc(var(--spacing) * 2);
|
||||||
|
}
|
||||||
|
@utility py-md {
|
||||||
|
padding-block: calc(var(--spacing) * 3);
|
||||||
|
}
|
||||||
|
@utility py-lg {
|
||||||
|
padding-block: calc(var(--spacing) * 4);
|
||||||
|
}
|
||||||
|
@utility py-xl {
|
||||||
|
padding-block: calc(var(--spacing) * 5);
|
||||||
|
}
|
||||||
|
@utility py-2xl {
|
||||||
|
padding-block: calc(var(--spacing) * 6);
|
||||||
|
}
|
||||||
13
packages/ui/src/styles/utility/skin.css
Normal file
13
packages/ui/src/styles/utility/skin.css
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@utility light {
|
||||||
|
--base-fg: var(--color-gray-950);
|
||||||
|
--base-bg: var(--color-white);
|
||||||
|
--base-fg-subtext: var(--color-gray-500);
|
||||||
|
--base-bg-subtext: var(--color-gray-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility dark {
|
||||||
|
--base-fg: var(--color-gray-50);
|
||||||
|
--base-bg: var(--color-black);
|
||||||
|
--base-fg-subtext: var(--color-gray-400);
|
||||||
|
--base-bg-subtext: var(--color-gray-600);
|
||||||
|
}
|
||||||
137
packages/ui/src/styles/utility/variant.css
Normal file
137
packages/ui/src/styles/utility/variant.css
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
@utility variant-filled {
|
||||||
|
color: var(--filled-fg);
|
||||||
|
background-color: var(--filled-bg);
|
||||||
|
border-color: var(--filled-border-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--filled-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--filled-bg-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
background-color: var(--filled-bg-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility variant-outline {
|
||||||
|
color: var(--outline-fg);
|
||||||
|
background-color: var(--outline-bg);
|
||||||
|
border-color: var(--outline-border-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--outline-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--outline-bg-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
background-color: var(--outline-bg-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility variant-subtle {
|
||||||
|
color: var(--subtle-fg);
|
||||||
|
background-color: var(--subtle-bg);
|
||||||
|
border-color: var(--subtle-border-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--subtle-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--subtle-bg-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
background-color: var(--subtle-bg-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility variant-filled-disabled {
|
||||||
|
color: var(--filled-fg);
|
||||||
|
background-color: var(--filled-bg);
|
||||||
|
border-color: var(--filled-border-color);
|
||||||
|
filter: grayscale(50%);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--filled-fg);
|
||||||
|
background-color: var(--filled-bg);
|
||||||
|
border-color: var(--filled-border-color);
|
||||||
|
filter: grayscale(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: var(--filled-fg);
|
||||||
|
background-color: var(--filled-bg);
|
||||||
|
border-color: var(--filled-border-color);
|
||||||
|
filter: grayscale(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
color: var(--filled-fg);
|
||||||
|
background-color: var(--filled-bg);
|
||||||
|
border-color: var(--filled-border-color);
|
||||||
|
filter: grayscale(50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility variant-outline-disabled {
|
||||||
|
color: var(--outline-fg);
|
||||||
|
background-color: var(--outline-bg);
|
||||||
|
border-color: var(--outline-border-color);
|
||||||
|
filter: grayscale(50%);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--outline-fg);
|
||||||
|
background-color: var(--outline-bg);
|
||||||
|
border-color: var(--outline-border-color);
|
||||||
|
filter: grayscale(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: var(--outline-fg);
|
||||||
|
background-color: var(--outline-bg);
|
||||||
|
border-color: var(--outline-border-color);
|
||||||
|
filter: grayscale(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
color: var(--outline-fg);
|
||||||
|
background-color: var(--outline-bg);
|
||||||
|
border-color: var(--outline-border-color);
|
||||||
|
filter: grayscale(50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility variant-subtle-disabled {
|
||||||
|
color: var(--subtle-fg);
|
||||||
|
background-color: var(--subtle-bg);
|
||||||
|
border-color: var(--subtle-border-color);
|
||||||
|
filter: grayscale(50%);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--subtle-fg);
|
||||||
|
background-color: var(--subtle-bg);
|
||||||
|
border-color: var(--subtle-border-color);
|
||||||
|
filter: grayscale(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: var(--subtle-fg);
|
||||||
|
background-color: var(--subtle-bg);
|
||||||
|
border-color: var(--subtle-border-color);
|
||||||
|
filter: grayscale(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
color: var(--subtle-fg);
|
||||||
|
background-color: var(--subtle-bg);
|
||||||
|
border-color: var(--subtle-border-color);
|
||||||
|
filter: grayscale(50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
packages/ui/src/styles/utility/width.css
Normal file
36
packages/ui/src/styles/utility/width.css
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
@utility w-xs {
|
||||||
|
width: calc(var(--spacing) * 5);
|
||||||
|
}
|
||||||
|
@utility w-sm {
|
||||||
|
width: calc(var(--spacing) * 6);
|
||||||
|
}
|
||||||
|
@utility w-md {
|
||||||
|
width: calc(var(--spacing) * 8);
|
||||||
|
}
|
||||||
|
@utility w-lg {
|
||||||
|
width: calc(var(--spacing) * 9);
|
||||||
|
}
|
||||||
|
@utility w-xl {
|
||||||
|
width: calc(var(--spacing) * 11);
|
||||||
|
}
|
||||||
|
@utility w-2xl {
|
||||||
|
width: calc(var(--spacing) * 16);
|
||||||
|
}
|
||||||
|
@utility w-inline-xs {
|
||||||
|
width: var(--text-xs--line-height);
|
||||||
|
}
|
||||||
|
@utility w-inline-sm {
|
||||||
|
width: var(--text-sm--line-height);
|
||||||
|
}
|
||||||
|
@utility w-inline-md {
|
||||||
|
width: var(--text-base--line-height);
|
||||||
|
}
|
||||||
|
@utility w-inline-lg {
|
||||||
|
width: var(--text-lg--line-height);
|
||||||
|
}
|
||||||
|
@utility w-inline-xl {
|
||||||
|
width: var(--text-xl--line-height);
|
||||||
|
}
|
||||||
|
@utility w-inline-2xl {
|
||||||
|
width: var(--text-2xl--line-height);
|
||||||
|
}
|
||||||
25
packages/ui/tsconfig.json
Normal file
25
packages/ui/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/vite-react/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"declaration": true, // 补充:生成类型声明文件,供其他子包引用
|
||||||
|
"skipLibCheck": true, // 补充:跳过第三方库类型校验,避免冲突、提升速度
|
||||||
|
"types": ["vite/client"]
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist",
|
||||||
|
"vite.config.ts",
|
||||||
|
"example/**/*",
|
||||||
|
"scripts/**/*",
|
||||||
|
".storybook/**/*",
|
||||||
|
"stories/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
78
packages/ui/vite.config.ts
Normal file
78
packages/ui/vite.config.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import dts from "vite-plugin-dts";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const isProduction = mode === "production";
|
||||||
|
|
||||||
|
return {
|
||||||
|
// React 核心插件
|
||||||
|
plugins: [
|
||||||
|
tailwindcss(),
|
||||||
|
react(),
|
||||||
|
dts({
|
||||||
|
include: ["src/**/*"],
|
||||||
|
exclude: ["src/**/*.test.ts", "src/**/*.stories.tsx"],
|
||||||
|
outDir: "./dist",
|
||||||
|
rollupTypes: true, // 合并类型声明文件
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
|
||||||
|
// 路径别名与后缀配置
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": resolve(__dirname, "./src"),
|
||||||
|
},
|
||||||
|
extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
|
||||||
|
},
|
||||||
|
|
||||||
|
// 纯 ES 模式打包配置(移除 UMD 相关)
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, "./src/index.ts"),
|
||||||
|
formats: ["es"], // 仅保留 ES 模块格式
|
||||||
|
fileName: () => "index.es.js", // 固定 ES 模式文件名
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
// 排除 React 相关依赖(用户项目自行引入)
|
||||||
|
external: ["react", "react-dom"],
|
||||||
|
output: {
|
||||||
|
compact: isProduction, // 生产环境压缩代码格式
|
||||||
|
globals: {
|
||||||
|
react: "React",
|
||||||
|
"react-dom": "ReactDOM",
|
||||||
|
},
|
||||||
|
assetFileNames: (assetInfo) => {
|
||||||
|
if (assetInfo.name && assetInfo.name.endsWith(".css")) {
|
||||||
|
return "index.css";
|
||||||
|
}
|
||||||
|
return assetInfo.name || "[name].[ext]";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 移除:rollupOptions 下无效的 exclude 配置
|
||||||
|
// exclude: ["example/**/*", "scripts/**/*"],
|
||||||
|
},
|
||||||
|
outDir: "./dist",
|
||||||
|
cssCodeSplit: false,
|
||||||
|
sourcemap: true,
|
||||||
|
minify: isProduction ? "esbuild" : false,
|
||||||
|
emptyOutDir: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// TS 兼容配置
|
||||||
|
esbuild: {
|
||||||
|
ignoreAnnotations: true,
|
||||||
|
// 移除:esbuild 下无效的 exclude 配置
|
||||||
|
// exclude: ["example/**/*", "scripts/**/*"],
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增:使用 Vite 官方支持的方式排除文件
|
||||||
|
// 通过 optimizeDeps.exclude 排除依赖,通过 build.assetsInclude 反向控制,
|
||||||
|
// 同时结合 tsconfig.json 的 exclude 确保 TS 编译也排除目标文件
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ["example", "scripts"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user