generated from template/astro-template
	update temp
This commit is contained in:
		
							
								
								
									
										2
									
								
								.github/workflows/git-sync.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/git-sync.yml
									
									
									
									
										vendored
									
									
								
							@@ -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 }} \
 | 
			
		||||
 
 | 
			
		||||
@@ -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({
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
									
									
								
							
							
						
						
									
										503
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -2,3 +2,7 @@ packages:
 | 
			
		||||
  - packages/*
 | 
			
		||||
  - apps/*
 | 
			
		||||
  - submodules/*
 | 
			
		||||
onlyBuiltDependencies:
 | 
			
		||||
  - '@tailwindcss/oxide'
 | 
			
		||||
  - esbuild
 | 
			
		||||
  - sharp
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										51
									
								
								src/apps/config/store.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/apps/config/store.ts
									
									
									
									
									
										Normal 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 || '保存配置失败');
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
}));
 | 
			
		||||
							
								
								
									
										100
									
								
								src/apps/shop-list/Confirm.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/apps/shop-list/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>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										173
									
								
								src/apps/shop-list/PackageManager.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								src/apps/shop-list/PackageManager.tsx
									
									
									
									
									
										Normal 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;
 | 
			
		||||
							
								
								
									
										10
									
								
								src/apps/shop-list/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/apps/shop-list/index.tsx
									
									
									
									
									
										Normal 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>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										8
									
								
								src/apps/shop-list/query.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/apps/shop-list/query.ts
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										135
									
								
								src/apps/shop-list/store.ts
									
									
									
									
									
										Normal 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',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
							
								
								
									
										120
									
								
								src/apps/shop-list/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/apps/shop-list/style.css
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										80
									
								
								src/components/a/auto-complate.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/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
									
								
								src/components/a/button.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/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
									
								
								src/components/a/confirm.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/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
									
								
								src/components/a/select.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/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
									
								
								src/components/a/tooltip.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/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>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										155
									
								
								src/components/ui/alert-dialog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								src/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,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								src/components/ui/alert.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/components/ui/alert.tsx
									
									
									
									
									
										Normal 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 }
 | 
			
		||||
							
								
								
									
										109
									
								
								src/components/ui/breadcrumb.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/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,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										59
									
								
								src/components/ui/button.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/components/ui/button.tsx
									
									
									
									
									
										Normal 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 }
 | 
			
		||||
							
								
								
									
										175
									
								
								src/components/ui/command.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								src/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
									
								
								src/components/ui/dialog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/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
									
								
								src/components/ui/form.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								src/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
									
								
								src/components/ui/input.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/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
									
								
								src/components/ui/label.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/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
									
								
								src/components/ui/popover.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/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
									
								
								src/components/ui/select.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/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
									
								
								src/components/ui/table.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/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
									
								
								src/components/ui/textarea.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/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
									
								
								src/components/ui/tooltip.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/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 }
 | 
			
		||||
@@ -1,3 +1,7 @@
 | 
			
		||||
import { Query } from '@kevisual/query';
 | 
			
		||||
 | 
			
		||||
export const query = new Query();
 | 
			
		||||
 | 
			
		||||
export const clientQuery = new Query({
 | 
			
		||||
  url: '/client/router',
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user