Compare commits
25 Commits
36f865b026
...
main
Author | SHA1 | Date | |
---|---|---|---|
737014b90f | |||
43980ae43c | |||
3011a92c73 | |||
784aa6380e | |||
8b2313f817 | |||
10fe490879 | |||
9a0e22ff8a | |||
54a486427b | |||
55b1c5fdca | |||
64bc50f1e8 | |||
e7bfee884d | |||
bb7ee2d2a5 | |||
b4c367b799 | |||
1d10cf5888 | |||
7b7a647612 | |||
6c8effeaf3 | |||
e8b0e353ef | |||
99cfa7f34d | |||
c5a509e4e8 | |||
5e67c93b63 | |||
1327438f89 | |||
4784ac623b | |||
c4e0ec2a53 | |||
bff92667f4 | |||
66aae218f3 |
67
.gitignore
vendored
67
.gitignore
vendored
@@ -1,9 +1,64 @@
|
||||
node_modules
|
||||
|
||||
.pnpm-debug.log
|
||||
.vscode
|
||||
dist
|
||||
build
|
||||
.env
|
||||
# mac
|
||||
.DS_Store
|
||||
|
||||
.DS_Store
|
||||
.env*
|
||||
!.env*example
|
||||
|
||||
dist
|
||||
# build
|
||||
/build
|
||||
|
||||
/logs
|
||||
|
||||
.turbo
|
||||
|
||||
pack-dist
|
||||
|
||||
# astro
|
||||
.astro
|
||||
|
||||
# next
|
||||
.next
|
||||
|
||||
# nuxt
|
||||
.nuxt
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# vuepress
|
||||
.vuepress/dist
|
||||
|
||||
# coverage
|
||||
coverage/
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
# debug logs
|
||||
*.log
|
||||
*.tmp
|
||||
|
||||
# vscode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# idea
|
||||
.idea
|
||||
|
||||
# system
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# temp files
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# local development
|
||||
*.local
|
3
.npmrc
3
.npmrc
@@ -1 +1,2 @@
|
||||
@abearxiong:registry=https://npm.pkg.github.com
|
||||
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"tailwindCSS.classFunctions": ["cva", "cx"]
|
||||
}
|
3
apps/.vscode/settings.json
vendored
Normal file
3
apps/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"tailwindCSS.classFunctions": ["cva", "cx"]
|
||||
}
|
66
apps/demo/.gitignore
vendored
Normal file
66
apps/demo/.gitignore
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
node_modules
|
||||
|
||||
# mac
|
||||
.DS_Store
|
||||
|
||||
.env*
|
||||
!.env*example
|
||||
|
||||
/dist
|
||||
# build
|
||||
/build
|
||||
|
||||
/logs
|
||||
|
||||
.turbo
|
||||
|
||||
/pack-dist
|
||||
|
||||
# astro
|
||||
.astro
|
||||
|
||||
# next
|
||||
.next
|
||||
|
||||
# nuxt
|
||||
.nuxt
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# vuepress
|
||||
.vuepress/dist
|
||||
|
||||
# coverage
|
||||
coverage/
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
# debug logs
|
||||
*.log
|
||||
*.tmp
|
||||
|
||||
# vscode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# idea
|
||||
.idea
|
||||
|
||||
# system
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# temp files
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# local development
|
||||
*.local
|
||||
|
||||
public/r
|
3
apps/demo/.npmrc
Normal file
3
apps/demo/.npmrc
Normal file
@@ -0,0 +1,3 @@
|
||||
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
ignore-workspace-root-check=true
|
28
apps/demo/astro.config.mjs
Normal file
28
apps/demo/astro.config.mjs
Normal file
@@ -0,0 +1,28 @@
|
||||
// astro.config.mjs
|
||||
|
||||
import { defineConfig } from 'astro/config';
|
||||
import mdx from '@astrojs/mdx';
|
||||
import react from '@astrojs/react';
|
||||
// import sitemap from '@astrojs/sitemap';
|
||||
import pkgs from './package.json';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
export default defineConfig({
|
||||
// ...
|
||||
// site: 'https://kevisual.xiongxiao.me/root/astro/',
|
||||
base: isDev ? undefined : pkgs.basename,
|
||||
integrations: [
|
||||
mdx(),
|
||||
react(), //
|
||||
// sitemap(), // sitemap must be site has a domain
|
||||
],
|
||||
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
define: {
|
||||
BASE_NAME: JSON.stringify(pkgs.basename),
|
||||
DEV_SERVER: JSON.stringify(isDev),
|
||||
},
|
||||
},
|
||||
});
|
21
apps/demo/components.json
Normal file
21
apps/demo/components.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/styles/global.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
35
apps/demo/package.json
Normal file
35
apps/demo/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "demo",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"build:registry": "pnpm dlx shadcn@latest build"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.10.0",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@astrojs/mdx": "^4.2.6",
|
||||
"@astrojs/react": "^4.2.7",
|
||||
"@kevisual/types": "^0.0.10",
|
||||
"@tailwindcss/vite": "^4.1.6",
|
||||
"@types/react": "^19.1.3",
|
||||
"astro": "^5.7.12",
|
||||
"tailwindcss": "^4.1.6",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tw-animate-css": "^1.2.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.509.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
}
|
||||
}
|
6
apps/demo/src/lib/utils.ts
Normal file
6
apps/demo/src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
1
apps/demo/src/modules/basename.ts
Normal file
1
apps/demo/src/modules/basename.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const basename = DEV_SERVER ? '' : '/root/center';
|
4
apps/demo/src/pages/index.astro
Normal file
4
apps/demo/src/pages/index.astro
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
import '../styles/global.css'
|
||||
---
|
||||
index
|
120
apps/demo/src/styles/global.css
Normal file
120
apps/demo/src/styles/global.css
Normal file
@@ -0,0 +1,120 @@
|
||||
@import 'tailwindcss';
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
22
apps/demo/tsconfig.json
Normal file
22
apps/demo/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "@kevisual/types/json/frontend.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@kevisual"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
],
|
||||
"@/registry/*": [
|
||||
"registry/*"
|
||||
]
|
||||
},
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"registry/**/*"
|
||||
],
|
||||
}
|
66
libs/registry/.gitignore
vendored
Normal file
66
libs/registry/.gitignore
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
node_modules
|
||||
|
||||
# mac
|
||||
.DS_Store
|
||||
|
||||
.env*
|
||||
!.env*example
|
||||
|
||||
/dist
|
||||
# build
|
||||
/build
|
||||
|
||||
/logs
|
||||
|
||||
.turbo
|
||||
|
||||
/pack-dist
|
||||
|
||||
# astro
|
||||
.astro
|
||||
|
||||
# next
|
||||
.next
|
||||
|
||||
# nuxt
|
||||
.nuxt
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# vuepress
|
||||
.vuepress/dist
|
||||
|
||||
# coverage
|
||||
coverage/
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
# debug logs
|
||||
*.log
|
||||
*.tmp
|
||||
|
||||
# vscode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# idea
|
||||
.idea
|
||||
|
||||
# system
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# temp files
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# local development
|
||||
*.local
|
||||
|
||||
public/r
|
3
libs/registry/.npmrc
Normal file
3
libs/registry/.npmrc
Normal file
@@ -0,0 +1,3 @@
|
||||
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
ignore-workspace-root-check=true
|
10
libs/registry/.vscode/settings.json
vendored
Normal file
10
libs/registry/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"tailwindCSS.classFunctions": [
|
||||
"cva",
|
||||
"cx"
|
||||
],
|
||||
"workbench.editorAssociations": {
|
||||
// "*.md": "vscode.markdown.preview.editor" // 预览打开
|
||||
"*.md": "default" // 默认打开
|
||||
}
|
||||
}
|
43
libs/registry/astro.config.mjs
Normal file
43
libs/registry/astro.config.mjs
Normal file
@@ -0,0 +1,43 @@
|
||||
// astro.config.mjs
|
||||
|
||||
import { defineConfig } from 'astro/config';
|
||||
import mdx from '@astrojs/mdx';
|
||||
import react from '@astrojs/react';
|
||||
// import sitemap from '@astrojs/sitemap';
|
||||
import pkgs from './package.json';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
export default defineConfig({
|
||||
// ...
|
||||
// site: 'https://kevisual.xiongxiao.me/root/astro/',
|
||||
base: pkgs.basename,
|
||||
markdown: {
|
||||
// 适用于 MDX 和普通 Markdown 的配置
|
||||
syntaxHighlight: 'shiki',
|
||||
shikiConfig: {
|
||||
theme: 'nord',
|
||||
},
|
||||
remarkPlugins: [
|
||||
'remark-gfm', // GitHub Flavored Markdown
|
||||
['remark-toc', { headings: ['h2', 'h3'] }], // 目录生成
|
||||
],
|
||||
rehypePlugins: [
|
||||
'rehype-slug', // 为标题添加 ID
|
||||
'rehype-autolink-headings', // 为标题添加链接
|
||||
],
|
||||
},
|
||||
integrations: [
|
||||
mdx(),
|
||||
react(), //
|
||||
// sitemap(), // sitemap must be site has a domain
|
||||
],
|
||||
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
define: {
|
||||
BASE_NAME: JSON.stringify(pkgs.basename),
|
||||
DEV_SERVER: JSON.stringify(isDev),
|
||||
},
|
||||
},
|
||||
});
|
21
libs/registry/components.json
Normal file
21
libs/registry/components.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/styles/global.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
14
libs/registry/kevisual.json
Normal file
14
libs/registry/kevisual.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"metadata": {
|
||||
"share": "public"
|
||||
},
|
||||
"syncDirectory": [
|
||||
{
|
||||
"files": [
|
||||
"registry/**/*"
|
||||
],
|
||||
"registry": "https://kevisual.xiongxiao.me/root/ai/code",
|
||||
"replace": {}
|
||||
}
|
||||
]
|
||||
}
|
63
libs/registry/package.json
Normal file
63
libs/registry/package.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "@kevisual/registry",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"basename": "/root/registry",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "paraglide-js compile --project ./project.inlang --outdir ./src/paraglide && astro build",
|
||||
"preview": "astro preview",
|
||||
"build:registry": "pnpm dlx shadcn@latest build",
|
||||
"machine-translate": "inlang machine translate --project project.inlang",
|
||||
"pub": ""
|
||||
},
|
||||
"files": [
|
||||
"registry",
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.10.0",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@astrojs/mdx": "^4.2.6",
|
||||
"@astrojs/react": "^4.2.7",
|
||||
"@kevisual/types": "^0.0.10",
|
||||
"@tailwindcss/vite": "^4.1.6",
|
||||
"@types/react": "^19.1.3",
|
||||
"astro": "^5.7.12",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-toc": "^9.0.0",
|
||||
"tailwindcss": "^4.1.6",
|
||||
"tw-animate-css": "^1.2.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"i18next": "^25.1.2",
|
||||
"i18next-browser-languagedetector": "^8.1.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"lucide-react": "^0.509.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^15.5.1",
|
||||
"react-toastify": "^11.0.5",
|
||||
"tailwind-merge": "^3.2.0"
|
||||
},
|
||||
"exports": {
|
||||
".": "./registry/index.js",
|
||||
"./components/*": "./registry/components/*",
|
||||
"./hooks/*": "./registry/hooks/*",
|
||||
"./lib/*": "./registry/lib/*",
|
||||
"./pages/*": "./registry/pages/*",
|
||||
"./styles/*": "./registry/styles/*",
|
||||
"./types/*": "./registry/types/*"
|
||||
}
|
||||
}
|
32
libs/registry/registry.json
Normal file
32
libs/registry/registry.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry.json",
|
||||
"name": "shadcn",
|
||||
"homepage": "https://ui.shadcn.com",
|
||||
"items": [
|
||||
{
|
||||
"name": "hello-world",
|
||||
"type": "registry:block",
|
||||
"title": "Hello World",
|
||||
"description": "A simple hello world component.",
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/hello-world/hello-world.tsx",
|
||||
"type": "registry:component"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "basename",
|
||||
"type": "registry:file",
|
||||
"title": "Basename",
|
||||
"description": "The basename for the router.",
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/modules/basename.ts",
|
||||
"type": "registry:file",
|
||||
"target": "src/modules/basename.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
27
libs/registry/registry/astro/i18n/index.tsx
Normal file
27
libs/registry/registry/astro/i18n/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
|
||||
// 导入你的翻译资源
|
||||
import enTranslation from './locales/en/translation.json';
|
||||
import zhTranslation from './locales/zh/translation.json';
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources: {
|
||||
en: {
|
||||
translation: enTranslation,
|
||||
},
|
||||
zh: {
|
||||
translation: zhTranslation,
|
||||
},
|
||||
},
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false, // React 已经处理了转义
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"welcome": "Welcome to our site",
|
||||
"about": "About us"
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"welcome": "欢迎来到我们的网站",
|
||||
"about": "关于我们"
|
||||
}
|
36
libs/registry/registry/astro/layouts/mdx/main.astro
Normal file
36
libs/registry/registry/astro/layouts/mdx/main.astro
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
export interface Props {
|
||||
children: any;
|
||||
}
|
||||
---
|
||||
|
||||
<html lang='zh'>
|
||||
<header>
|
||||
<meta charset='UTF-8' />
|
||||
<title>Docs</title>
|
||||
<link
|
||||
rel='stylesheet'
|
||||
href='https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.8.1/github-markdown-light.min.css'
|
||||
integrity='sha512-X175XRJAO6PHAUi8AA7GP8uUF5Wiv+w9bOi64i02CHKDQBsO1yy0jLSKaUKg/NhRCDYBmOLQCfKaTaXiyZlLrw=='
|
||||
crossorigin='anonymous'
|
||||
referrerpolicy='no-referrer'
|
||||
/>
|
||||
</header>
|
||||
<body>
|
||||
<header>
|
||||
<slot name='header'>
|
||||
<h1>My Site Header</h1>
|
||||
</slot>
|
||||
</header>
|
||||
<main class='markdown-body' style='padding: 1rem'>
|
||||
<slot />
|
||||
</main>
|
||||
<footer>
|
||||
<slot name='footer'>
|
||||
<p>Copyrignt © 2025</p>
|
||||
</slot>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
80
libs/registry/registry/components/a/auto-complate.tsx
Normal file
80
libs/registry/registry/components/a/auto-complate.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Check, ChevronsUpDown } from 'lucide-react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Button } from '@/components/a/button';
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
|
||||
type Option = {
|
||||
value?: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
type AutoComplateProps = {
|
||||
options: Option[];
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
width?: string;
|
||||
};
|
||||
export function AutoComplate(props: AutoComplateProps) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [value, _setValue] = React.useState('');
|
||||
const setValue = (value: string) => {
|
||||
props?.onChange?.(value);
|
||||
_setValue(value);
|
||||
};
|
||||
const showLabel = React.useMemo(() => {
|
||||
const option = props.options.find((option) => option.value === value);
|
||||
if (option) {
|
||||
return option?.label;
|
||||
}
|
||||
if (props.value) return props.value;
|
||||
if (value) return value;
|
||||
return 'Select ...';
|
||||
}, [value, props.value]);
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant='outline' role='combobox' aria-expanded={open} className={cn(props.width ? props.width : 'w-[400px]', 'justify-between')}>
|
||||
{showLabel}
|
||||
<ChevronsUpDown className='ml-2 h-4 w-4 shrink-0 opacity-50' />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className={cn(props.width ? props.width : 'w-[400px]', ' p-0')}>
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder={props.placeholder ?? 'Search options...'}
|
||||
onKeyDown={(e: any) => {
|
||||
if (e.key === 'Enter') {
|
||||
setOpen(false);
|
||||
const value = e.target?.value || '';
|
||||
setValue(value.trim());
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>No options found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{props.options.map((framework) => (
|
||||
<CommandItem
|
||||
key={framework.value}
|
||||
value={framework.value}
|
||||
onSelect={(currentValue) => {
|
||||
setValue(currentValue === value ? '' : currentValue);
|
||||
setOpen(false);
|
||||
}}>
|
||||
<Check className={cn('mr-2 h-4 w-4', value === framework.value ? 'opacity-100' : 'opacity-0')} />
|
||||
{framework.label}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
18
libs/registry/registry/components/a/button.tsx
Normal file
18
libs/registry/registry/components/a/button.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Button as UiButton, ButtonProps } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
export const IconButton: typeof UiButton = (props) => {
|
||||
return <UiButton variant='ghost' size='icon' {...props} className={cn('h-8 w-8 cursor-pointer', props?.className)} />;
|
||||
};
|
||||
|
||||
export const Button: typeof UiButton = (props) => {
|
||||
return <UiButton variant='ghost' {...props} className={cn('cursor-pointer', props?.className)} />;
|
||||
};
|
||||
|
||||
export const ButtonTextIcon = (props: ButtonProps & { icon: React.ReactNode }) => {
|
||||
return (
|
||||
<UiButton variant={'outline'} size='sm' {...props} className={cn('cursor-pointer flex items-center gap-2', props?.className)}>
|
||||
{props.icon}
|
||||
{props.children}
|
||||
</UiButton>
|
||||
);
|
||||
};
|
100
libs/registry/registry/components/a/confirm.tsx
Normal file
100
libs/registry/registry/components/a/confirm.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
type useConfirmOptions = {
|
||||
confrimProps: ConfirmProps;
|
||||
};
|
||||
type Fn = () => void;
|
||||
export const useConfirm = (opts?: useConfirmOptions) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
type ConfirmOptions = {
|
||||
onOk?: Fn;
|
||||
onCancel?: Fn;
|
||||
};
|
||||
const confirm = (opts?: ConfirmOptions) => {
|
||||
setOpen(true);
|
||||
};
|
||||
const module = useMemo(() => {
|
||||
return <Confirm {...opts?.confrimProps} hasTrigger={false} open={open} setOpen={setOpen} />;
|
||||
}, [open]);
|
||||
return {
|
||||
module: module,
|
||||
open,
|
||||
setOpen,
|
||||
confirm,
|
||||
};
|
||||
};
|
||||
|
||||
type ConfirmProps = {
|
||||
children?: React.ReactNode;
|
||||
tip?: React.ReactNode;
|
||||
title?: string;
|
||||
description?: string;
|
||||
onOkText?: string;
|
||||
onCancelText?: string;
|
||||
onOk?: Fn;
|
||||
onCancle?: Fn;
|
||||
hasTrigger?: boolean;
|
||||
open?: boolean;
|
||||
setOpen?: (open: boolean) => void;
|
||||
footer?: React.ReactNode;
|
||||
};
|
||||
export const Confirm = (props: ConfirmProps) => {
|
||||
const [isOpen, setIsOpen] = useState(props.open);
|
||||
const hasTrigger = props.hasTrigger ?? true;
|
||||
useEffect(() => {
|
||||
setIsOpen(props.open);
|
||||
}, [props.open]);
|
||||
return (
|
||||
<AlertDialog
|
||||
open={isOpen}
|
||||
onOpenChange={(v) => {
|
||||
setIsOpen(v);
|
||||
props?.setOpen?.(v);
|
||||
}}>
|
||||
{hasTrigger && (
|
||||
<>
|
||||
{props?.children && <AlertDialogTrigger asChild>{props?.children ?? props?.tip ?? '提示'}</AlertDialogTrigger>}
|
||||
{!props?.children && <AlertDialogTrigger>{props?.tip ?? '提示'}</AlertDialogTrigger>}
|
||||
</>
|
||||
)}
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{props?.title ?? '是否确认删除?'}</AlertDialogTitle>
|
||||
<AlertDialogDescription>{props?.description ?? '此操作无法撤销,是否继续。'}</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
{props?.footer && <div className='flex gap-2'>{props?.footer}</div>}
|
||||
{!props?.footer && (
|
||||
<>
|
||||
<AlertDialogCancel
|
||||
onClick={(e) => {
|
||||
props?.onCancle?.();
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
{props?.onCancelText ?? '取消'}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={(e) => {
|
||||
props?.onOk?.();
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
{props?.onOkText ?? '确定'}
|
||||
</AlertDialogAction>{' '}
|
||||
</>
|
||||
)}
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
31
libs/registry/registry/components/a/select.tsx
Normal file
31
libs/registry/registry/components/a/select.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Select as UISelect, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
|
||||
type Option = {
|
||||
value: string;
|
||||
label?: string;
|
||||
};
|
||||
type SelectProps = {
|
||||
options?: Option[];
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
onChange?: (value: string) => any;
|
||||
};
|
||||
export const Select = (props: SelectProps) => {
|
||||
const options = props.options || [];
|
||||
return (
|
||||
<UISelect onValueChange={props.onChange} value={props.value}>
|
||||
<SelectTrigger className='w-[180px]'>
|
||||
<SelectValue placeholder={props.placeholder || '请选择'} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{options.map((item, index) => {
|
||||
return (
|
||||
<SelectItem key={index} value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</UISelect>
|
||||
);
|
||||
};
|
14
libs/registry/registry/components/a/tooltip.tsx
Normal file
14
libs/registry/registry/components/a/tooltip.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Tooltip as UITooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
|
||||
export const Tooltip = (props: { children?: React.ReactNode; title?: string }) => {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<UITooltip>
|
||||
<TooltipTrigger asChild>{props.children}</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{props.title}</p>
|
||||
</TooltipContent>
|
||||
</UITooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
45
libs/registry/registry/components/b/button/button.tsx
Normal file
45
libs/registry/registry/components/b/button/button.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
const button = cva('button', {
|
||||
variants: {
|
||||
intent: {
|
||||
primary: ['bg-blue-500', 'text-white', 'border-transparent'],
|
||||
secondary: ['bg-white', 'text-gray-800', 'border-gray-400'],
|
||||
},
|
||||
size: {
|
||||
small: ['text-sm', 'py-1', 'px-2'],
|
||||
medium: ['text-base', 'py-2', 'px-4'],
|
||||
},
|
||||
disabled: {
|
||||
false: null,
|
||||
true: ['opacity-50', 'cursor-not-allowed'],
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
intent: 'primary',
|
||||
disabled: false,
|
||||
class: 'hover:bg-blue-600',
|
||||
},
|
||||
{
|
||||
intent: 'secondary',
|
||||
disabled: false,
|
||||
class: 'hover:bg-gray-100',
|
||||
},
|
||||
{ intent: 'primary', size: 'medium', class: 'uppercase' },
|
||||
],
|
||||
defaultVariants: {
|
||||
disabled: false,
|
||||
intent: 'primary',
|
||||
size: 'medium',
|
||||
},
|
||||
});
|
||||
|
||||
export interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'disabled'>, VariantProps<typeof button> {}
|
||||
|
||||
export const Button: React.FC<ButtonProps> = ({ className, intent, size, disabled, ...props }) => (
|
||||
<button className={twMerge('cursor-pointer', button({ intent, size, disabled, className }))} disabled={disabled || undefined} {...props} />
|
||||
);
|
56
libs/registry/registry/components/b/card/card.tsx
Normal file
56
libs/registry/registry/components/b/card/card.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
// components/card.ts
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva, cx } from 'class-variance-authority';
|
||||
import { cn } from '@/lib/utils';
|
||||
/**
|
||||
* Box
|
||||
*/
|
||||
export type BoxProps = VariantProps<typeof box>;
|
||||
export const box = cva(['box', 'box-border'], {
|
||||
variants: {
|
||||
margin: { 0: 'm-0', 2: 'm-2', 4: 'm-4', 8: 'm-8' },
|
||||
padding: { 0: 'p-0', 2: 'p-2', 4: 'p-4', 8: 'p-8' },
|
||||
},
|
||||
defaultVariants: {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Card
|
||||
*/
|
||||
type CardBaseProps = VariantProps<typeof cardBase>;
|
||||
const cardBase = cva(['card', 'border-solid', 'border-slate-300', 'rounded'], {
|
||||
variants: {
|
||||
shadow: {
|
||||
md: 'drop-shadow-md',
|
||||
lg: 'drop-shadow-lg',
|
||||
xl: 'drop-shadow-xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export interface CardProps extends BoxProps, CardBaseProps {}
|
||||
export const card = ({ margin, padding, shadow }: CardProps = {}) => cx(box({ margin, padding }), cardBase({ shadow }));
|
||||
|
||||
type CardBlankProps = {
|
||||
number?: number;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* CardBlank 空的卡片,用于占位
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export const CardBlank = (props: CardBlankProps) => {
|
||||
const { number = 4, className } = props;
|
||||
return (
|
||||
<>
|
||||
{new Array(number).fill(0).map((_, index) => {
|
||||
return <div key={index} className={cn('w-[300px] shark-0', className)}></div>;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
74
libs/registry/registry/components/b/i18n/index.tsx
Normal file
74
libs/registry/registry/components/b/i18n/index.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import Backend from 'i18next-http-backend'; // 引入 Backend 插件
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
export { useTranslation };
|
||||
type I18NextProviderProps = {
|
||||
children: React.ReactNode;
|
||||
basename?: string;
|
||||
noUse?: boolean;
|
||||
};
|
||||
|
||||
export const initI18n = async (basename: string) => {
|
||||
// 初始化 i18n
|
||||
return new Promise((resolve) => {
|
||||
i18n
|
||||
.use(Backend) // 使用 Backend 插件
|
||||
.use(initReactI18next)
|
||||
.init(
|
||||
{
|
||||
backend: {
|
||||
loadPath: `${basename}/locales/{{lng}}/{{ns}}.json`, // 指定 JSON 文件的路径
|
||||
},
|
||||
lng: 'zh', // 默认语言
|
||||
fallbackLng: 'en', // 备用语言
|
||||
interpolation: {
|
||||
escapeValue: false, // react 已经安全地处理了转义
|
||||
},
|
||||
},
|
||||
(e) => {
|
||||
console.log('e', e);
|
||||
resolve(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 国际化组件,初始化
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export const I18NextProvider = (props: I18NextProviderProps) => {
|
||||
const { children, basename, noUse } = props;
|
||||
if (noUse) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
const [init, setInit] = useState(false);
|
||||
useLayoutEffect(() => {
|
||||
initCheck();
|
||||
}, []);
|
||||
const initCheck = async () => {
|
||||
let _currentBasename = '';
|
||||
if (typeof basename === 'undefined') {
|
||||
const local = localStorage.getItem('locale-basename');
|
||||
if (local) {
|
||||
_currentBasename = local;
|
||||
} else {
|
||||
_currentBasename = '';
|
||||
}
|
||||
} else {
|
||||
_currentBasename = basename;
|
||||
}
|
||||
if (_currentBasename === '/') {
|
||||
_currentBasename = '';
|
||||
}
|
||||
initI18n(_currentBasename);
|
||||
setInit(true);
|
||||
};
|
||||
if (!init) {
|
||||
return <></>;
|
||||
}
|
||||
return <>{children}</>;
|
||||
};
|
40
libs/registry/registry/components/b/render/ReactRender.tsx
Normal file
40
libs/registry/registry/components/b/render/ReactRender.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
export class ReactRenderer {
|
||||
component: any;
|
||||
element: HTMLElement;
|
||||
ref: React.RefObject<any>;
|
||||
props: any;
|
||||
root: any;
|
||||
|
||||
constructor(component: any, { props }: any) {
|
||||
this.component = component;
|
||||
this.element = document.createElement('div');
|
||||
this.ref = React.createRef();
|
||||
this.props = {
|
||||
...props,
|
||||
ref: this.ref,
|
||||
};
|
||||
this.root = createRoot(this.element);
|
||||
this.render();
|
||||
}
|
||||
|
||||
updateProps(props: any) {
|
||||
this.props = {
|
||||
...this.props,
|
||||
...props,
|
||||
};
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.root.render(React.createElement(this.component, this.props));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.root.unmount();
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactRenderer;
|
155
libs/registry/registry/components/ui/alert-dialog.tsx
Normal file
155
libs/registry/registry/components/ui/alert-dialog.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import * as React from "react"
|
||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
|
||||
function AlertDialog({
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
||||
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
||||
}
|
||||
|
||||
function AlertDialogTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function AlertDialogPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function AlertDialogOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Overlay
|
||||
data-slot="alert-dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AlertDialogContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
|
||||
return (
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
data-slot="alert-dialog-content"
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</AlertDialogPortal>
|
||||
)
|
||||
}
|
||||
|
||||
function AlertDialogHeader({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="alert-dialog-header"
|
||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AlertDialogFooter({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="alert-dialog-footer"
|
||||
className={cn(
|
||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AlertDialogTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Title
|
||||
data-slot="alert-dialog-title"
|
||||
className={cn("text-lg font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AlertDialogDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Description
|
||||
data-slot="alert-dialog-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AlertDialogAction({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Action
|
||||
className={cn(buttonVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AlertDialogCancel({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Cancel
|
||||
className={cn(buttonVariants({ variant: "outline" }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
AlertDialog,
|
||||
AlertDialogPortal,
|
||||
AlertDialogOverlay,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogFooter,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
}
|
109
libs/registry/registry/components/ui/breadcrumb.tsx
Normal file
109
libs/registry/registry/components/ui/breadcrumb.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
|
||||
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
|
||||
}
|
||||
|
||||
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||
return (
|
||||
<ol
|
||||
data-slot="breadcrumb-list"
|
||||
className={cn(
|
||||
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||
return (
|
||||
<li
|
||||
data-slot="breadcrumb-item"
|
||||
className={cn("inline-flex items-center gap-1.5", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function BreadcrumbLink({
|
||||
asChild,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"a"> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot : "a"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="breadcrumb-link"
|
||||
className={cn("hover:text-foreground transition-colors", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="breadcrumb-page"
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
className={cn("text-foreground font-normal", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function BreadcrumbSeparator({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"li">) {
|
||||
return (
|
||||
<li
|
||||
data-slot="breadcrumb-separator"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("[&>svg]:size-3.5", className)}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <ChevronRight />}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function BreadcrumbEllipsis({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="breadcrumb-ellipsis"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("flex size-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="size-4" />
|
||||
<span className="sr-only">More</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Breadcrumb,
|
||||
BreadcrumbList,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbEllipsis,
|
||||
}
|
53
libs/registry/registry/components/ui/button.tsx
Normal file
53
libs/registry/registry/components/ui/button.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import * as React from 'react';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
outline: 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
||||
secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
||||
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
||||
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
||||
icon: 'size-9',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
},
|
||||
);
|
||||
export type ButtonProps = React.ComponentProps<'button'> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean;
|
||||
};
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<'button'> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean;
|
||||
}) {
|
||||
const Comp = asChild ? Slot : 'button';
|
||||
|
||||
return <Comp data-slot='button' className={cn(buttonVariants({ variant, size, className }))} {...props} />;
|
||||
}
|
||||
|
||||
export { Button, buttonVariants };
|
175
libs/registry/registry/components/ui/command.tsx
Normal file
175
libs/registry/registry/components/ui/command.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
import * as React from "react"
|
||||
import { Command as CommandPrimitive } from "cmdk"
|
||||
import { SearchIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
|
||||
function Command({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive>) {
|
||||
return (
|
||||
<CommandPrimitive
|
||||
data-slot="command"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandDialog({
|
||||
title = "Command Palette",
|
||||
description = "Search for a command to run...",
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof Dialog> & {
|
||||
title?: string
|
||||
description?: string
|
||||
}) {
|
||||
return (
|
||||
<Dialog {...props}>
|
||||
<DialogHeader className="sr-only">
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogContent className="overflow-hidden p-0">
|
||||
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||
{children}
|
||||
</Command>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandInput({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="command-input-wrapper"
|
||||
className="flex h-9 items-center gap-2 border-b px-3"
|
||||
>
|
||||
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
data-slot="command-input"
|
||||
className={cn(
|
||||
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandList({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.List>) {
|
||||
return (
|
||||
<CommandPrimitive.List
|
||||
data-slot="command-list"
|
||||
className={cn(
|
||||
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandEmpty({
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
||||
return (
|
||||
<CommandPrimitive.Empty
|
||||
data-slot="command-empty"
|
||||
className="py-6 text-center text-sm"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandGroup({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
||||
return (
|
||||
<CommandPrimitive.Group
|
||||
data-slot="command-group"
|
||||
className={cn(
|
||||
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
||||
return (
|
||||
<CommandPrimitive.Separator
|
||||
data-slot="command-separator"
|
||||
className={cn("bg-border -mx-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
||||
return (
|
||||
<CommandPrimitive.Item
|
||||
data-slot="command-item"
|
||||
className={cn(
|
||||
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="command-shortcut"
|
||||
className={cn(
|
||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Command,
|
||||
CommandDialog,
|
||||
CommandInput,
|
||||
CommandList,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
CommandShortcut,
|
||||
CommandSeparator,
|
||||
}
|
133
libs/registry/registry/components/ui/dialog.tsx
Normal file
133
libs/registry/registry/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { XIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Dialog({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
||||
}
|
||||
|
||||
function DialogTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
||||
}
|
||||
|
||||
function DialogPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
||||
}
|
||||
|
||||
function DialogClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
||||
}
|
||||
|
||||
function DialogOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
||||
return (
|
||||
<DialogPrimitive.Overlay
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogContent({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
|
||||
return (
|
||||
<DialogPortal data-slot="dialog-portal">
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
|
||||
<XIcon />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-header"
|
||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-footer"
|
||||
className={cn(
|
||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
data-slot="dialog-title"
|
||||
className={cn("text-lg leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||
return (
|
||||
<DialogPrimitive.Description
|
||||
data-slot="dialog-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
}
|
165
libs/registry/registry/components/ui/form.tsx
Normal file
165
libs/registry/registry/components/ui/form.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import {
|
||||
Controller,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
useFormState,
|
||||
type ControllerProps,
|
||||
type FieldPath,
|
||||
type FieldValues,
|
||||
} from "react-hook-form"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Label } from "@/components/ui/label"
|
||||
|
||||
const Form = FormProvider
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName
|
||||
}
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue
|
||||
)
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext)
|
||||
const itemContext = React.useContext(FormItemContext)
|
||||
const { getFieldState } = useFormContext()
|
||||
const formState = useFormState({ name: fieldContext.name })
|
||||
const fieldState = getFieldState(fieldContext.name, formState)
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>")
|
||||
}
|
||||
|
||||
const { id } = itemContext
|
||||
|
||||
return {
|
||||
id,
|
||||
name: fieldContext.name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
}
|
||||
}
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string
|
||||
}
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue
|
||||
)
|
||||
|
||||
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
||||
const id = React.useId()
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div
|
||||
data-slot="form-item"
|
||||
className={cn("grid gap-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
</FormItemContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function FormLabel({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||
const { error, formItemId } = useFormField()
|
||||
|
||||
return (
|
||||
<Label
|
||||
data-slot="form-label"
|
||||
data-error={!!error}
|
||||
className={cn("data-[error=true]:text-destructive", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||
|
||||
return (
|
||||
<Slot
|
||||
data-slot="form-control"
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error
|
||||
? `${formDescriptionId}`
|
||||
: `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||
const { formDescriptionId } = useFormField()
|
||||
|
||||
return (
|
||||
<p
|
||||
data-slot="form-description"
|
||||
id={formDescriptionId}
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
||||
const { error, formMessageId } = useFormField()
|
||||
const body = error ? String(error?.message ?? "") : props.children
|
||||
|
||||
if (!body) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
data-slot="form-message"
|
||||
id={formMessageId}
|
||||
className={cn("text-destructive text-sm", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
}
|
21
libs/registry/registry/components/ui/input.tsx
Normal file
21
libs/registry/registry/components/ui/input.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
data-slot="input"
|
||||
className={cn(
|
||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Input }
|
24
libs/registry/registry/components/ui/label.tsx
Normal file
24
libs/registry/registry/components/ui/label.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Label({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||
return (
|
||||
<LabelPrimitive.Root
|
||||
data-slot="label"
|
||||
className={cn(
|
||||
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Label }
|
46
libs/registry/registry/components/ui/popover.tsx
Normal file
46
libs/registry/registry/components/ui/popover.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Popover({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
||||
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
||||
}
|
||||
|
||||
function PopoverTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
||||
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
||||
}
|
||||
|
||||
function PopoverContent({
|
||||
className,
|
||||
align = "center",
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
||||
return (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
data-slot="popover-content"
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function PopoverAnchor({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
||||
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
||||
}
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
183
libs/registry/registry/components/ui/select.tsx
Normal file
183
libs/registry/registry/components/ui/select.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Select({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
||||
}
|
||||
|
||||
function SelectGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
||||
}
|
||||
|
||||
function SelectValue({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
||||
}
|
||||
|
||||
function SelectTrigger({
|
||||
className,
|
||||
size = "default",
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||
size?: "sm" | "default"
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDownIcon className="size-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectContent({
|
||||
className,
|
||||
children,
|
||||
position = "popper",
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
||||
return (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
data-slot="select-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectLabel({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
||||
return (
|
||||
<SelectPrimitive.Label
|
||||
data-slot="select-label"
|
||||
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
||||
return (
|
||||
<SelectPrimitive.Separator
|
||||
data-slot="select-separator"
|
||||
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectScrollUpButton({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
data-slot="select-scroll-up-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectScrollDownButton({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
data-slot="select-scroll-down-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectScrollDownButton,
|
||||
SelectScrollUpButton,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
}
|
114
libs/registry/registry/components/ui/table.tsx
Normal file
114
libs/registry/registry/components/ui/table.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="table-container"
|
||||
className="relative w-full overflow-x-auto"
|
||||
>
|
||||
<table
|
||||
data-slot="table"
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
||||
return (
|
||||
<thead
|
||||
data-slot="table-header"
|
||||
className={cn("[&_tr]:border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
||||
return (
|
||||
<tbody
|
||||
data-slot="table-body"
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
||||
return (
|
||||
<tfoot
|
||||
data-slot="table-footer"
|
||||
className={cn(
|
||||
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
||||
return (
|
||||
<tr
|
||||
data-slot="table-row"
|
||||
className={cn(
|
||||
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
||||
return (
|
||||
<th
|
||||
data-slot="table-head"
|
||||
className={cn(
|
||||
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
||||
return (
|
||||
<td
|
||||
data-slot="table-cell"
|
||||
className={cn(
|
||||
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TableCaption({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"caption">) {
|
||||
return (
|
||||
<caption
|
||||
data-slot="table-caption"
|
||||
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
}
|
18
libs/registry/registry/components/ui/textarea.tsx
Normal file
18
libs/registry/registry/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||
return (
|
||||
<textarea
|
||||
data-slot="textarea"
|
||||
className={cn(
|
||||
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Textarea }
|
59
libs/registry/registry/components/ui/tooltip.tsx
Normal file
59
libs/registry/registry/components/ui/tooltip.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function TooltipProvider({
|
||||
delayDuration = 0,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
||||
return (
|
||||
<TooltipPrimitive.Provider
|
||||
data-slot="tooltip-provider"
|
||||
delayDuration={delayDuration}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function Tooltip({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function TooltipTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
||||
}
|
||||
|
||||
function TooltipContent({
|
||||
className,
|
||||
sideOffset = 0,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
||||
return (
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
data-slot="tooltip-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
3
libs/registry/registry/hello-world/hello-world.tsx
Normal file
3
libs/registry/registry/hello-world/hello-world.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export const HelloWorld = () => {
|
||||
return <div>Hello Wrold</div>;
|
||||
};
|
35
libs/registry/registry/kevisual.json
Normal file
35
libs/registry/registry/kevisual.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"metadata": {
|
||||
"share": "public"
|
||||
},
|
||||
"syncDirectory": [
|
||||
{
|
||||
"files": [
|
||||
"components/**/*"
|
||||
],
|
||||
"registry": "https://kevisual.xiongxiao.me/root/ai/code/registry",
|
||||
"replace": {}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"lib/**/*"
|
||||
],
|
||||
"registry": "https://kevisual.xiongxiao.me/root/ai/code/registry",
|
||||
"replace": {}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"modules/**/*"
|
||||
],
|
||||
"registry": "https://kevisual.xiongxiao.me/root/ai/code/registry",
|
||||
"replace": {}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"styles/**/*"
|
||||
],
|
||||
"registry": "https://kevisual.xiongxiao.me/root/ai/code/registry",
|
||||
"replace": {}
|
||||
}
|
||||
]
|
||||
}
|
194
libs/registry/registry/lib/player-stream.ts
Normal file
194
libs/registry/registry/lib/player-stream.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
type VideoStreamPlayerOptions = {
|
||||
emitter?: EventEmitter;
|
||||
};
|
||||
export class VideoStreamPlayer {
|
||||
emitter: EventEmitter;
|
||||
audioContext: AudioContext;
|
||||
audioBuffer: AudioBuffer | null = null;
|
||||
audioQueue: Uint8Array[] = [];
|
||||
decodedBuffers: AudioBuffer[] = []; // 存储已解码的音频缓冲区
|
||||
currentSource: AudioBufferSourceNode | null = null;
|
||||
audioElement: HTMLAudioElement;
|
||||
processing: boolean = false;
|
||||
canPlaying: boolean = false;
|
||||
isPlaying: boolean = false;
|
||||
bufferingThreshold: number = 3; // 预缓冲的音频块数量
|
||||
decodePromises: Promise<void>[] = []; // 跟踪解码进程
|
||||
nextPlayTime: number = 0; // 下一个音频片段的开始时间
|
||||
playStatus: 'paused' | 'playing' | 'buffering' | 'ended' = 'paused';
|
||||
|
||||
constructor(opts?: VideoStreamPlayerOptions) {
|
||||
this.emitter = opts?.emitter || new EventEmitter();
|
||||
this.audioContext = new AudioContext();
|
||||
this.audioElement = new Audio();
|
||||
this.audioElement.autoplay = false;
|
||||
// 确保在页面交互后恢复音频上下文(解决自动播放限制问题)
|
||||
document.addEventListener(
|
||||
'click',
|
||||
() => {
|
||||
if (this.audioContext.state === 'suspended') {
|
||||
this.audioContext.resume();
|
||||
}
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
}
|
||||
|
||||
// 处理收到的音频数据
|
||||
async appendAudioChunk(chunk: ArrayBuffer | Uint8Array | string) {
|
||||
let audioData: Uint8Array;
|
||||
|
||||
// 处理不同类型的输入数据
|
||||
if (typeof chunk === 'string') {
|
||||
// 如果是base64编码的数据
|
||||
const binary = atob(chunk);
|
||||
audioData = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
audioData[i] = binary.charCodeAt(i);
|
||||
}
|
||||
} else if (chunk instanceof ArrayBuffer) {
|
||||
audioData = new Uint8Array(chunk);
|
||||
} else {
|
||||
audioData = chunk;
|
||||
}
|
||||
|
||||
// 将音频数据加入队列
|
||||
this.audioQueue.push(audioData);
|
||||
|
||||
// 开始解码音频,不等待前面的解码完成
|
||||
this.decodeAudio();
|
||||
|
||||
// 如果当前没有在播放且可以播放,并且已有足够缓冲则开始播放
|
||||
if (!this.isPlaying && this.canPlaying && this.decodedBuffers.length >= this.bufferingThreshold) {
|
||||
this.startPlaying();
|
||||
}
|
||||
}
|
||||
|
||||
// 异步解码音频,不阻塞主线程
|
||||
async decodeAudio() {
|
||||
if (this.processing || this.audioQueue.length === 0) return;
|
||||
|
||||
this.processing = true;
|
||||
const chunk = this.audioQueue.shift()!;
|
||||
|
||||
try {
|
||||
// 解码音频数据
|
||||
const decodePromise = this.audioContext
|
||||
.decodeAudioData(chunk.buffer.slice(0))
|
||||
.then((audioBuffer) => {
|
||||
this.decodedBuffers.push(audioBuffer);
|
||||
|
||||
// 如果已经可以开始播放但尚未播放,开始播放
|
||||
if (this.canPlaying && !this.isPlaying && this.decodedBuffers.length >= this.bufferingThreshold) {
|
||||
this.startPlaying();
|
||||
}
|
||||
|
||||
const index = this.decodePromises.indexOf(decodePromise as any);
|
||||
if (index > -1) {
|
||||
this.decodePromises.splice(index, 1);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('音频解码错误:', error);
|
||||
const index = this.decodePromises.indexOf(decodePromise as any);
|
||||
if (index > -1) {
|
||||
this.decodePromises.splice(index, 1);
|
||||
}
|
||||
});
|
||||
|
||||
this.decodePromises.push(decodePromise as any);
|
||||
} finally {
|
||||
this.processing = false;
|
||||
|
||||
// 继续处理队列中的下一个
|
||||
if (this.audioQueue.length > 0) {
|
||||
this.decodeAudio();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始播放
|
||||
startPlaying() {
|
||||
if (this.decodedBuffers.length === 0 || this.isPlaying) return;
|
||||
|
||||
this.isPlaying = true;
|
||||
this.nextPlayTime = this.audioContext.currentTime;
|
||||
this.scheduleNextBuffer();
|
||||
this.emitter.emit('play-start');
|
||||
}
|
||||
|
||||
// 安排播放下一个音频缓冲区
|
||||
scheduleNextBuffer() {
|
||||
if (this.decodedBuffers.length === 0) {
|
||||
// 没有更多缓冲区时,如果队列中也没有待解码的数据,就标记为未播放状态
|
||||
if (this.audioQueue.length === 0 && this.decodePromises.length === 0) {
|
||||
this.isPlaying = false;
|
||||
this.emitter.emit('play-end', true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const audioBuffer = this.decodedBuffers.shift()!;
|
||||
const source = this.audioContext.createBufferSource();
|
||||
this.currentSource = source;
|
||||
source.buffer = audioBuffer;
|
||||
source.connect(this.audioContext.destination);
|
||||
|
||||
// 在确切的时间安排播放,确保无缝连接
|
||||
source.start(this.nextPlayTime);
|
||||
this.nextPlayTime = parseFloat((this.nextPlayTime + audioBuffer.duration).toFixed(4));
|
||||
// console.log('audioBuffer.duration start', audioBuffer.duration, this.nextPlayTime);
|
||||
// 在音频播放结束前安排下一个缓冲区(提前一点安排可以减少间隙)
|
||||
const safetyOffset = Math.min(0.05, audioBuffer.duration / 2); // 至少提前50ms或一半时长
|
||||
setTimeout(() => {
|
||||
this.scheduleNextBuffer();
|
||||
}, (audioBuffer.duration - safetyOffset) * 1000);
|
||||
|
||||
// 发出事件通知
|
||||
this.emitter.emit('playing', { duration: audioBuffer.duration });
|
||||
|
||||
// 如果缓冲区不足,继续解码
|
||||
if (this.decodedBuffers.length < this.bufferingThreshold && this.audioQueue.length > 0 && !this.processing) {
|
||||
this.decodeAudio();
|
||||
}
|
||||
}
|
||||
|
||||
// 处理WebSocket接收到的音频数据
|
||||
handleWebSocketAudio(data: any) {
|
||||
if (data && data.audio) {
|
||||
this.appendAudioChunk(data.audio);
|
||||
}
|
||||
}
|
||||
|
||||
// 停止播放
|
||||
stop() {
|
||||
if (this.currentSource) {
|
||||
try {
|
||||
this.currentSource.stop();
|
||||
} catch (e) {
|
||||
// 可能已经停止,忽略错误
|
||||
}
|
||||
this.currentSource.disconnect();
|
||||
this.currentSource = null;
|
||||
}
|
||||
|
||||
this.audioQueue = [];
|
||||
this.decodedBuffers = [];
|
||||
this.decodePromises = [];
|
||||
this.processing = false;
|
||||
this.isPlaying = false;
|
||||
this.canPlaying = false;
|
||||
this.nextPlayTime = 0;
|
||||
this.emitter.emit('stopped');
|
||||
}
|
||||
|
||||
setCanPlaying(canPlaying = true) {
|
||||
this.canPlaying = canPlaying;
|
||||
|
||||
// 如果设置为可播放,且有足够的解码缓冲区,则开始播放
|
||||
if (canPlaying && !this.isPlaying && this.decodedBuffers.length >= this.bufferingThreshold) {
|
||||
this.startPlaying();
|
||||
}
|
||||
}
|
||||
}
|
75
libs/registry/registry/lib/player.ts
Normal file
75
libs/registry/registry/lib/player.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
type VideoPlayerOptions = {
|
||||
url?: string;
|
||||
emitter?: EventEmitter;
|
||||
};
|
||||
export class VideoPlayer {
|
||||
url?: string;
|
||||
isPlaying = false;
|
||||
audio?: HTMLAudioElement;
|
||||
emitter?: EventEmitter;
|
||||
private endedHandler?: () => void;
|
||||
constructor(opts?: VideoPlayerOptions) {
|
||||
this.url = opts?.url;
|
||||
this.emitter = opts?.emitter || new EventEmitter();
|
||||
}
|
||||
init() {
|
||||
if (!this.emitter) {
|
||||
this.emitter = new EventEmitter();
|
||||
}
|
||||
return this.emitter;
|
||||
}
|
||||
play(url?: string) {
|
||||
if (this.isPlaying) {
|
||||
return { code: 400 };
|
||||
}
|
||||
const playUrl = url || this.url;
|
||||
if (!playUrl) {
|
||||
return { code: 404 };
|
||||
}
|
||||
if (playUrl !== this.url) {
|
||||
this.url = playUrl;
|
||||
}
|
||||
// 创建新的Audio对象前,确保清理之前的资源
|
||||
if (this.audio && this.endedHandler) {
|
||||
this.audio.removeEventListener('ended', this.endedHandler);
|
||||
}
|
||||
|
||||
this.audio = new Audio(playUrl);
|
||||
this.audio.play();
|
||||
this.isPlaying = true;
|
||||
this.emitter?.emit('start', { url: playUrl, status: 'start' });
|
||||
|
||||
// 保存引用以便于后续移除
|
||||
this.endedHandler = () => {
|
||||
this.audio = undefined;
|
||||
this.isPlaying = false;
|
||||
this.emitter?.emit('stop', this.url);
|
||||
};
|
||||
|
||||
this.audio.addEventListener('ended', this.endedHandler);
|
||||
return { code: 200 };
|
||||
}
|
||||
stop() {
|
||||
if (this.isPlaying) {
|
||||
// 移除事件监听器
|
||||
if (this.audio && this.endedHandler) {
|
||||
this.audio.removeEventListener('ended', this.endedHandler);
|
||||
}
|
||||
this.audio?.pause();
|
||||
this.audio = undefined;
|
||||
this.isPlaying = false;
|
||||
}
|
||||
this.emitter?.emit('stop', this.url);
|
||||
}
|
||||
onStop(callback: (url: string) => void) {
|
||||
this.emitter?.on('stop', callback);
|
||||
return () => {
|
||||
this.emitter?.off('stop', callback);
|
||||
};
|
||||
}
|
||||
close() {
|
||||
this.emitter?.removeAllListeners();
|
||||
this.emitter = undefined;
|
||||
}
|
||||
}
|
21
libs/registry/registry/lib/random.ts
Normal file
21
libs/registry/registry/lib/random.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
export const letter = 'abcdefghijklmnopqrstuvwxyz';
|
||||
export const number = '0123456789';
|
||||
const alphanumeric = `${letter}${number}`;
|
||||
export const alphanumericWithDash = `${alphanumeric}-`;
|
||||
export const uuid = customAlphabet(letter);
|
||||
|
||||
export const nanoid = customAlphabet(alphanumeric, 10);
|
||||
|
||||
export const nanoidWithDash = customAlphabet(alphanumericWithDash, 10);
|
||||
|
||||
/**
|
||||
* 创建一个随机的 id,以字母开头的字符串
|
||||
* @param number
|
||||
* @returns
|
||||
*/
|
||||
export const randomId = (number: number) => {
|
||||
const _letter = uuid(1);
|
||||
return `${_letter}${nanoid(number)}`;
|
||||
};
|
6
libs/registry/registry/lib/utils.ts
Normal file
6
libs/registry/registry/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
4
libs/registry/registry/modules/basename.ts
Normal file
4
libs/registry/registry/modules/basename.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// @ts-ignore
|
||||
export const basename = BASE_NAME;
|
||||
|
||||
console.log(basename);
|
13
libs/registry/registry/modules/toast/Provider.tsx
Normal file
13
libs/registry/registry/modules/toast/Provider.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
|
||||
type ToastProviderProps = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
export const ToastProvider = ({ children }: ToastProviderProps) => {
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<ToastContainer />
|
||||
</>
|
||||
);
|
||||
};
|
52
libs/registry/registry/modules/toast/ToastLogin.tsx
Normal file
52
libs/registry/registry/modules/toast/ToastLogin.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { toast } from 'react-toastify';
|
||||
// Custom message component
|
||||
const LoginMessage = (props: ToastLoginProps) => {
|
||||
const lng = props.lng || 'zh';
|
||||
const isZH = lng === 'zh';
|
||||
const en = {
|
||||
'Please login': 'Please login',
|
||||
'Click here to go to the login page.': 'Click here to go to the login page.',
|
||||
};
|
||||
const zh = {
|
||||
'Please login': '请登录',
|
||||
'Click here to go to the login page.': '点击这里前往登录页面',
|
||||
};
|
||||
const t = isZH ? zh : en;
|
||||
const handleClick = () => {
|
||||
const currentUrl = window.location.href;
|
||||
const redirect = encodeURIComponent(props?.redirectUrl || currentUrl);
|
||||
const loginUrl = props?.loginUrl || '/user/login/';
|
||||
const newUrl = location.origin + loginUrl + '?redirect=' + redirect;
|
||||
window.open(newUrl, '_self');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='msg-container' onClick={handleClick} style={{ cursor: 'pointer' }}>
|
||||
<p className='msg-title'>{t['Please login']}</p>
|
||||
<p className='msg-description'>{t['Click here to go to the login page.']}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
type ToastLoginProps = {
|
||||
/**
|
||||
* 登录页面地址, /user/login
|
||||
*/
|
||||
loginUrl?: string;
|
||||
/**
|
||||
* 登录成功后跳转的地址, 默认是当前页面
|
||||
*/
|
||||
redirectUrl?: string;
|
||||
lng?: 'en' | 'zh';
|
||||
};
|
||||
/**
|
||||
* 登录提示
|
||||
* @param props
|
||||
* @example
|
||||
* toastLogin({
|
||||
* loginUrl: '/user/login/',
|
||||
* redirectUrl: window.location.href,
|
||||
* });
|
||||
*/
|
||||
export const toastLogin = (props: ToastLoginProps = {}) => {
|
||||
toast.info(<LoginMessage {...props} />);
|
||||
};
|
119
libs/registry/registry/styles/global.css
Normal file
119
libs/registry/registry/styles/global.css
Normal file
@@ -0,0 +1,119 @@
|
||||
@import 'tailwindcss';
|
||||
@import "tw-animate-css";
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
78
libs/registry/registry/styles/theme.css
Normal file
78
libs/registry/registry/styles/theme.css
Normal file
@@ -0,0 +1,78 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@theme {
|
||||
--color-primary: #ffc107;
|
||||
--color-secondary: #ffa000;
|
||||
--color-text-primary: #000000;
|
||||
--color-text-secondary: #000000;
|
||||
--color-success: #28a745;
|
||||
--color-scrollbar-thumb: #999999;
|
||||
--color-scrollbar-track: rgba(0, 0, 0, 0.1);
|
||||
--color-scrollbar-thumb-hover: #666666;
|
||||
--scrollbar-color: #ffc107; /* 滚动条颜色 */
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 16px;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
/* font-family */
|
||||
@utility font-family-mon {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
@utility font-family-rob {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
@utility font-family-int {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
@utility font-family-orb {
|
||||
font-family: 'Orbitron', sans-serif;
|
||||
}
|
||||
@utility font-family-din {
|
||||
font-family: 'DIN', sans-serif;
|
||||
}
|
||||
|
||||
@utility flex-row-center {
|
||||
@apply flex flex-row items-center justify-center;
|
||||
}
|
||||
@utility flex-col-center {
|
||||
@apply flex flex-col items-center justify-center;
|
||||
}
|
||||
|
||||
@utility scrollbar {
|
||||
overflow: auto;
|
||||
/* 整个滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: var(--color-scrollbar-track);
|
||||
}
|
||||
/* 滚动条有滑块的轨道部分 */
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background-color: transparent;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块(竖向:vertical 横向:horizontal) */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
cursor: pointer;
|
||||
background-color: var(--color-scrollbar-thumb);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块hover */
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--color-scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
/* 同时有垂直和水平滚动条时交汇的部分 */
|
||||
&::-webkit-scrollbar-corner {
|
||||
display: block; /* 修复交汇时出现的白块 */
|
||||
}
|
||||
}
|
14
libs/registry/src/content.config.ts
Normal file
14
libs/registry/src/content.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// @ts-ignore
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
import { glob, file } from 'astro/loaders'; // 不适用于旧版 API
|
||||
|
||||
const blog = defineCollection({
|
||||
loader: glob({ pattern: '**/*.md', base: './src/data/blog' }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
// pubDate: z.coerce.date(),
|
||||
// updatedDate: z.coerce.date().optional(),
|
||||
}),
|
||||
});
|
||||
export const collections = { blog };
|
5
libs/registry/src/data/blog/1.md
Normal file
5
libs/registry/src/data/blog/1.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: My Blog Post
|
||||
description: This is a post on My Blog about win-win survival strategies.
|
||||
---
|
||||
Your blog post content here.
|
10
libs/registry/src/data/blog/shadcn-init.md
Normal file
10
libs/registry/src/data/blog/shadcn-init.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: test dir
|
||||
description: This is a post on My Blog about win-win survival strategies.
|
||||
---
|
||||
|
||||
# Download shadcn
|
||||
|
||||
```sh
|
||||
pnpm dlx shadcn@latest add https://kevisual.xiongxiao.me/root/registry/r/basename.json
|
||||
```
|
6
libs/registry/src/lib/utils.ts
Normal file
6
libs/registry/src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
20
libs/registry/src/modules/demo/ShowLogin.tsx
Normal file
20
libs/registry/src/modules/demo/ShowLogin.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { toastLogin } from '@/registry/modules/toast/ToastLogin';
|
||||
import { ToastProvider } from '@/registry/modules/toast/Provider';
|
||||
import { Button } from '@/registry/components/b/button/button';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import '@/registry/astro/i18n'; // 初始化i18n
|
||||
|
||||
export const ShowLogin = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<ToastProvider>
|
||||
<Button
|
||||
onClick={() => {
|
||||
toastLogin();
|
||||
console.log('clicked');
|
||||
}}>
|
||||
{t('welcome')} Click me
|
||||
</Button>
|
||||
</ToastProvider>
|
||||
);
|
||||
};
|
23
libs/registry/src/pages/demo/[id].astro
Normal file
23
libs/registry/src/pages/demo/[id].astro
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
import { getCollection, render } from 'astro:content';
|
||||
import Main from '@/registry/astro/layouts/mdx/main.astro';
|
||||
// 1. 为每个集合条目生成一个新路径
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection('blog');
|
||||
return posts.map((post) => ({
|
||||
params: { id: post.id },
|
||||
props: { post },
|
||||
}));
|
||||
}
|
||||
type Post = {
|
||||
data: { title: string };
|
||||
};
|
||||
// 2. 对于你的模板,你可以直接从 prop 获取条目
|
||||
const { post } = Astro.props as { post: Post };
|
||||
const { Content } = await render(post);
|
||||
---
|
||||
|
||||
<Main>
|
||||
<h1 slot={'header'}>{post.data.title}</h1>
|
||||
<Content />
|
||||
</Main>
|
17
libs/registry/src/pages/demo/index.astro
Normal file
17
libs/registry/src/pages/demo/index.astro
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
const posts = await getCollection('blog');
|
||||
console.log('post', posts);
|
||||
import { basename } from '@/registry/modules/basename';
|
||||
---
|
||||
|
||||
<h1>My posts</h1>
|
||||
<ul>
|
||||
{
|
||||
posts.map((post) => (
|
||||
<li>
|
||||
<a href={`${basename}/demo/${post.id}`}>{post.data.title}</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
76
libs/registry/src/pages/examples/markdown.md
Normal file
76
libs/registry/src/pages/examples/markdown.md
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: Markdown
|
||||
layout: '@/registry/astro/layouts/mdx/main.astro'
|
||||
---
|
||||
|
||||
## Heading 2
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed eu ante elementum, ultrices massa ut, ornare lacus. Praesent iaculis, ex ac pellentesque malesuada, arcu mi imperdiet purus, et molestie neque leo quis felis. Nunc et odio bibendum, vestibulum elit sit amet, viverra lorem.
|
||||
|
||||
### Heading 3
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed eu ante elementum, ultrices massa ut, ornare lacus. Praesent iaculis, ex ac pellentesque malesuada, arcu mi imperdiet purus, et molestie neque leo quis felis. Nunc et odio bibendum, vestibulum elit sit amet, viverra lorem.
|
||||
|
||||
#### Heading 4
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed eu ante elementum, ultrices massa ut, ornare lacus. Praesent iaculis, ex ac pellentesque malesuada, arcu mi imperdiet purus, et molestie neque leo quis felis. Nunc et odio bibendum, vestibulum elit sit amet, viverra lorem.
|
||||
|
||||
##### Heading 5
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed eu ante elementum, ultrices massa ut, ornare lacus. Praesent iaculis, ex ac pellentesque malesuada, arcu mi imperdiet purus, et molestie neque leo quis felis. Nunc et odio bibendum, vestibulum elit sit amet, viverra lorem.
|
||||
|
||||
###### Heading 6
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed eu ante elementum, ultrices massa ut, ornare lacus. Praesent iaculis, ex ac pellentesque malesuada, arcu mi imperdiet purus, et molestie neque leo quis felis. Nunc et odio bibendum, vestibulum elit sit amet, viverra lorem.
|
||||
|
||||
## Styling text
|
||||
|
||||
Fusce imperdiet, **tellus** ornare tempor _cursus_, tellus ipsum fringilla ~~quam~~, in venenatis neque augue vitae **_turpis_**. Nulla sed neque volutpat, eleifend purus <sub>sit amet</sub>, porttitor nisi. Ut id sodales lorem. Suspendisse <sup>auctor</sup> augue nisl, sed placerat enim porttitor at. Etiam eu ipsum suscipit, egestas ante non, eleifend nulla.
|
||||
|
||||
## Quoting text
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla in enim sem.
|
||||
|
||||
> Mauris dictum augue augue, ut accumsan tellus convallis ut. Nam vitae libero vestibulum, feugiat mi rhoncus, imperdiet dui. In eros diam, sagittis ultrices dolor ac, ultrices cursus sapien. Morbi fringilla porta purus, sed interdum urna luctus vitae.
|
||||
|
||||
## Quoting code
|
||||
|
||||
Curabitur congue ac `enim` id hendrerit.
|
||||
|
||||
```js
|
||||
const foo = 'bar'
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
Fusce tincidunt urna at [ultricies](#_) sollicitudin.
|
||||
|
||||
## Lists
|
||||
|
||||
- Lorem
|
||||
- Ipsum
|
||||
- Dolor
|
||||
- Sit
|
||||
- Amet
|
||||
1. Consectetur
|
||||
2. Adipiscing
|
||||
|
||||
## Tables
|
||||
|
||||
| Column 1 | Column 2 | Column 3 | Column 4 |
|
||||
| -------- | -------- | -------- | -------- |
|
||||
| Cell 1-1 | Cell 1-2 | Cell 1-3 | Cell 1-4 |
|
||||
| Cell 2-1 | Cell 2-2 | Cell 2-3 | Cell 2-4 |
|
||||
| Cell 3-1 | Cell 3-2 | Cell 3-3 | Cell 3-4 |
|
||||
| Cell 4-1 | Cell 4-2 | Cell 4-3 | Cell 4-4 |
|
||||
| Cell 5-1 | Cell 5-2 | Cell 5-3 | Cell 5-4 |
|
||||
| Cell 6-1 | Cell 6-2 | Cell 6-3 | Cell 6-4 |
|
||||
|
||||
## Details
|
||||
|
||||
<details>
|
||||
<summary>Nullam nec posuere lorem.</summary>
|
||||
|
||||
Aenean tempor, orci eget ullamcorper luctus, nisl turpis pharetra mauris, sit amet tristique elit orci et sem. Aenean odio purus, suscipit quis accumsan in, blandit at ex.
|
||||
|
||||
</details>
|
10
libs/registry/src/pages/index.astro
Normal file
10
libs/registry/src/pages/index.astro
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
import '../styles/global.css';
|
||||
import { ShowLogin } from '@/modules/demo/ShowLogin';
|
||||
const url = new URL(Astro.request.url);
|
||||
---
|
||||
|
||||
<h1 class='text-4xl'>Registry</h1>
|
||||
|
||||
<ShowLogin client:only="react" />
|
||||
|
11
libs/registry/src/pages/shadcn/index.astro
Normal file
11
libs/registry/src/pages/shadcn/index.astro
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
// layout: '@/registry/astro/layouts/mdx/main.astro'
|
||||
import Main from '@/registry/astro/layouts/mdx/main.astro';
|
||||
const baseurl = 'http://localhost:4321/r';
|
||||
---
|
||||
|
||||
<Main>
|
||||
# Download shadcn ```sh pnpm dlx shadcn@latest add ${baseurl}/basename.json ```
|
||||
|
||||
<p>这是默认插槽的内容</p>
|
||||
</Main>
|
119
libs/registry/src/styles/global.css
Normal file
119
libs/registry/src/styles/global.css
Normal file
@@ -0,0 +1,119 @@
|
||||
@import 'tailwindcss';
|
||||
@import "tw-animate-css";
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
22
libs/registry/tsconfig.json
Normal file
22
libs/registry/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "@kevisual/types/json/frontend.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@kevisual"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
],
|
||||
"@/registry/*": [
|
||||
"registry/*"
|
||||
]
|
||||
},
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"registry/**/*"
|
||||
],
|
||||
}
|
@@ -8,5 +8,10 @@
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.10.0",
|
||||
"devDependencies": {
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
}
|
||||
}
|
64
packages/codemirror/.gitignore
vendored
Normal file
64
packages/codemirror/.gitignore
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
node_modules
|
||||
|
||||
# mac
|
||||
.DS_Store
|
||||
|
||||
.env*
|
||||
!.env*example
|
||||
|
||||
dist
|
||||
# build
|
||||
build
|
||||
|
||||
logs
|
||||
|
||||
.turbo
|
||||
|
||||
pack-dist
|
||||
|
||||
# astro
|
||||
.astro
|
||||
|
||||
# next
|
||||
.next
|
||||
|
||||
# nuxt
|
||||
.nuxt
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# vuepress
|
||||
.vuepress/dist
|
||||
|
||||
# coverage
|
||||
coverage/
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
# debug logs
|
||||
*.log
|
||||
*.tmp
|
||||
|
||||
# vscode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# idea
|
||||
.idea
|
||||
|
||||
# system
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# temp files
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# local development
|
||||
*.local
|
2
packages/codemirror/.npmrc
Normal file
2
packages/codemirror/.npmrc
Normal file
@@ -0,0 +1,2 @@
|
||||
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "@kevisual/codemirror",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.12",
|
||||
"description": "",
|
||||
"main": "dist/editor.js",
|
||||
"privite": false,
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"build": "rimraf -rf dist && rollup -c"
|
||||
},
|
||||
@@ -16,19 +16,47 @@
|
||||
],
|
||||
"author": "abearxiong",
|
||||
"license": "ISC",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"codemirror": "^6.0.1",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.18.6",
|
||||
"@codemirror/commands": "^6.8.1",
|
||||
"@codemirror/lang-css": "^6.3.1",
|
||||
"@codemirror/lang-html": "^6.4.9",
|
||||
"@codemirror/lang-javascript": "^6.2.3",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"rollup": "^4.22.2",
|
||||
"tslib": "^2.7.0",
|
||||
"typescript": "^5.6.2"
|
||||
"@codemirror/lang-markdown": "^6.3.2",
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/view": "^6.36.7",
|
||||
"codemirror": "^6.0.1",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"prettier": "^3.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"rollup": "^4.40.2",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"access": "public"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/editor.js",
|
||||
"types": "./dist/editor.d.ts"
|
||||
},
|
||||
"./base": {
|
||||
"import": "./dist/editor.base.js",
|
||||
"types": "./dist/editor.base.d.ts"
|
||||
},
|
||||
"./json": {
|
||||
"import": "./dist/editor.json.js",
|
||||
"types": "./dist/editor.json.d.ts"
|
||||
},
|
||||
"./utils": {
|
||||
"import": "./dist/editor.utils.js",
|
||||
"types": "./dist/editor.utils.d.ts"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
# kevisual codemirror
|
||||
|
||||
```ts
|
||||
import { createEditorInstance } from '@kevisual/codemirror';
|
||||
const editor = createEditorInstance(ref.current!, { typescript: false });
|
||||
editor.dom.style.height = '100%';
|
||||
```
|
||||
|
@@ -1,14 +1,16 @@
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
|
||||
const entrys = ['editor', 'editor.json'];
|
||||
const entrys = ['editor', 'editor.json', 'editor.base', 'editor.utils'];
|
||||
const configs = entrys.map((entry) => ({
|
||||
input: `./src/${entry}.ts`, // 修改输入文件为 TypeScript 文件
|
||||
output: {
|
||||
file: `./dist/${entry}.js`,
|
||||
// file: `./dist/${entry}.js`,
|
||||
dir: './dist',
|
||||
},
|
||||
target: 'browser',
|
||||
plugins: [
|
||||
nodeResolve(),
|
||||
// nodeResolve(),
|
||||
typescript({
|
||||
tsconfig: './tsconfig.json',
|
||||
compilerOptions: {
|
||||
|
92
packages/codemirror/src/editor.base.ts
Normal file
92
packages/codemirror/src/editor.base.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { basicSetup } from 'codemirror';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { StateEffect } from '@codemirror/state';
|
||||
import { ViewUpdate } from '@codemirror/view';
|
||||
import { formatKeymap } from './extensions/tab';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
export type CodeEditor = EditorView & {
|
||||
emitter?: EventEmitter;
|
||||
};
|
||||
let editor: CodeEditor = null;
|
||||
|
||||
export type EditorOptions = {
|
||||
extensions?: any[];
|
||||
hasBasicSetup?: boolean;
|
||||
onChange?: (content: string) => void;
|
||||
};
|
||||
/**
|
||||
* 创建单例
|
||||
* @param el
|
||||
* @returns
|
||||
*/
|
||||
const createEditorInstance = (el?: HTMLDivElement, opts?: EditorOptions) => {
|
||||
if (editor && el) {
|
||||
el.appendChild(editor.dom);
|
||||
return editor;
|
||||
} else if (editor) {
|
||||
return editor;
|
||||
}
|
||||
const extensions = opts?.extensions || [];
|
||||
extensions.push(formatKeymap);
|
||||
const hasBaseicSetup = opts?.hasBasicSetup ?? true;
|
||||
if (hasBaseicSetup) {
|
||||
extensions.unshift(basicSetup);
|
||||
}
|
||||
const emitter = new EventEmitter();
|
||||
createOnChangeListener(emitter, opts.onChange).appendTo(extensions);
|
||||
editor = new EditorView({
|
||||
extensions: extensions,
|
||||
parent: el || document.body,
|
||||
});
|
||||
editor.emitter = emitter;
|
||||
editor.dom.style.height = '100%';
|
||||
return editor as CodeEditor;
|
||||
};
|
||||
|
||||
/**
|
||||
* 每次都创建新的实例
|
||||
* @param el
|
||||
* @returns
|
||||
*/
|
||||
export const createEditor = (el: HTMLDivElement, opts?: EditorOptions) => {
|
||||
const extensions = opts?.extensions || [];
|
||||
const hasBaseicSetup = opts?.hasBasicSetup ?? true;
|
||||
if (hasBaseicSetup) {
|
||||
extensions.unshift(basicSetup);
|
||||
}
|
||||
extensions.push(formatKeymap);
|
||||
const emitter = new EventEmitter();
|
||||
createOnChangeListener(emitter, opts.onChange).appendTo(extensions);
|
||||
const editor = new EditorView({
|
||||
extensions,
|
||||
parent: el || document.body,
|
||||
}) as CodeEditor;
|
||||
|
||||
editor.emitter = emitter;
|
||||
editor.dom.style.height = '100%';
|
||||
|
||||
return editor as CodeEditor;
|
||||
};
|
||||
export const getEditor = () => editor;
|
||||
|
||||
export { editor, createEditorInstance };
|
||||
|
||||
export const createOnChangeListener = (emitter: EventEmitter, callback?: (content: string) => void) => {
|
||||
const listener = EditorView.updateListener.of((update: ViewUpdate) => {
|
||||
if (update.docChanged) {
|
||||
const editor = update.view;
|
||||
if (callback) {
|
||||
callback(editor.state.doc.toString());
|
||||
}
|
||||
// 触发自定义事件
|
||||
emitter.emit('change', editor.state.doc.toString());
|
||||
}
|
||||
});
|
||||
// 返回监听器配置,而不是直接应用它
|
||||
return {
|
||||
extension: listener,
|
||||
appendTo: (ext: any[]) => {
|
||||
ext.push(listener);
|
||||
},
|
||||
};
|
||||
};
|
@@ -1,26 +1,13 @@
|
||||
import { EditorView, basicSetup } from 'codemirror';
|
||||
import { json } from '@codemirror/lang-json';
|
||||
|
||||
let editor: EditorView = null;
|
||||
import * as Base from './editor.base';
|
||||
|
||||
/**
|
||||
* 创建单例
|
||||
* @param el
|
||||
* @returns
|
||||
*/
|
||||
const createEditorInstance = (el?: HTMLDivElement) => {
|
||||
if (editor && el) {
|
||||
el.appendChild(editor.dom);
|
||||
return editor;
|
||||
} else if (editor) {
|
||||
return editor;
|
||||
}
|
||||
editor = new EditorView({
|
||||
extensions: [basicSetup, json()],
|
||||
parent: el || document.body,
|
||||
});
|
||||
editor.dom.style.height = '100%';
|
||||
return editor;
|
||||
export const createEditorInstance = (el?: HTMLDivElement) => {
|
||||
return Base.createEditorInstance(el, { extensions: [json()] });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -29,12 +16,9 @@ const createEditorInstance = (el?: HTMLDivElement) => {
|
||||
* @returns
|
||||
*/
|
||||
export const createEditor = (el: HTMLDivElement) => {
|
||||
const editor = new EditorView({
|
||||
extensions: [basicSetup, json()],
|
||||
parent: el || document.body,
|
||||
});
|
||||
editor.dom.style.height = '100%';
|
||||
return editor;
|
||||
return Base.createEditor(el, { extensions: [json()] });
|
||||
};
|
||||
|
||||
export { editor, createEditorInstance };
|
||||
export { Base };
|
||||
|
||||
export const editor = Base.editor;
|
||||
|
@@ -1,11 +1,27 @@
|
||||
import { EditorView, basicSetup } from 'codemirror';
|
||||
import { basicSetup } from 'codemirror';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { json } from '@codemirror/lang-json';
|
||||
import { html } from '@codemirror/lang-html';
|
||||
import { markdown } from '@codemirror/lang-markdown';
|
||||
import { css } from '@codemirror/lang-css';
|
||||
import { formatKeymap } from './extensions/tab';
|
||||
import { createOnChangeListener } from './editor.base';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
let editor: EditorView = null;
|
||||
export type CodeEditor = EditorView & {
|
||||
emitter?: EventEmitter;
|
||||
};
|
||||
let editor: CodeEditor = null;
|
||||
|
||||
type CreateOpts = {
|
||||
jsx?: boolean;
|
||||
typescript?: boolean;
|
||||
type?: 'javascript' | 'json' | 'html' | 'markdown' | 'css';
|
||||
hasBasicSetup?: boolean;
|
||||
extensions?: any[];
|
||||
hasKeymap?: boolean;
|
||||
onChange?: (content: string) => void;
|
||||
};
|
||||
/**
|
||||
* 创建单例
|
||||
@@ -19,13 +35,42 @@ const createEditorInstance = (el?: HTMLDivElement, opts?: CreateOpts) => {
|
||||
} else if (editor) {
|
||||
return editor;
|
||||
}
|
||||
const { jsx, typescript } = opts || {};
|
||||
const { type = 'javascript' } = opts || {};
|
||||
const extensions = opts?.extensions || [];
|
||||
const hasBaseicSetup = opts?.hasBasicSetup ?? true;
|
||||
const hasKeymap = opts?.hasKeymap ?? true;
|
||||
if (hasBaseicSetup) {
|
||||
extensions.unshift(basicSetup);
|
||||
}
|
||||
if (hasKeymap) {
|
||||
extensions.push(formatKeymap);
|
||||
}
|
||||
switch (type) {
|
||||
case 'json':
|
||||
extensions.push(json());
|
||||
break;
|
||||
case 'javascript':
|
||||
extensions.push(javascript({ jsx: opts?.jsx, typescript: opts?.typescript }));
|
||||
break;
|
||||
case 'css':
|
||||
extensions.push(css());
|
||||
break;
|
||||
case 'html':
|
||||
extensions.push(html());
|
||||
break;
|
||||
case 'markdown':
|
||||
extensions.push(markdown());
|
||||
break;
|
||||
}
|
||||
const emitter = new EventEmitter();
|
||||
createOnChangeListener(emitter, opts.onChange).appendTo(extensions);
|
||||
editor = new EditorView({
|
||||
extensions: [basicSetup, javascript({ jsx, typescript })],
|
||||
extensions: extensions,
|
||||
parent: el || document.body,
|
||||
});
|
||||
editor.dom.style.height = '100%';
|
||||
return editor;
|
||||
editor.emitter = emitter;
|
||||
return editor as CodeEditor;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -34,12 +79,42 @@ const createEditorInstance = (el?: HTMLDivElement, opts?: CreateOpts) => {
|
||||
* @returns
|
||||
*/
|
||||
export const createEditor = (el: HTMLDivElement, opts?: CreateOpts) => {
|
||||
const { type = 'javascript' } = opts || {};
|
||||
const extensions = opts?.extensions || [];
|
||||
const hasBaseicSetup = opts?.hasBasicSetup ?? true;
|
||||
const hasKeymap = opts?.hasKeymap ?? true;
|
||||
if (hasBaseicSetup) {
|
||||
extensions.unshift(basicSetup);
|
||||
}
|
||||
if (hasKeymap) {
|
||||
extensions.push(formatKeymap);
|
||||
}
|
||||
switch (type) {
|
||||
case 'json':
|
||||
extensions.push(json());
|
||||
break;
|
||||
case 'javascript':
|
||||
extensions.push(javascript({ jsx: opts?.jsx, typescript: opts?.typescript }));
|
||||
break;
|
||||
case 'css':
|
||||
extensions.push(css());
|
||||
break;
|
||||
case 'html':
|
||||
extensions.push(html());
|
||||
break;
|
||||
case 'markdown':
|
||||
extensions.push(markdown());
|
||||
break;
|
||||
}
|
||||
const emitter = new EventEmitter();
|
||||
createOnChangeListener(emitter, opts.onChange).appendTo(extensions);
|
||||
const editor = new EditorView({
|
||||
extensions: [basicSetup, javascript({ jsx: opts?.jsx, typescript: opts?.typescript })],
|
||||
extensions: extensions,
|
||||
parent: el || document.body,
|
||||
});
|
||||
}) as CodeEditor;
|
||||
editor.dom.style.height = '100%';
|
||||
return editor;
|
||||
editor.emitter = emitter;
|
||||
return editor as CodeEditor;
|
||||
};
|
||||
|
||||
export { editor, createEditorInstance };
|
||||
|
59
packages/codemirror/src/editor.utils.ts
Normal file
59
packages/codemirror/src/editor.utils.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { CodeEditor } from './editor.base';
|
||||
type ChainOpts = {
|
||||
editor?: CodeEditor;
|
||||
};
|
||||
export class Chain {
|
||||
editor: CodeEditor | EditorView;
|
||||
constructor(opts?: ChainOpts) {
|
||||
this.editor = opts?.editor;
|
||||
}
|
||||
getEditor() {
|
||||
return this.editor;
|
||||
}
|
||||
getContent() {
|
||||
return this.editor?.state.doc.toString() || '';
|
||||
}
|
||||
setContent(content: string) {
|
||||
if (this.editor) {
|
||||
this.editor.dispatch({
|
||||
changes: { from: 0, to: this.editor.state.doc.length, insert: content },
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
setEditor(editor: EditorView) {
|
||||
this.editor = editor;
|
||||
return this;
|
||||
}
|
||||
clearEditor() {
|
||||
if (this.editor) {
|
||||
this.editor.dispatch({
|
||||
changes: { from: 0, to: this.editor.state.doc.length, insert: '' },
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
destroy() {
|
||||
if (this.editor) {
|
||||
this.editor.destroy();
|
||||
this.editor = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
static create(opts?: ChainOpts) {
|
||||
return new Chain(opts);
|
||||
}
|
||||
setOnChange(callback: (content: string) => void) {
|
||||
if (this.editor) {
|
||||
const editor = this.editor as CodeEditor;
|
||||
if (editor.emitter) {
|
||||
editor.emitter.on('change', callback);
|
||||
return () => {
|
||||
editor.emitter.off('change', callback);
|
||||
};
|
||||
}
|
||||
}
|
||||
return () => {};
|
||||
}
|
||||
}
|
56
packages/codemirror/src/extensions/tab.ts
Normal file
56
packages/codemirror/src/extensions/tab.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { EditorView, keymap } from '@codemirror/view';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { defaultKeymap, indentWithTab, insertTab } from '@codemirror/commands';
|
||||
import prettier from 'prettier';
|
||||
// import parserBabel from 'prettier/plugins/babel';
|
||||
import parserEstree from 'prettier/plugins/estree';
|
||||
// import parserHtml from 'prettier/plugins/html';
|
||||
import parserTypescript from 'prettier/plugins/typescript';
|
||||
|
||||
// 格式化函数
|
||||
// Function to format the code using Prettier
|
||||
type FormatCodeOptions = {
|
||||
type: 'typescript';
|
||||
plugins?: any[];
|
||||
};
|
||||
async function formatCode(view: EditorView, opts?: FormatCodeOptions) {
|
||||
const editor = view;
|
||||
const code = editor.state.doc.toString();
|
||||
const plugins = opts?.plugins || [];
|
||||
plugins.push(parserEstree);
|
||||
const parser = opts?.type || 'typescript';
|
||||
if (parser === 'typescript') {
|
||||
plugins.push(parserTypescript);
|
||||
}
|
||||
try {
|
||||
const formattedCode = await prettier.format(code, {
|
||||
parser: parser,
|
||||
plugins: plugins,
|
||||
});
|
||||
|
||||
editor.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: editor.state.doc.length,
|
||||
insert: formattedCode.trim(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error formatting code:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export const formatKeymap = keymap.of([
|
||||
{
|
||||
// bug, 必须小写
|
||||
key: 'alt-shift-f', // 快捷键绑定
|
||||
// mac: 'cmd-shift-f',
|
||||
run: (view) => {
|
||||
formatCode(view);
|
||||
return true; // 表示按键事件被处理
|
||||
},
|
||||
},
|
||||
// indentWithTab, // Tab键自动缩进
|
||||
{ key: 'Tab', run: insertTab }, // 在光标位置插入Tab字符
|
||||
...defaultKeymap, // 默认快捷键
|
||||
]);
|
@@ -4,7 +4,7 @@ import { Fragment, useEffect, useMemo, useState } from 'react';
|
||||
import { useDemoStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { Form } from 'antd';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { EditOutlined, SettingOutlined, LinkOutlined, SaveOutlined, DeleteOutlined, LeftOutlined } from '@ant-design/icons';
|
||||
import clsx from 'clsx';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
@@ -84,7 +84,7 @@ const FormModal = () => {
|
||||
);
|
||||
};
|
||||
export const List = () => {
|
||||
const navicate = useNavigate();
|
||||
const navicate = useNewNavigate();
|
||||
const demoStore = useDemoStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@@ -161,7 +161,13 @@ export const List = () => {
|
||||
icon={<LinkOutlined />}></Button>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
demoStore.deleteData(item.id);
|
||||
Modal.confirm({
|
||||
title: 'Delete',
|
||||
content: 'Are you sure delete this data?',
|
||||
onOk: () => {
|
||||
demoStore.deleteData(item.id);
|
||||
},
|
||||
});
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<DeleteOutlined />}></Button>
|
||||
|
@@ -34,7 +34,7 @@ export const useDemoStore = create<DemoStore>((set, get) => {
|
||||
if (res.code === 200) {
|
||||
set({ list: res.data });
|
||||
} else {
|
||||
message.error(res.msg || 'Request failed');
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
updateData: async (data) => {
|
||||
@@ -49,7 +49,7 @@ export const useDemoStore = create<DemoStore>((set, get) => {
|
||||
set({ showEdit: false, formData: [] });
|
||||
getList();
|
||||
} else {
|
||||
message.error(res.msg || 'Request failed');
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
deleteData: async (id) => {
|
||||
@@ -63,7 +63,7 @@ export const useDemoStore = create<DemoStore>((set, get) => {
|
||||
getList();
|
||||
message.success('Success');
|
||||
} else {
|
||||
message.error(res.msg || 'Request failed');
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
2
packages/tailwind/.npmrc
Normal file
2
packages/tailwind/.npmrc
Normal file
@@ -0,0 +1,2 @@
|
||||
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
40
packages/tailwind/css/card.css
Normal file
40
packages/tailwind/css/card.css
Normal file
@@ -0,0 +1,40 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@utility {
|
||||
.btn {
|
||||
@apply bg-blue-500 text-white font-bold py-2 px-4 rounded;
|
||||
}
|
||||
.card {
|
||||
@apply bg-white shadow-md rounded-lg p-4;
|
||||
.card-title {
|
||||
@apply text-lg font-bold;
|
||||
}
|
||||
.card-subtitle {
|
||||
@apply text-sm text-gray-500;
|
||||
}
|
||||
.card-description {
|
||||
@apply text-gray-700 break-words;
|
||||
}
|
||||
.card-code {
|
||||
@apply bg-gray-100 p-2;
|
||||
}
|
||||
.card-body {
|
||||
@apply text-gray-700;
|
||||
}
|
||||
.card-key {
|
||||
@apply text-xs text-gray-400;
|
||||
}
|
||||
.card-footer {
|
||||
@apply text-sm text-gray-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@utilities {
|
||||
.layout-menu {
|
||||
@apply bg-gray-900 p-2 text-white flex justify-between h-12;
|
||||
}
|
||||
.bg-custom-blue {
|
||||
background-color: #3490dc;
|
||||
}
|
||||
}
|
87
packages/tailwind/css/globals.css
Normal file
87
packages/tailwind/css/globals.css
Normal file
@@ -0,0 +1,87 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@theme {
|
||||
--color-primary: #ffc107;
|
||||
--color-secondary: #ffa000;
|
||||
--color-text-primary: #000000;
|
||||
--color-text-secondary: #000000;
|
||||
--color-success: #28a745;
|
||||
--color-scrollbar-thumb: #999999;
|
||||
--color-scrollbar-track: rgba(0, 0, 0, 0.1);
|
||||
--color-scrollbar-thumb-hover: #666666;
|
||||
--scrollbar-color: #ffc107; /* 滚动条颜色 */
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 16px;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
h1 {
|
||||
@apply text-2xl font-bold;
|
||||
}
|
||||
h2 {
|
||||
@apply text-xl font-bold;
|
||||
}
|
||||
h3 {
|
||||
@apply text-lg font-bold;
|
||||
}
|
||||
}
|
||||
/* font-family */
|
||||
@utility font-family-mon {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
@utility font-family-rob {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
@utility font-family-int {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
@utility font-family-orb {
|
||||
font-family: 'Orbitron', sans-serif;
|
||||
}
|
||||
@utility font-family-din {
|
||||
font-family: 'DIN', sans-serif;
|
||||
}
|
||||
|
||||
@utility flex-row-center {
|
||||
@apply flex flex-row items-center justify-center;
|
||||
}
|
||||
@utility flex-col-center {
|
||||
@apply flex flex-col items-center justify-center;
|
||||
}
|
||||
|
||||
@utility scrollbar {
|
||||
overflow: auto;
|
||||
/* 整个滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: var(--color-scrollbar-track);
|
||||
}
|
||||
/* 滚动条有滑块的轨道部分 */
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background-color: transparent;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块(竖向:vertical 横向:horizontal) */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
cursor: pointer;
|
||||
background-color: var(--color-scrollbar-thumb);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块hover */
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--color-scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
/* 同时有垂直和水平滚动条时交汇的部分 */
|
||||
&::-webkit-scrollbar-corner {
|
||||
display: block; /* 修复交汇时出现的白块 */
|
||||
}
|
||||
}
|
14
packages/tailwind/css/loading.css
Normal file
14
packages/tailwind/css/loading.css
Normal file
@@ -0,0 +1,14 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@utility {
|
||||
.loading {
|
||||
@apply w-full h-full flex justify-center items-center;
|
||||
> div {
|
||||
@apply w-20 h-20 border-t-8 border-b-8 rounded-full animate-spin;
|
||||
}
|
||||
}
|
||||
.loading-sm {
|
||||
@apply w-4 h-4 border-t-2 border-b-2 rounded-full animate-spin;
|
||||
}
|
||||
}
|
||||
|
31
packages/tailwind/css/scrollbar.css
Normal file
31
packages/tailwind/css/scrollbar.css
Normal file
@@ -0,0 +1,31 @@
|
||||
@import 'tailwindcss';
|
||||
@utility .scrollbar {
|
||||
/* 整个滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
/* 滚动条有滑块的轨道部分 */
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background-color: transparent;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块(竖向:vertical 横向:horizontal) */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
cursor: pointer;
|
||||
background-color: black;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块hover */
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #999999;
|
||||
}
|
||||
|
||||
/* 同时有垂直和水平滚动条时交汇的部分 */
|
||||
&::-webkit-scrollbar-corner {
|
||||
display: block; /* 修复交汇时出现的白块 */
|
||||
}
|
||||
}
|
9
packages/tailwind/extends/theme.js
Normal file
9
packages/tailwind/extends/theme.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const extend = {
|
||||
fontFamily: {
|
||||
mon: ['Montserrat', 'sans-serif'], // 定义自定义字体族
|
||||
rob: ['Roboto', 'sans-serif'],
|
||||
int: ['Inter', 'sans-serif'],
|
||||
orb: ['Orbitron', 'sans-serif'],
|
||||
din: ['DIN', 'sans-serif'],
|
||||
},
|
||||
};
|
3
packages/tailwind/index.css
Normal file
3
packages/tailwind/index.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@import "./css/globals.css";
|
||||
@import "./css/loading.css";
|
||||
/* @import "./css/scrollbar.css" */
|
@@ -1,15 +1,28 @@
|
||||
{
|
||||
"name": "@kevisual/tailwind",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.3",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"main": "plugin/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"files": [
|
||||
"plugins"
|
||||
"plugins",
|
||||
"css",
|
||||
"extends",
|
||||
"tailwind.config.js",
|
||||
"src",
|
||||
"index.css",
|
||||
"dist"
|
||||
],
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
"author": "abearxiong",
|
||||
"license": "ISC",
|
||||
"exports": {
|
||||
".": "./plugins/index.js",
|
||||
"./main.css": "./index.css",
|
||||
"./css": "./css/globals.css",
|
||||
"./loading": "./css/loading.css"
|
||||
}
|
||||
}
|
@@ -20,6 +20,7 @@ const flexCenter = plugin(function ({ addUtilities }) {
|
||||
'.card-body': {},
|
||||
'.card-footer': {},
|
||||
'.card-key': {},
|
||||
'.loading': {},
|
||||
});
|
||||
});
|
||||
|
51
packages/tailwind/readme.md
Normal file
51
packages/tailwind/readme.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# tailwind 收集模块
|
||||
|
||||
```mjs
|
||||
import path from 'path';
|
||||
|
||||
const root = path.resolve(process.cwd());
|
||||
const contents = ['./src/**/*.{ts,tsx,html}', './src/**/*.css'];
|
||||
const content = contents.map((item) => path.join(root, item));
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
darkMode: ['class'],
|
||||
content: content,
|
||||
plugins: [
|
||||
require('@tailwindcss/aspect-ratio'), //
|
||||
require('@tailwindcss/typography'),
|
||||
require('tailwindcss-animate'),
|
||||
require('./plugins/index'),
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
mon: ['Montserrat', 'sans-serif'], // 定义自定义字体族
|
||||
rob: ['Roboto', 'sans-serif'],
|
||||
int: ['Inter', 'sans-serif'],
|
||||
orb: ['Orbitron', 'sans-serif'],
|
||||
din: ['DIN', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
screen: {
|
||||
sm: '640px',
|
||||
// => @media (min-width: 640px) { ... }
|
||||
|
||||
md: '768px',
|
||||
// => @media (min-width: 768px) { ... }
|
||||
|
||||
lg: '1024px',
|
||||
// => @media (min-width: 1024px) { ... }
|
||||
|
||||
xl: '1280px',
|
||||
// => @media (min-width: 1280px) { ... }
|
||||
|
||||
'2xl': '1536px',
|
||||
// => @media (min-width: 1536px) { ... }
|
||||
'3xl': '1920px',
|
||||
// => @media (min-width: 1920) { ... }
|
||||
'4xl': '2560px',
|
||||
// => @media (min-width: 2560) { ... }
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
47
packages/tailwind/tailwind.config.js
Normal file
47
packages/tailwind/tailwind.config.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import path from 'path';
|
||||
|
||||
const root = path.resolve(process.cwd());
|
||||
const contents = ['./src/**/*.{ts,tsx,html}', './src/**/*.css']
|
||||
const content = contents.map((item) => path.join(root, item));
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
darkMode: ['class'],
|
||||
content: content,
|
||||
plugins: [
|
||||
require('@tailwindcss/aspect-ratio'), //
|
||||
require('@tailwindcss/typography'),
|
||||
require('tailwindcss-animate'),
|
||||
require('./plugins/index'),
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
mon: ['Montserrat', 'sans-serif'], // 定义自定义字体族
|
||||
rob: ['Roboto', 'sans-serif'],
|
||||
int: ['Inter', 'sans-serif'],
|
||||
orb: ['Orbitron', 'sans-serif'],
|
||||
din: ['DIN', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
screen: {
|
||||
sm: '640px',
|
||||
// => @media (min-width: 640px) { ... }
|
||||
|
||||
md: '768px',
|
||||
// => @media (min-width: 768px) { ... }
|
||||
|
||||
lg: '1024px',
|
||||
// => @media (min-width: 1024px) { ... }
|
||||
|
||||
xl: '1280px',
|
||||
// => @media (min-width: 1280px) { ... }
|
||||
|
||||
'2xl': '1536px',
|
||||
// => @media (min-width: 1536px) { ... }
|
||||
'3xl': '1920px',
|
||||
// => @media (min-width: 1920) { ... }
|
||||
'4xl': '2560px',
|
||||
// => @media (min-width: 2560) { ... }
|
||||
},
|
||||
},
|
||||
};
|
@@ -15,39 +15,39 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@abearxiong/ui": "0.0.1-alpha.0",
|
||||
"@kevisual/codemirror": "workspace:^",
|
||||
"@kevisual/ui": "workspace:^",
|
||||
"antd": "^5.20.6",
|
||||
"antd": "^5.25.1",
|
||||
"clsx": "^2.1.1",
|
||||
"immer": "^10.1.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^5.0.7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router": "^6.26.2",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"react-toastify": "^10.0.5",
|
||||
"zustand": "^4.5.5"
|
||||
"nanoid": "^5.1.5",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router": "^7.6.0",
|
||||
"react-router-dom": "^7.6.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"zustand": "^5.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.10.0",
|
||||
"@eslint/js": "^9.26.0",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@types/node": "^22.5.5",
|
||||
"@types/react": "^18.3.8",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.10.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.12",
|
||||
"globals": "^15.9.0",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tailwindcss": "^3.4.12",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tailwindcss/vite": "^4.1.6",
|
||||
"@types/node": "^22.15.17",
|
||||
"@types/react": "^19.1.3",
|
||||
"@types/react-dom": "^19.1.3",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"eslint": "^9.26.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.1.0",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"tailwindcss": "^4.1.6",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5.6.2",
|
||||
"typescript-eslint": "^8.6.0",
|
||||
"vite": "^5.4.6"
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.32.0",
|
||||
"vite": "^6.3.5"
|
||||
}
|
||||
}
|
@@ -13,8 +13,8 @@ export const App = () => {
|
||||
}}>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path='/' element={<Navigate to='/model/' />} />
|
||||
<Route path='/modal/*' element={<FlowApps />} />
|
||||
<Route path='/' element={<Navigate to='/modal/' />} />
|
||||
{/* <Route path='/modal/*' element={<FlowApps />} /> */}
|
||||
<Route path='/codemirror/*' element={<CodeMirrorApp />} />
|
||||
<Route path='/404' element={<div>404</div>} />
|
||||
<Route path='*' element={<div>404</div>} />
|
||||
|
@@ -1,15 +1 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
h1 {
|
||||
@apply text-2xl font-bold;
|
||||
}
|
||||
h2 {
|
||||
@apply text-xl font-bold;
|
||||
}
|
||||
h3 {
|
||||
@apply text-lg font-bold;
|
||||
}
|
||||
}
|
||||
@import 'tailwindcss';
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { App } from './App.tsx';
|
||||
// import './tailwind.css';
|
||||
const App2 = () => {
|
||||
return <div>hello</div>;
|
||||
};
|
||||
import './globals.css';
|
||||
import './index.css';
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { createEditorInstance } from '@kevisual/codemirror/dist/editor.json';
|
||||
import { createEditor } from '@kevisual/codemirror/json';
|
||||
import { useEffect, useRef } from 'react';
|
||||
export const App = () => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
@@ -6,7 +6,7 @@ export const App = () => {
|
||||
init();
|
||||
}, []);
|
||||
const init = () => {
|
||||
const editor = createEditorInstance(ref.current!);
|
||||
const editor = createEditor(ref.current!);
|
||||
editor.dom.style.height = '100%';
|
||||
};
|
||||
return (
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user