update temp

This commit is contained in:
熊潇 2025-05-18 17:58:10 +08:00
parent cd3faff5d2
commit 10fb591405
33 changed files with 2695 additions and 53 deletions

View File

@ -20,7 +20,7 @@ jobs:
docker run --rm \
-v ${{ github.workspace }}:${{ github.workspace }} \
-w ${{ github.workspace }} \
-e PLUGIN_TARGET_URL="https://cnb.cool/kevisual/astro-template.git" \
-e PLUGIN_TARGET_URL="https://cnb.cool/kevisual/assistant-shop.git" \
-e PLUGIN_AUTH_TYPE="https" \
-e PLUGIN_USERNAME="cnb" \
-e PLUGIN_PASSWORD=${{ secrets.GIT_PASSWORD }} \

View File

@ -7,12 +7,13 @@ import tailwindcss from '@tailwindcss/vite';
import basicSsl from '@vitejs/plugin-basic-ssl';
const isDev = process.env.NODE_ENV === 'development';
const plugins = [tailwindcss()]
const plugins = [tailwindcss()];
const isCNB = process.env.CNB === 'true';
if (isDev && !isCNB) {
plugins.push(basicSsl());
}
let target = process.env.VITE_API_URL || 'http://localhost:3000';
let target = process.env.VITE_API_URL || 'https://localhost:51015';
const apiProxy = { target: target, changeOrigin: true, ws: true, rewriteWsOrigin: true, secure: false, cookieDomainRewrite: 'localhost' };
let proxy = {
'/root/center/': {
target: `${target}/root/center/`,
@ -20,13 +21,8 @@ let proxy = {
'/user/login/': {
target: `${target}/user/login/`,
},
'/api': {
target: target,
changeOrigin: true,
ws: true,
rewriteWsOrigin: true,
cookieDomainRewrite: 'localhost',
},
'/api': apiProxy,
'/client': apiProxy,
};
export default defineConfig({

View File

@ -1,14 +1,14 @@
{
"name": "@kevisual/astro-template",
"name": "@kevisual/assistant-shop",
"version": "0.0.1",
"description": "",
"main": "index.js",
"basename": "root/astro-template",
"basename": "/root/assistant-shop",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"pub": "envision deploy ./dist -k vite-react -v 0.0.1",
"pub": "envision deploy ./dist -k assistant-shop -v 0.0.1 -u",
"git:submodule": "git submodule update --init --recursive",
"sn": "pnpm dlx shadcn@latest add "
},
@ -23,6 +23,8 @@
"@kevisual/query": "^0.0.18",
"@kevisual/query-login": "^0.0.5",
"@kevisual/registry": "^0.0.1",
"@radix-ui/react-alert-dialog": "^1.1.13",
"@radix-ui/react-slot": "^1.2.2",
"@tailwindcss/vite": "^4.1.7",
"astro": "^5.7.13",
"class-variance-authority": "^0.7.1",
@ -42,6 +44,7 @@
},
"devDependencies": {
"@kevisual/query-awesome": "^0.0.2",
"@kevisual/router": "^0.0.20",
"@kevisual/types": "^0.0.10",
"@types/react": "^19.1.4",
"@types/react-dom": "^19.1.5",

503
pnpm-lock.yaml generated
View File

@ -26,6 +26,12 @@ importers:
'@kevisual/registry':
specifier: ^0.0.1
version: 0.0.1(typescript@5.8.3)
'@radix-ui/react-alert-dialog':
specifier: ^1.1.13
version: 1.1.13(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-slot':
specifier: ^1.2.2
version: 1.2.2(@types/react@19.1.4)(react@19.1.0)
'@tailwindcss/vite':
specifier: ^4.1.7
version: 4.1.7(vite@6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1))
@ -69,6 +75,9 @@ importers:
'@kevisual/query-awesome':
specifier: ^0.0.2
version: 0.0.2
'@kevisual/router':
specifier: ^0.0.20
version: 0.0.20
'@kevisual/types':
specifier: ^0.0.10
version: 0.0.10
@ -525,6 +534,9 @@ packages:
'@kevisual/registry@0.0.1':
resolution: {integrity: sha512-//OHu9m4JDrMjgP8o8dcjZd3D3IAUkRVlkTSviouZEH7r5m7mccA3Hvzw0XJ/lelx6exC6LWsyv6c4uV0Dp+gw==}
'@kevisual/router@0.0.20':
resolution: {integrity: sha512-uSwDYWh+kvAu6i0m0SJVgcLR/CYz7WvIWGz0nSF8Vg6smJuAgI+laHR4ESO8Fbz+Xn8bPHuSwmM//HHLMLx2FA==}
'@kevisual/types@0.0.10':
resolution: {integrity: sha512-Q73uzzjk9UidumnmCvOpgzqDDvQxsblz22bIFuoiioUFJWwaparx8bpd8ArRyFojicYL1YJoFDzDZ9j9NN8grA==}
@ -534,6 +546,190 @@ packages:
'@oslojs/encoding@1.1.0':
resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
'@radix-ui/primitive@1.1.2':
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
'@radix-ui/react-alert-dialog@1.1.13':
resolution: {integrity: sha512-/uPs78OwxGxslYOG5TKeUsv9fZC0vo376cXSADdKirTmsLJU2au6L3n34c3p6W26rFDDDze/hwy4fYeNd0qdGA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-compose-refs@1.1.2':
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-context@1.1.2':
resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-dialog@1.1.13':
resolution: {integrity: sha512-ARFmqUyhIVS3+riWzwGTe7JLjqwqgnODBUZdqpWar/z1WFs9z76fuOs/2BOWCR+YboRn4/WN9aoaGVwqNRr8VA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-dismissable-layer@1.1.9':
resolution: {integrity: sha512-way197PiTvNp+WBP7svMJasHl+vibhWGQDb6Mgf5mhEWJkgb85z7Lfl9TUdkqpWsf8GRNmoopx9ZxCyDzmgRMQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-focus-guards@1.1.2':
resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-focus-scope@1.1.6':
resolution: {integrity: sha512-r9zpYNUQY+2jWHWZGyddQLL9YHkM/XvSFHVcWs7bdVuxMAnCwTAuy6Pf47Z4nw7dYcUou1vg/VgjjrrH03VeBw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-id@1.1.1':
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-portal@1.1.8':
resolution: {integrity: sha512-hQsTUIn7p7fxCPvao/q6wpbxmCwgLrlz+nOrJgC+RwfZqWY/WN+UMqkXzrtKbPrF82P43eCTl3ekeKuyAQbFeg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-presence@1.1.4':
resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-primitive@2.1.2':
resolution: {integrity: sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slot@1.2.2':
resolution: {integrity: sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-callback-ref@1.1.1':
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-controllable-state@1.2.2':
resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-effect-event@0.0.2':
resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-escape-keydown@1.1.1':
resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-layout-effect@1.1.1':
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@rollup/plugin-commonjs@28.0.3':
resolution: {integrity: sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==}
engines: {node: '>=16.0.0 || 14 >= 14.17'}
@ -830,6 +1026,9 @@ packages:
'@types/node-fetch@2.6.12':
resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==}
'@types/node-forge@1.3.11':
resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==}
'@types/node@17.0.45':
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
@ -914,6 +1113,10 @@ packages:
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
aria-hidden@1.2.5:
resolution: {integrity: sha512-N+u63/2br5AG+OSrk0rIgFOYPSEYlkD7Yk5fq3LDole6QDIAbAFpbchE5fhBiUdRV2Fa8pWAaXvy+VK/maBeTA==}
engines: {node: '>=10'}
aria-query@5.3.2:
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
engines: {node: '>= 0.4'}
@ -972,6 +1175,9 @@ packages:
caniuse-lite@1.0.30001715:
resolution: {integrity: sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==}
caniuse-lite@1.0.30001718:
resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==}
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
@ -1120,6 +1326,9 @@ packages:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
detect-node-es@1.1.0:
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
deterministic-object-hash@2.0.2:
resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==}
engines: {node: '>=18'}
@ -1293,6 +1502,10 @@ packages:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
get-nonce@1.0.1:
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
engines: {node: '>=6'}
get-proto@1.0.1:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
@ -1807,6 +2020,10 @@ packages:
encoding:
optional: true
node-forge@1.3.1:
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
engines: {node: '>= 6.13.0'}
node-mock-http@1.0.0:
resolution: {integrity: sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==}
@ -1871,6 +2088,10 @@ packages:
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
path-to-regexp@8.2.0:
resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==}
engines: {node: '>=16'}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -1928,6 +2149,36 @@ packages:
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
engines: {node: '>=0.10.0'}
react-remove-scroll-bar@2.3.8:
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
react-remove-scroll@2.7.0:
resolution: {integrity: sha512-sGsQtcjMqdQyijAHytfGEELB8FufGbfXIsvUTe+NLx1GDRJCXtCFLBLUI1eyZCKXXvbEU2C6gai0PZKoIE9Vbg==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
react-style-singleton@2.2.3:
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
react-toastify@11.0.5:
resolution: {integrity: sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==}
peerDependencies:
@ -2038,6 +2289,10 @@ packages:
scheduler@0.26.0:
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
selfsigned@2.4.1:
resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==}
engines: {node: '>=10'}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@ -2289,6 +2544,26 @@ packages:
peerDependencies:
browserslist: '>= 4.21.0'
use-callback-ref@1.3.3:
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
use-sidecar@1.1.3:
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
vfile-location@5.0.3:
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
@ -2887,6 +3162,11 @@ snapshots:
- react-native
- typescript
'@kevisual/router@0.0.20':
dependencies:
path-to-regexp: 8.2.0
selfsigned: 2.4.1
'@kevisual/types@0.0.10': {}
'@mdx-js/mdx@3.1.0(acorn@8.14.1)':
@ -2921,6 +3201,163 @@ snapshots:
'@oslojs/encoding@1.1.0': {}
'@radix-ui/primitive@1.1.2': {}
'@radix-ui/react-alert-dialog@1.1.13(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
'@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
'@radix-ui/react-dialog': 1.1.13(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-slot': 1.2.2(@types/react@19.1.4)(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.4
'@types/react-dom': 19.1.5(@types/react@19.1.4)
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.4)(react@19.1.0)':
dependencies:
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.4
'@radix-ui/react-context@1.1.2(@types/react@19.1.4)(react@19.1.0)':
dependencies:
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.4
'@radix-ui/react-dialog@1.1.13(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
'@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
'@radix-ui/react-dismissable-layer': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.4)(react@19.1.0)
'@radix-ui/react-focus-scope': 1.1.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
'@radix-ui/react-portal': 1.1.8(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-slot': 1.2.2(@types/react@19.1.4)(react@19.1.0)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
aria-hidden: 1.2.5
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
react-remove-scroll: 2.7.0(@types/react@19.1.4)(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.4
'@types/react-dom': 19.1.5(@types/react@19.1.4)
'@radix-ui/react-dismissable-layer@1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
'@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.4)(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.4
'@types/react-dom': 19.1.5(@types/react@19.1.4)
'@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.4)(react@19.1.0)':
dependencies:
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.4
'@radix-ui/react-focus-scope@1.1.6(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
'@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.4
'@types/react-dom': 19.1.5(@types/react@19.1.4)
'@radix-ui/react-id@1.1.1(@types/react@19.1.4)(react@19.1.0)':
dependencies:
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.4
'@radix-ui/react-portal@1.1.8(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.4
'@types/react-dom': 19.1.5(@types/react@19.1.4)
'@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.4
'@types/react-dom': 19.1.5(@types/react@19.1.4)
'@radix-ui/react-primitive@2.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/react-slot': 1.2.2(@types/react@19.1.4)(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.4
'@types/react-dom': 19.1.5(@types/react@19.1.4)
'@radix-ui/react-slot@1.2.2(@types/react@19.1.4)(react@19.1.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.4
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.4)(react@19.1.0)':
dependencies:
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.4
'@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.4)(react@19.1.0)':
dependencies:
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.4)(react@19.1.0)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.4
'@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.4)(react@19.1.0)':
dependencies:
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.4
'@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.4)(react@19.1.0)':
dependencies:
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.4
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.4)(react@19.1.0)':
dependencies:
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.4
'@rollup/plugin-commonjs@28.0.3(rollup@4.40.1)':
dependencies:
'@rollup/pluginutils': 5.1.4(rollup@4.40.1)
@ -3184,6 +3621,10 @@ snapshots:
'@types/node': 18.19.87
form-data: 4.0.2
'@types/node-forge@1.3.11':
dependencies:
'@types/node': 18.19.87
'@types/node@17.0.45': {}
'@types/node@18.19.87':
@ -3258,6 +3699,11 @@ snapshots:
argparse@2.0.1: {}
aria-hidden@1.2.5:
dependencies:
caniuse-lite: 1.0.30001718
tslib: 2.8.1
aria-query@5.3.2: {}
array-iterate@2.0.1: {}
@ -3406,6 +3852,8 @@ snapshots:
caniuse-lite@1.0.30001715: {}
caniuse-lite@1.0.30001718: {}
ccount@2.0.1: {}
chalk@5.4.1: {}
@ -3523,6 +3971,8 @@ snapshots:
detect-libc@2.0.4: {}
detect-node-es@1.1.0: {}
deterministic-object-hash@2.0.2:
dependencies:
base-64: 1.0.0
@ -3727,6 +4177,8 @@ snapshots:
hasown: 2.0.2
math-intrinsics: 1.1.0
get-nonce@1.0.1: {}
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
@ -4534,6 +4986,8 @@ snapshots:
dependencies:
whatwg-url: 5.0.0
node-forge@1.3.1: {}
node-mock-http@1.0.0: {}
node-releases@2.0.19: {}
@ -4610,6 +5064,8 @@ snapshots:
path-parse@1.0.7: {}
path-to-regexp@8.2.0: {}
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@ -4652,6 +5108,33 @@ snapshots:
react-refresh@0.17.0: {}
react-remove-scroll-bar@2.3.8(@types/react@19.1.4)(react@19.1.0):
dependencies:
react: 19.1.0
react-style-singleton: 2.2.3(@types/react@19.1.4)(react@19.1.0)
tslib: 2.8.1
optionalDependencies:
'@types/react': 19.1.4
react-remove-scroll@2.7.0(@types/react@19.1.4)(react@19.1.0):
dependencies:
react: 19.1.0
react-remove-scroll-bar: 2.3.8(@types/react@19.1.4)(react@19.1.0)
react-style-singleton: 2.2.3(@types/react@19.1.4)(react@19.1.0)
tslib: 2.8.1
use-callback-ref: 1.3.3(@types/react@19.1.4)(react@19.1.0)
use-sidecar: 1.1.3(@types/react@19.1.4)(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.4
react-style-singleton@2.2.3(@types/react@19.1.4)(react@19.1.0):
dependencies:
get-nonce: 1.0.1
react: 19.1.0
tslib: 2.8.1
optionalDependencies:
'@types/react': 19.1.4
react-toastify@11.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
clsx: 2.1.1
@ -4856,6 +5339,11 @@ snapshots:
scheduler@0.26.0: {}
selfsigned@2.4.1:
dependencies:
'@types/node-forge': 1.3.11
node-forge: 1.3.1
semver@6.3.1: {}
semver@7.7.1: {}
@ -5097,6 +5585,21 @@ snapshots:
escalade: 3.2.0
picocolors: 1.1.1
use-callback-ref@1.3.3(@types/react@19.1.4)(react@19.1.0):
dependencies:
react: 19.1.0
tslib: 2.8.1
optionalDependencies:
'@types/react': 19.1.4
use-sidecar@1.1.3(@types/react@19.1.4)(react@19.1.0):
dependencies:
detect-node-es: 1.1.0
react: 19.1.0
tslib: 2.8.1
optionalDependencies:
'@types/react': 19.1.4
vfile-location@5.0.3:
dependencies:
'@types/unist': 3.0.3

View File

@ -1,4 +1,8 @@
packages:
- packages/*
- apps/*
- submodules/*
- submodules/*
onlyBuiltDependencies:
- '@tailwindcss/oxide'
- esbuild
- sharp

51
src/apps/config/store.ts Normal file
View File

@ -0,0 +1,51 @@
import { create } from 'zustand';
import { clientQuery as client } from '@/modules/query';
import { toast } from 'react-toastify';
type ConfigStore = {
config: any;
setConfig: (config: any) => void;
getConfig: () => Promise<void>;
saveConfig: (config: any) => Promise<void>;
pageApi: string;
setPageApi: (pageApi: string) => void;
pageStoreApi: string;
setPageStoreApi: (pageStoreApi: string) => void;
};
export const useConfigStore = create<ConfigStore>((set) => ({
config: {},
setConfig: (config) => set({ config }),
getConfig: async () => {
const res = await client.post({
path: 'config',
});
if (res.code === 200) {
console.log(res.data);
set({ config: res.data, pageApi: res.data?.pageApi || '', pageStoreApi: res.data?.pageStoreApi || '' });
} else {
toast.error(res.message || '获取配置失败');
}
},
pageApi: '',
setPageApi: (pageApi) => set({ pageApi }),
pageStoreApi: '',
setPageStoreApi: (pageStoreApi) => set({ pageStoreApi }),
saveConfig: async ({ pageApi, loadURL }) => {
console.log(pageApi, loadURL);
if (!pageApi) {
toast.error('配置不能为空');
return;
}
const res = await client.post({
path: 'config',
key: 'set',
data: { pageApi, loadURL },
});
if (res.code === 200) {
toast.success('保存配置成功');
} else {
toast.error(res.message || '保存配置失败');
}
},
}));

View 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>
);
};

View File

@ -0,0 +1,173 @@
import { useEffect, useState } from 'react';
import './style.css';
import { usePackageStore, Package } from './store.ts';
import { Link2, SquareArrowOutUpRight } from 'lucide-react';
import { useConfigStore } from '@/apps/config/store';
import { useConfirm } from './Confirm.tsx';
import { AlertDialogAction } from '@/components/ui/alert-dialog.tsx';
const BASE_REGISTRY = 'https://kevisual.cn';
export const PackageManager = () => {
const { shopPackages, installedPackages, getInstalledPackages, getShopPackages, uninstallPackage, installPackage } = usePackageStore();
const { pageApi, pageStoreApi } = useConfigStore();
const [confirmId, setConfirmId] = useState<string>('');
const { confirm, module } = useConfirm({
confrimProps: {
title: '应用类型',
description: '确认应用类型web 或者 app, web 为前端的访问界面。',
footer: (
<>
<AlertDialogAction
className='cursor-pointer'
onClick={() => {
handleInstall(confirmId, 'web');
}}>
Web
</AlertDialogAction>
<AlertDialogAction className='cursor-pointer' onClick={() => handleInstall(confirmId, 'app')}>
App
</AlertDialogAction>
</>
),
},
});
useEffect(() => {
getInstalledPackages();
getShopPackages();
}, []);
const getPackageStatus = (pkg: Package) => {
const installed = installedPackages.find((p) => p.id === pkg.id || (p.user === pkg.user && p.key === pkg.key));
if (!installed) return { status: 'not-installed' };
if (installed.version !== pkg.version) return { status: 'update-available' };
console.log('installed', installed);
return { status: 'installed', appType: installed.appType };
};
const handleInstall = (id: string, type?: 'web' | 'app') => {
if (!type) {
setConfirmId(id);
confirm();
return;
}
const pkg = shopPackages.find((p) => p.id === id);
if (pkg) {
installPackage({
id: `${pkg.user}/${pkg.key}`,
force: false,
type: pkg.appType || 'web',
});
}
};
const handleUpdate = (id: string) => {
const pkg = shopPackages.find((p) => p.id === id);
if (pkg) {
installPackage({
id: `${pkg.user}/${pkg.key}`,
force: false,
type: pkg.appType || 'web',
});
}
};
const handleReinstall = (id: string) => {
const pkg = installedPackages.find((p) => p.id === id);
if (pkg) {
installPackage({
id: `${pkg.user}/${pkg.key}`,
force: false,
type: pkg.appType || 'web',
});
}
};
const handleUninstall = (id: string) => {
const pkg = installedPackages.find((p) => p.id === id);
if (pkg) {
uninstallPackage({ id: `${pkg.user}/${pkg.key}`, type: pkg.appType });
}
};
const getActionButton = (status: string, pkg: Package) => {
switch (status) {
case 'not-installed':
return (
<button className='button button-install' onClick={() => handleInstall(pkg.id)}>
Install
</button>
);
case 'update-available':
return (
<button className='button button-update' onClick={() => handleUpdate(pkg.id)}>
Update
</button>
);
case 'installed':
return (
<button className='button button-reinstall' onClick={() => handleReinstall(pkg.id)}>
Reinstall
</button>
);
}
};
const handleOpenWindow = (pkg: Package) => {
let baseUrl = pageStoreApi;
if (!baseUrl) {
baseUrl = new URL(window.location.href).origin;
}
const path = `/${pkg.user}/${pkg.key}`;
window.open(`${baseUrl}${path}`, '_blank');
};
const handleOpenClientWindow = (pkg: Package) => {
const baseUrl = new URL(window.location.href).origin;
const path = `/${pkg.user}/${pkg.key}`;
window.open(`${baseUrl}${path}`, '_blank');
};
return (
<div id='app' className=''>
<h1>Package Manager</h1>
<div className='package-list'>
{shopPackages.map((pkg) => {
const pkgStatus = getPackageStatus(pkg);
const status = pkgStatus.status;
const isInstalled = status !== 'not-installed';
return (
<div key={pkg.id} className='package-card'>
<h2>{pkg.title}</h2>
<p className='description'>{pkg.description}</p>
<div className='package-info'>
<span>Version: {pkg.version}</span>
<span>User: {pkg.user}</span>
</div>
<div className='actions'>
{getActionButton(status, pkg)}
{status !== 'not-installed' && (
<button className='button button-uninstall' onClick={() => handleUninstall(pkg.id)}>
Uninstall
</button>
)}
<div className='flex gap-2'>
<div className='cursor-pointer p-2 rounded-md bg-amber-500 text-white'>
<SquareArrowOutUpRight onClick={() => handleOpenWindow(pkg)} />
</div>
{isInstalled && pkgStatus?.appType === 'web' && (
<div className='cursor-pointer p-2 rounded-md bg-amber-500 text-white'>
<Link2 onClick={() => handleOpenClientWindow(pkg)} />
</div>
)}
</div>
</div>
</div>
);
})}
</div>
{module}
</div>
);
};
export default PackageManager;

View File

@ -0,0 +1,10 @@
import { PackageManager } from './PackageManager.tsx';
import { ToastContainer } from 'react-toastify';
export const App = () => {
return (
<div className='bg-amber-800 w-full h-full'>
<ToastContainer />
<PackageManager />
</div>
);
};

View File

@ -0,0 +1,8 @@
import { clientQuery, query } from '@/modules/query';
import { QueryApp as QueryShop } from '@kevisual/query-awesome/query-shop.js';
import { QueryApp } from '@kevisual/query-awesome/query-app.js';
export const qs = new QueryShop({ query: clientQuery });
export const qa = new QueryApp({ query: query });
console.log('qs', qa.query.url);

135
src/apps/shop-list/store.ts Normal file
View File

@ -0,0 +1,135 @@
import { create } from 'zustand';
import { clientQuery as client, query } from '@/modules/query';
import { toast } from 'react-toastify';
import { qa, qs } from './query.ts';
export type Package = {
id: string;
name?: string;
version?: string;
description?: string;
title?: string;
user?: string;
key?: string;
appType?: any;
};
export type InstallOptions = {
id: string;
type?: 'web' | 'app';
/**
*
*/
force?: boolean;
yes?: boolean;
};
export type UninstallOptions = {
id: string;
type?: 'web' | 'app';
yes?: boolean;
};
type PackageStore = {
installedPackages: Package[];
shopPackages: Package[];
setInstalledPackages: (packages: Package[]) => void;
setShopPackages: (packages: Package[]) => void;
getInstalledPackages: () => Promise<any>;
getShopPackages: () => Promise<Package[]>;
uninstallPackage: (data: UninstallOptions) => Promise<void>;
installPackage: (data: InstallOptions) => Promise<void>;
};
export const usePackageStore = create<PackageStore>((set, get) => ({
installedPackages: [],
shopPackages: [],
setInstalledPackages: (packages) => set({ installedPackages: packages }),
setShopPackages: (packages) => set({ shopPackages: packages }),
getInstalledPackages: async () => {
type ListPackage = {
appInfo: Package;
id?: string;
[key: string]: any;
};
const res = await qs.appDefine.queryChain('listInstalled').get<ListPackage[]>({});
if (res.code === 200) {
const listPackages = res.data || [];
const installed = listPackages.map((item) => {
return { appType: item.appType, ...item.appInfo };
});
set({ installedPackages: installed });
}
return res;
},
getShopPackages: async () => {
const res = await qa.post({
path: 'app',
key: 'public-list',
});
if (res.code === 200) {
set({ shopPackages: res.data });
}
return res.data;
},
uninstallPackage: async (data: UninstallOptions) => {
if (typeof data.yes === 'undefined') data.yes = true;
const res = await qs.post({
path: 'shop',
key: 'uninstall',
data,
});
if (res.code === 200) {
get().getInstalledPackages();
toast.success('Package uninstalled successfully');
} else {
toast.error(res.message || 'Failed to uninstall package');
}
console.log('uninstallPackage', res);
},
installPackage: async (data: InstallOptions) => {
const toastId = toast.loading('Installing package...');
if (typeof data.yes === 'undefined') data.yes = true;
const res = await client.post({
path: 'shop',
key: 'install',
data,
});
toast.dismiss(toastId);
if (res.code === 200) {
get().getInstalledPackages();
toast.success('Package installed successfully');
} else {
toast.error(res.message || 'Failed to install package');
}
console.log('installPackage', res);
},
}));
const installedPackages: Package[] = [
{ user: 'test', key: 'test-key', version: '1.0.0', id: '1', title: '', description: '' },
{ user: 'demo', key: 'demo-package', version: '1.2.0', id: '2', title: '', description: '' },
];
const mockPackages: Package[] = [
{
id: '1',
title: 'Demo Package 1',
description: 'A test package for demonstration',
version: '1.0.0',
user: 'test',
key: 'test-key',
},
{
id: '2',
title: 'Demo Package 2',
description: 'Another test package with updates',
version: '2.0.0',
user: 'demo',
key: 'demo-package',
},
{
id: '3',
title: 'New Package',
description: "A package that hasn't been installed yet",
version: '1.0.0',
user: 'demo',
key: 'new-package',
},
];

View File

@ -0,0 +1,120 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
background-color: #fff8e1;
color: #213547;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
}
h1 {
text-align: center;
color: #ff8f00;
}
.package-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
padding: 1rem;
}
.package-card {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 4px rgba(255, 143, 0, 0.1);
border: 1px solid #ffe0b2;
}
.package-card h2 {
margin: 0 0 0.5rem 0;
color: #f57c00;
}
.package-card .description {
color: #666;
margin-bottom: 1rem;
font-size: 0.9rem;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.5;
max-height: 6em;
}
.package-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
font-size: 0.9rem;
color: #666;
}
.actions {
display: flex;
gap: 0.5rem;
}
.button {
padding: 0.5rem 1rem;
border-radius: 4px;
border: none;
cursor: pointer;
font-weight: 500;
transition: background-color 0.2s;
}
.button-install {
background-color: #ffa000;
color: white;
}
.button-update {
background-color: #ff8f00;
color: white;
}
.button-reinstall {
background-color: #ffb300;
color: white;
}
.button-uninstall {
background-color: #ff6f00;
color: white;
}
.button:hover {
opacity: 0.9;
}
.button:disabled {
background-color: #ffe0b2;
cursor: not-allowed;
}
.error-message {
text-align: center;
color: #ff6f00;
padding: 2rem;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(255, 143, 0, 0.1);
grid-column: 1 / -1;
}

View 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>
);
}

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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,
}

View File

@ -0,0 +1,66 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
{
variants: {
variant: {
default: "bg-card text-card-foreground",
destructive:
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Alert({
className,
variant,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
return (
<div
data-slot="alert"
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
)
}
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-title"
className={cn(
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
className
)}
{...props}
/>
)
}
function AlertDescription({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-description"
className={cn(
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
className
)}
{...props}
/>
)
}
export { Alert, AlertTitle, AlertDescription }

View 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,
}

View File

@ -0,0 +1,59 @@
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",
},
}
)
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 }

View 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,
}

View 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
src/components/ui/form.tsx Normal file
View 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,
}

View 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 }

View 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 }

View 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 }

View 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
src/components/ui/table.tsx Normal file
View 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,
}

View 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 }

View 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 }

View File

@ -1,3 +1,7 @@
import { Query } from '@kevisual/query';
export const query = new Query();
export const clientQuery = new Query({
url: '/client/router',
});

View File

@ -1,50 +1,15 @@
---
// import { query } from '@/modules/query.ts';
console.log('Hello from index.astro');
import { Test } from '@/components/Test.tsx';
import { App } from '@/apps/shop-list/index.tsx';
import '../styles/global.css';
---
<html lang='en'>
<html lang='zh-CN'>
<head>
<title>My Homepage</title>
</head>e
</head>
<body>
<h1 onclick="{onClick}">Welcome to my website!</h1>
<div class='bg-amber-50 w-20 h-20 rounded-full'></div>
<div id='root'></div>
<Test client:only />
<!-- <Button client:only="react">sdf sdf</Button> -->
<script type='importmap' data-vite-ignore is:inline>
{
"imports": {
"react": "https://esm.sh/react@19.1.0",
"react-dom": "https://esm.sh/react-dom@19.1.0/client.js",
"react-toastify": "https://esm.sh/react-toastify@11.0.5"
}
}
</script>
<script type='module' data-vite-ignore is:inline>
import { Button, message } from 'https://esm.sh/antd?standalone';
import React from 'react';
import { ToastContainer, toast } from 'react-toastify';
import { createRoot } from 'react-dom';
setTimeout(() => {
toast.loading('Hello from index.astro');
window.toast = toast;
console.log('message', toast);
}, 1000);
console.log('Hello from index.astro', Button);
const root = document.getElementById('root');
const render = createRoot(root);
const App = () => {
const button = React.createElement(Button, null, 'Hello');
const messageEl = React.createElement(ToastContainer, null, 'Hello');
const wrapperMessage = React.createElement('div', null, [button, messageEl]);
return wrapperMessage;
};
// render.render(React.createElement(Button, null, 'Hello'), root);
render.render(App(), root);
</script>
<App client:only />
</body>
</html>