Compare commits
	
		
			10 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | b97c746d36 | ||
| 9793d96def | |||
|   | a6d9c23179 | ||
|   | 28a86dcbff | ||
| 7cd826b052 | |||
|   | da20e34dc9 | ||
|   | a43b5a96bb | ||
|   | 48aaa262c1 | ||
| 660839d854 | |||
|   | 6d23d601a8 | 
| @@ -36,6 +36,7 @@ jobs: | |||||||
|             type=raw,value=latest,enable=${{ !contains(github.ref, 'snapshot') && !contains(github.ref, 'alpha') && !contains(github.ref, 'beta') }} |             type=raw,value=latest,enable=${{ !contains(github.ref, 'snapshot') && !contains(github.ref, 'alpha') && !contains(github.ref, 'beta') }} | ||||||
|        |        | ||||||
|       - name: 构建并推送 Docker 镜像 |       - name: 构建并推送 Docker 镜像 | ||||||
|  |         id: build | ||||||
|         uses: docker/build-push-action@v5 |         uses: docker/build-push-action@v5 | ||||||
|         with: |         with: | ||||||
|           context: . |           context: . | ||||||
| @@ -47,3 +48,10 @@ jobs: | |||||||
|           # platforms: linux/amd64,linux/arm64 |           # platforms: linux/amd64,linux/arm64 | ||||||
|           platforms: linux/amd64 |           platforms: linux/amd64 | ||||||
|        |        | ||||||
|  |       - name: 触发部署 workflow | ||||||
|  |         uses: benc-uk/workflow-dispatch@v1 | ||||||
|  |         with: | ||||||
|  |           workflow: deploy.yaml | ||||||
|  |           token: ${{ secrets.GITHUB_TOKEN }} | ||||||
|  |           inputs: '{"image_tag": "${{ github.ref_name }}"}' | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										82
									
								
								.gitea/workflows/deploy.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								.gitea/workflows/deploy.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | |||||||
|  | name: Deploy to Production | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   workflow_dispatch: | ||||||
|  |     inputs: | ||||||
|  |       image_tag: | ||||||
|  |         description: '要部署的镜像标签 (例如: v1.0.0, latest)' | ||||||
|  |         required: true | ||||||
|  |         default: 'latest' | ||||||
|  |       compose_path: | ||||||
|  |         description: 'docker-compose 文件路径' | ||||||
|  |         required: true | ||||||
|  |         default: '/root/.compose/litek/compose.yaml' | ||||||
|  |   workflow_call: | ||||||
|  |     inputs: | ||||||
|  |       image_tag: | ||||||
|  |         description: '要部署的镜像标签' | ||||||
|  |         required: true | ||||||
|  |         type: string | ||||||
|  |       compose_path: | ||||||
|  |         description: 'docker-compose 文件路径' | ||||||
|  |         required: false | ||||||
|  |         type: string | ||||||
|  |         default: '/root/.compose/litek/compose.yaml' | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   deploy: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |      | ||||||
|  |     steps: | ||||||
|  |       - name: 解析 SSH 连接信息 | ||||||
|  |         id: ssh-info | ||||||
|  |         run: | | ||||||
|  |           SSH_CONN="${{ secrets.SSH_CONNECTION }}" | ||||||
|  |           # 格式: user@host:port | ||||||
|  |           SSH_USER=$(echo $SSH_CONN | cut -d'@' -f1) | ||||||
|  |           SSH_HOST_PORT=$(echo $SSH_CONN | cut -d'@' -f2) | ||||||
|  |           SSH_HOST=$(echo $SSH_HOST_PORT | cut -d':' -f1) | ||||||
|  |           SSH_PORT=$(echo $SSH_HOST_PORT | cut -d':' -f2) | ||||||
|  |            | ||||||
|  |           echo "user=$SSH_USER" >> $GITHUB_OUTPUT | ||||||
|  |           echo "host=$SSH_HOST" >> $GITHUB_OUTPUT | ||||||
|  |           echo "port=$SSH_PORT" >> $GITHUB_OUTPUT | ||||||
|  |            | ||||||
|  |           echo "SSH 连接信息已解析: $SSH_USER@$SSH_HOST:$SSH_PORT" | ||||||
|  |        | ||||||
|  |       - name: 部署到生产服务器 | ||||||
|  |         uses: appleboy/ssh-action@v1.0.3 | ||||||
|  |         with: | ||||||
|  |           host: ${{ steps.ssh-info.outputs.host }} | ||||||
|  |           username: ${{ steps.ssh-info.outputs.user }} | ||||||
|  |           port: ${{ steps.ssh-info.outputs.port }} | ||||||
|  |           key: ${{ secrets.SSH_PRIVATE_KEY }} | ||||||
|  |           script: | | ||||||
|  |             echo "开始部署 litek 应用..." | ||||||
|  |             echo "镜像标签: ${{ inputs.image_tag }}" | ||||||
|  |             echo "Compose 文件: ${{ inputs.compose_path }}" | ||||||
|  |              | ||||||
|  |             # 切换到 compose 文件所在目录 | ||||||
|  |             COMPOSE_DIR=$(dirname "${{ inputs.compose_path }}") | ||||||
|  |             cd "$COMPOSE_DIR" | ||||||
|  |              | ||||||
|  |             echo "当前目录: $(pwd)" | ||||||
|  |              | ||||||
|  |             # 拉取最新镜像 | ||||||
|  |             echo "正在拉取镜像..." | ||||||
|  |             docker compose -f "${{ inputs.compose_path }}" pull | ||||||
|  |              | ||||||
|  |             # 更新服务 | ||||||
|  |             echo "正在更新服务..." | ||||||
|  |             docker compose -f "${{ inputs.compose_path }}" up -d | ||||||
|  |              | ||||||
|  |             # 清理旧镜像 | ||||||
|  |             echo "清理未使用的镜像..." | ||||||
|  |             docker image prune -f | ||||||
|  |              | ||||||
|  |             echo "部署完成!" | ||||||
|  |              | ||||||
|  |             # 显示运行状态 | ||||||
|  |             echo "当前运行的容器:" | ||||||
|  |             docker compose -f "${{ inputs.compose_path }}" ps | ||||||
|  |  | ||||||
| @@ -11,7 +11,8 @@ WORKDIR /app | |||||||
| COPY package.json pnpm-lock.yaml ./ | COPY package.json pnpm-lock.yaml ./ | ||||||
|  |  | ||||||
| # 安装依赖 | # 安装依赖 | ||||||
| RUN pnpm install --frozen-lockfile | RUN --mount=type=cache,target=/root/.local/share/pnpm/store \ | ||||||
|  |     pnpm install --frozen-lockfile | ||||||
|  |  | ||||||
| # 复制源代码 | # 复制源代码 | ||||||
| COPY . . | COPY . . | ||||||
|   | |||||||
| @@ -1,16 +1,20 @@ | |||||||
| { | { | ||||||
|   "name": "litek", |   "name": "litek", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "version": "0.0.3", |   "version": "0.0.8", | ||||||
|   "type": "module", |   "type": "module", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "dev": "vite", |     "dev": "vite", | ||||||
|     "build": "tsc -b && vite build", |     "build": "tsc -b && vite build", | ||||||
|     "lint": "eslint .", |     "lint": "eslint .", | ||||||
|     "preview": "vite preview" |     "preview": "vite preview", | ||||||
|  |     "release:patch": "npm version patch && git push --follow-tags", | ||||||
|  |     "release:minor": "npm version minor && git push --follow-tags", | ||||||
|  |     "release:major": "npm version major && git push --follow-tags" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@radix-ui/react-dialog": "^1.1.15", |     "@radix-ui/react-dialog": "^1.1.15", | ||||||
|  |     "@radix-ui/react-dropdown-menu": "^2.1.16", | ||||||
|     "@radix-ui/react-separator": "^1.1.7", |     "@radix-ui/react-separator": "^1.1.7", | ||||||
|     "@radix-ui/react-slot": "^1.2.3", |     "@radix-ui/react-slot": "^1.2.3", | ||||||
|     "@radix-ui/react-tooltip": "^1.2.8", |     "@radix-ui/react-tooltip": "^1.2.8", | ||||||
|   | |||||||
							
								
								
									
										140
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										140
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @@ -14,6 +14,9 @@ importers: | |||||||
|       '@radix-ui/react-dialog': |       '@radix-ui/react-dialog': | ||||||
|         specifier: ^1.1.15 |         specifier: ^1.1.15 | ||||||
|         version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) |         version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-dropdown-menu': | ||||||
|  |         specifier: ^2.1.16 | ||||||
|  |         version: 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|       '@radix-ui/react-separator': |       '@radix-ui/react-separator': | ||||||
|         specifier: ^1.1.7 |         specifier: ^1.1.7 | ||||||
|         version: 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) |         version: 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
| @@ -320,6 +323,19 @@ packages: | |||||||
|       '@types/react-dom': |       '@types/react-dom': | ||||||
|         optional: true |         optional: true | ||||||
|  |  | ||||||
|  |   '@radix-ui/react-collection@1.1.7': | ||||||
|  |     resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} | ||||||
|  |     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': |   '@radix-ui/react-compose-refs@1.1.2': | ||||||
|     resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} |     resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} | ||||||
|     peerDependencies: |     peerDependencies: | ||||||
| @@ -351,6 +367,15 @@ packages: | |||||||
|       '@types/react-dom': |       '@types/react-dom': | ||||||
|         optional: true |         optional: true | ||||||
|  |  | ||||||
|  |   '@radix-ui/react-direction@1.1.1': | ||||||
|  |     resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} | ||||||
|  |     peerDependencies: | ||||||
|  |       '@types/react': '*' | ||||||
|  |       react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc | ||||||
|  |     peerDependenciesMeta: | ||||||
|  |       '@types/react': | ||||||
|  |         optional: true | ||||||
|  |  | ||||||
|   '@radix-ui/react-dismissable-layer@1.1.11': |   '@radix-ui/react-dismissable-layer@1.1.11': | ||||||
|     resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} |     resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} | ||||||
|     peerDependencies: |     peerDependencies: | ||||||
| @@ -364,6 +389,19 @@ packages: | |||||||
|       '@types/react-dom': |       '@types/react-dom': | ||||||
|         optional: true |         optional: true | ||||||
|  |  | ||||||
|  |   '@radix-ui/react-dropdown-menu@2.1.16': | ||||||
|  |     resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} | ||||||
|  |     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.3': |   '@radix-ui/react-focus-guards@1.1.3': | ||||||
|     resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} |     resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} | ||||||
|     peerDependencies: |     peerDependencies: | ||||||
| @@ -395,6 +433,19 @@ packages: | |||||||
|       '@types/react': |       '@types/react': | ||||||
|         optional: true |         optional: true | ||||||
|  |  | ||||||
|  |   '@radix-ui/react-menu@2.1.16': | ||||||
|  |     resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} | ||||||
|  |     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-popper@1.2.8': |   '@radix-ui/react-popper@1.2.8': | ||||||
|     resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} |     resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} | ||||||
|     peerDependencies: |     peerDependencies: | ||||||
| @@ -447,6 +498,19 @@ packages: | |||||||
|       '@types/react-dom': |       '@types/react-dom': | ||||||
|         optional: true |         optional: true | ||||||
|  |  | ||||||
|  |   '@radix-ui/react-roving-focus@1.1.11': | ||||||
|  |     resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} | ||||||
|  |     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-separator@1.1.7': |   '@radix-ui/react-separator@1.1.7': | ||||||
|     resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} |     resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} | ||||||
|     peerDependencies: |     peerDependencies: | ||||||
| @@ -1857,6 +1921,18 @@ snapshots: | |||||||
|       '@types/react': 19.2.2 |       '@types/react': 19.2.2 | ||||||
|       '@types/react-dom': 19.2.2(@types/react@19.2.2) |       '@types/react-dom': 19.2.2(@types/react@19.2.2) | ||||||
|  |  | ||||||
|  |   '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': | ||||||
|  |     dependencies: | ||||||
|  |       '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       react: 19.2.0 | ||||||
|  |       react-dom: 19.2.0(react@19.2.0) | ||||||
|  |     optionalDependencies: | ||||||
|  |       '@types/react': 19.2.2 | ||||||
|  |       '@types/react-dom': 19.2.2(@types/react@19.2.2) | ||||||
|  |  | ||||||
|   '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)': |   '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)': | ||||||
|     dependencies: |     dependencies: | ||||||
|       react: 19.2.0 |       react: 19.2.0 | ||||||
| @@ -1891,6 +1967,12 @@ snapshots: | |||||||
|       '@types/react': 19.2.2 |       '@types/react': 19.2.2 | ||||||
|       '@types/react-dom': 19.2.2(@types/react@19.2.2) |       '@types/react-dom': 19.2.2(@types/react@19.2.2) | ||||||
|  |  | ||||||
|  |   '@radix-ui/react-direction@1.1.1(@types/react@19.2.2)(react@19.2.0)': | ||||||
|  |     dependencies: | ||||||
|  |       react: 19.2.0 | ||||||
|  |     optionalDependencies: | ||||||
|  |       '@types/react': 19.2.2 | ||||||
|  |  | ||||||
|   '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': |   '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': | ||||||
|     dependencies: |     dependencies: | ||||||
|       '@radix-ui/primitive': 1.1.3 |       '@radix-ui/primitive': 1.1.3 | ||||||
| @@ -1904,6 +1986,21 @@ snapshots: | |||||||
|       '@types/react': 19.2.2 |       '@types/react': 19.2.2 | ||||||
|       '@types/react-dom': 19.2.2(@types/react@19.2.2) |       '@types/react-dom': 19.2.2(@types/react@19.2.2) | ||||||
|  |  | ||||||
|  |   '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': | ||||||
|  |     dependencies: | ||||||
|  |       '@radix-ui/primitive': 1.1.3 | ||||||
|  |       '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       react: 19.2.0 | ||||||
|  |       react-dom: 19.2.0(react@19.2.0) | ||||||
|  |     optionalDependencies: | ||||||
|  |       '@types/react': 19.2.2 | ||||||
|  |       '@types/react-dom': 19.2.2(@types/react@19.2.2) | ||||||
|  |  | ||||||
|   '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.2)(react@19.2.0)': |   '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.2)(react@19.2.0)': | ||||||
|     dependencies: |     dependencies: | ||||||
|       react: 19.2.0 |       react: 19.2.0 | ||||||
| @@ -1928,6 +2025,32 @@ snapshots: | |||||||
|     optionalDependencies: |     optionalDependencies: | ||||||
|       '@types/react': 19.2.2 |       '@types/react': 19.2.2 | ||||||
|  |  | ||||||
|  |   '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': | ||||||
|  |     dependencies: | ||||||
|  |       '@radix-ui/primitive': 1.1.3 | ||||||
|  |       '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       aria-hidden: 1.2.6 | ||||||
|  |       react: 19.2.0 | ||||||
|  |       react-dom: 19.2.0(react@19.2.0) | ||||||
|  |       react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |     optionalDependencies: | ||||||
|  |       '@types/react': 19.2.2 | ||||||
|  |       '@types/react-dom': 19.2.2(@types/react@19.2.2) | ||||||
|  |  | ||||||
|   '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': |   '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': | ||||||
|     dependencies: |     dependencies: | ||||||
|       '@floating-ui/react-dom': 2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) |       '@floating-ui/react-dom': 2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
| @@ -1975,6 +2098,23 @@ snapshots: | |||||||
|       '@types/react': 19.2.2 |       '@types/react': 19.2.2 | ||||||
|       '@types/react-dom': 19.2.2(@types/react@19.2.2) |       '@types/react-dom': 19.2.2(@types/react@19.2.2) | ||||||
|  |  | ||||||
|  |   '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': | ||||||
|  |     dependencies: | ||||||
|  |       '@radix-ui/primitive': 1.1.3 | ||||||
|  |       '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|  |       '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) | ||||||
|  |       react: 19.2.0 | ||||||
|  |       react-dom: 19.2.0(react@19.2.0) | ||||||
|  |     optionalDependencies: | ||||||
|  |       '@types/react': 19.2.2 | ||||||
|  |       '@types/react-dom': 19.2.2(@types/react@19.2.2) | ||||||
|  |  | ||||||
|   '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': |   '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': | ||||||
|     dependencies: |     dependencies: | ||||||
|       '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) |       '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar"; | import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar"; | ||||||
| import { tools } from "@/components/tool"; | import { tools } from "@/components/tool"; | ||||||
| import { Link } from "react-router-dom"; | import { Link } from "react-router-dom"; | ||||||
|  | import { ModeToggle } from "@/components/theme/toggle"; | ||||||
|  | import { Button } from "../ui/button"; | ||||||
|  |  | ||||||
| export const AppSidebar = () => ( | export const AppSidebar = () => ( | ||||||
|   <Sidebar> |   <Sidebar> | ||||||
| @@ -28,8 +30,11 @@ export const AppSidebar = () => ( | |||||||
|         </SidebarGroupContent> |         </SidebarGroupContent> | ||||||
|       </SidebarGroup> |       </SidebarGroup> | ||||||
|     </SidebarContent> |     </SidebarContent> | ||||||
|     <SidebarFooter> |     <SidebarFooter className="flex flex-row justify-between items-center gap-2"> | ||||||
|       <a href="mailto:litek@mail.typist.cc">contact us</a> |       <Button variant="link"> | ||||||
|  |         <a href="mailto:litek@mail.typist.cc">need more tools?</a> | ||||||
|  |       </Button> | ||||||
|  |       <ModeToggle /> | ||||||
|     </SidebarFooter> |     </SidebarFooter> | ||||||
|   </Sidebar> |   </Sidebar> | ||||||
| ) | ) | ||||||
							
								
								
									
										73
									
								
								src/components/theme/provider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/components/theme/provider.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | import { createContext, useContext, useEffect, useState } from "react" | ||||||
|  |  | ||||||
|  | type Theme = "dark" | "light" | "system" | ||||||
|  |  | ||||||
|  | type ThemeProviderProps = { | ||||||
|  |   children: React.ReactNode | ||||||
|  |   defaultTheme?: Theme | ||||||
|  |   storageKey?: string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ThemeProviderState = { | ||||||
|  |   theme: Theme | ||||||
|  |   setTheme: (theme: Theme) => void | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const initialState: ThemeProviderState = { | ||||||
|  |   theme: "system", | ||||||
|  |   setTheme: () => null, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const ThemeProviderContext = createContext<ThemeProviderState>(initialState) | ||||||
|  |  | ||||||
|  | export function ThemeProvider({ | ||||||
|  |   children, | ||||||
|  |   defaultTheme = "system", | ||||||
|  |   storageKey = "vite-ui-theme", | ||||||
|  |   ...props | ||||||
|  | }: ThemeProviderProps) { | ||||||
|  |   const [theme, setTheme] = useState<Theme>( | ||||||
|  |     () => (localStorage.getItem(storageKey) as Theme) || defaultTheme | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     const root = window.document.documentElement | ||||||
|  |  | ||||||
|  |     root.classList.remove("light", "dark") | ||||||
|  |  | ||||||
|  |     if (theme === "system") { | ||||||
|  |       const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") | ||||||
|  |         .matches | ||||||
|  |         ? "dark" | ||||||
|  |         : "light" | ||||||
|  |  | ||||||
|  |       root.classList.add(systemTheme) | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     root.classList.add(theme) | ||||||
|  |   }, [theme]) | ||||||
|  |  | ||||||
|  |   const value = { | ||||||
|  |     theme, | ||||||
|  |     setTheme: (theme: Theme) => { | ||||||
|  |       localStorage.setItem(storageKey, theme) | ||||||
|  |       setTheme(theme) | ||||||
|  |     }, | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <ThemeProviderContext.Provider {...props} value={value}> | ||||||
|  |       {children} | ||||||
|  |     </ThemeProviderContext.Provider> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export const useTheme = () => { | ||||||
|  |   const context = useContext(ThemeProviderContext) | ||||||
|  |  | ||||||
|  |   if (context === undefined) | ||||||
|  |     throw new Error("useTheme must be used within a ThemeProvider") | ||||||
|  |  | ||||||
|  |   return context | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								src/components/theme/toggle.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/components/theme/toggle.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | import { Moon, Sun } from "lucide-react" | ||||||
|  |  | ||||||
|  | import { Button } from "@/components/ui/button" | ||||||
|  | import { | ||||||
|  |   DropdownMenu, | ||||||
|  |   DropdownMenuContent, | ||||||
|  |   DropdownMenuItem, | ||||||
|  |   DropdownMenuTrigger, | ||||||
|  | } from "@/components/ui/dropdown-menu" | ||||||
|  |  | ||||||
|  | import { useTheme } from "./provider" | ||||||
|  |  | ||||||
|  | export function ModeToggle() { | ||||||
|  |   const { setTheme } = useTheme() | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <DropdownMenu> | ||||||
|  |       <DropdownMenuTrigger asChild> | ||||||
|  |         <Button variant="outline" size="icon"> | ||||||
|  |           <Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" /> | ||||||
|  |           <Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" /> | ||||||
|  |           <span className="sr-only">Toggle theme</span> | ||||||
|  |         </Button> | ||||||
|  |       </DropdownMenuTrigger> | ||||||
|  |       <DropdownMenuContent align="end"> | ||||||
|  |         <DropdownMenuItem onClick={() => setTheme("light")}> | ||||||
|  |           Light | ||||||
|  |         </DropdownMenuItem> | ||||||
|  |         <DropdownMenuItem onClick={() => setTheme("dark")}> | ||||||
|  |           Dark | ||||||
|  |         </DropdownMenuItem> | ||||||
|  |         <DropdownMenuItem onClick={() => setTheme("system")}> | ||||||
|  |           System | ||||||
|  |         </DropdownMenuItem> | ||||||
|  |       </DropdownMenuContent> | ||||||
|  |     </DropdownMenu> | ||||||
|  |   ) | ||||||
|  | } | ||||||
							
								
								
									
										70
									
								
								src/components/tool/base64.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/components/tool/base64.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | import { useState, type FC } from "react"; | ||||||
|  | import { Textarea } from "@/components/ui/textarea"; | ||||||
|  | import { Button } from "@/components/ui/button"; | ||||||
|  | import { toast } from "sonner"; | ||||||
|  | import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"; | ||||||
|  |  | ||||||
|  | const Tool: FC = () => { | ||||||
|  |   const [decoded, setDecoded] = useState<string>(""); | ||||||
|  |   const [encoded, setEncoded] = useState<string>(""); | ||||||
|  |  | ||||||
|  |   const encode = () => { | ||||||
|  |     try { | ||||||
|  |       const encoded64 = btoa(decoded); | ||||||
|  |       setEncoded(encoded64); | ||||||
|  |       setDecoded(""); | ||||||
|  |       toast.success("encoded successfully"); | ||||||
|  |     } catch (error: unknown) { | ||||||
|  |       if (error instanceof Error) { | ||||||
|  |         toast.error(error.message); | ||||||
|  |       } else { | ||||||
|  |         toast.error("encoding failed"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const decode = () => { | ||||||
|  |     try { | ||||||
|  |       const decoded64 = atob(encoded); | ||||||
|  |       setDecoded(decoded64); | ||||||
|  |       setEncoded(""); | ||||||
|  |       toast.success("decoded successfully"); | ||||||
|  |     } catch (error: unknown) { | ||||||
|  |       if (error instanceof Error) { | ||||||
|  |         toast.error(error.message); | ||||||
|  |       } else { | ||||||
|  |         toast.error("decoding failed"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <div className="h-[50vh] flex flex-row gap-4 pt-[20vh]"> | ||||||
|  |       <Textarea | ||||||
|  |         className="flex-1 resize-none" | ||||||
|  |         placeholder="Enter the original text" | ||||||
|  |         value={decoded} | ||||||
|  |         onChange={(e) => setDecoded(e.target.value)} | ||||||
|  |       /> | ||||||
|  |       <div className="flex flex-col gap-2 justify-center"> | ||||||
|  |         <Button onClick={encode}> | ||||||
|  |           <ArrowRightIcon className="size-4" /> | ||||||
|  |           Encode | ||||||
|  |         </Button> | ||||||
|  |         <Button onClick={decode}> | ||||||
|  |           <ArrowLeftIcon className="size-4" /> | ||||||
|  |           Decode | ||||||
|  |         </Button> | ||||||
|  |       </div> | ||||||
|  |       <Textarea | ||||||
|  |         className="flex-1 resize-none" | ||||||
|  |         placeholder="Enter the Base64 encoded text" | ||||||
|  |         value={encoded} | ||||||
|  |         onChange={(e) => setEncoded(e.target.value)} | ||||||
|  |       /> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default Tool; | ||||||
|  |  | ||||||
| @@ -1,8 +1,9 @@ | |||||||
| import type { ReactNode } from 'react'; | import type { ReactNode } from 'react'; | ||||||
| import { FileJson, Hash } from 'lucide-react' | import { FileJson, Hash, Binary } from 'lucide-react' | ||||||
|  |  | ||||||
| import UUID from './uuid' | import UUID from './uuid' | ||||||
| import JSON from './json' | import JSON from './json' | ||||||
|  | import Base64 from './base64' | ||||||
|  |  | ||||||
| export interface Tool { | export interface Tool { | ||||||
|   path: string; |   path: string; | ||||||
| @@ -26,5 +27,12 @@ export const tools: Tool[] = [ | |||||||
|     description: "Format and validate JSON", |     description: "Format and validate JSON", | ||||||
|     icon: <FileJson />, |     icon: <FileJson />, | ||||||
|     component: <JSON />, |     component: <JSON />, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     path: "base64", | ||||||
|  |     name: "Base64 Encoder/Decoder", | ||||||
|  |     description: "Encode and decode Base64", | ||||||
|  |     icon: <Binary />, | ||||||
|  |     component: <Base64 />, | ||||||
|   } |   } | ||||||
| ]; | ]; | ||||||
							
								
								
									
										255
									
								
								src/components/ui/dropdown-menu.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								src/components/ui/dropdown-menu.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,255 @@ | |||||||
|  | import * as React from "react" | ||||||
|  | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" | ||||||
|  | import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" | ||||||
|  |  | ||||||
|  | import { cn } from "@/lib/utils" | ||||||
|  |  | ||||||
|  | function DropdownMenu({ | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) { | ||||||
|  |   return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} /> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuPortal({ | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) { | ||||||
|  |   return ( | ||||||
|  |     <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} /> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuTrigger({ | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) { | ||||||
|  |   return ( | ||||||
|  |     <DropdownMenuPrimitive.Trigger | ||||||
|  |       data-slot="dropdown-menu-trigger" | ||||||
|  |       {...props} | ||||||
|  |     /> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuContent({ | ||||||
|  |   className, | ||||||
|  |   sideOffset = 4, | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) { | ||||||
|  |   return ( | ||||||
|  |     <DropdownMenuPrimitive.Portal> | ||||||
|  |       <DropdownMenuPrimitive.Content | ||||||
|  |         data-slot="dropdown-menu-content" | ||||||
|  |         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 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md", | ||||||
|  |           className | ||||||
|  |         )} | ||||||
|  |         {...props} | ||||||
|  |       /> | ||||||
|  |     </DropdownMenuPrimitive.Portal> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuGroup({ | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) { | ||||||
|  |   return ( | ||||||
|  |     <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} /> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuItem({ | ||||||
|  |   className, | ||||||
|  |   inset, | ||||||
|  |   variant = "default", | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & { | ||||||
|  |   inset?: boolean | ||||||
|  |   variant?: "default" | "destructive" | ||||||
|  | }) { | ||||||
|  |   return ( | ||||||
|  |     <DropdownMenuPrimitive.Item | ||||||
|  |       data-slot="dropdown-menu-item" | ||||||
|  |       data-inset={inset} | ||||||
|  |       data-variant={variant} | ||||||
|  |       className={cn( | ||||||
|  |         "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_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]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", | ||||||
|  |         className | ||||||
|  |       )} | ||||||
|  |       {...props} | ||||||
|  |     /> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuCheckboxItem({ | ||||||
|  |   className, | ||||||
|  |   children, | ||||||
|  |   checked, | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) { | ||||||
|  |   return ( | ||||||
|  |     <DropdownMenuPrimitive.CheckboxItem | ||||||
|  |       data-slot="dropdown-menu-checkbox-item" | ||||||
|  |       className={cn( | ||||||
|  |         "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 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", | ||||||
|  |         className | ||||||
|  |       )} | ||||||
|  |       checked={checked} | ||||||
|  |       {...props} | ||||||
|  |     > | ||||||
|  |       <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> | ||||||
|  |         <DropdownMenuPrimitive.ItemIndicator> | ||||||
|  |           <CheckIcon className="size-4" /> | ||||||
|  |         </DropdownMenuPrimitive.ItemIndicator> | ||||||
|  |       </span> | ||||||
|  |       {children} | ||||||
|  |     </DropdownMenuPrimitive.CheckboxItem> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuRadioGroup({ | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) { | ||||||
|  |   return ( | ||||||
|  |     <DropdownMenuPrimitive.RadioGroup | ||||||
|  |       data-slot="dropdown-menu-radio-group" | ||||||
|  |       {...props} | ||||||
|  |     /> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuRadioItem({ | ||||||
|  |   className, | ||||||
|  |   children, | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) { | ||||||
|  |   return ( | ||||||
|  |     <DropdownMenuPrimitive.RadioItem | ||||||
|  |       data-slot="dropdown-menu-radio-item" | ||||||
|  |       className={cn( | ||||||
|  |         "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 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", | ||||||
|  |         className | ||||||
|  |       )} | ||||||
|  |       {...props} | ||||||
|  |     > | ||||||
|  |       <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> | ||||||
|  |         <DropdownMenuPrimitive.ItemIndicator> | ||||||
|  |           <CircleIcon className="size-2 fill-current" /> | ||||||
|  |         </DropdownMenuPrimitive.ItemIndicator> | ||||||
|  |       </span> | ||||||
|  |       {children} | ||||||
|  |     </DropdownMenuPrimitive.RadioItem> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuLabel({ | ||||||
|  |   className, | ||||||
|  |   inset, | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & { | ||||||
|  |   inset?: boolean | ||||||
|  | }) { | ||||||
|  |   return ( | ||||||
|  |     <DropdownMenuPrimitive.Label | ||||||
|  |       data-slot="dropdown-menu-label" | ||||||
|  |       data-inset={inset} | ||||||
|  |       className={cn( | ||||||
|  |         "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", | ||||||
|  |         className | ||||||
|  |       )} | ||||||
|  |       {...props} | ||||||
|  |     /> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuSeparator({ | ||||||
|  |   className, | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) { | ||||||
|  |   return ( | ||||||
|  |     <DropdownMenuPrimitive.Separator | ||||||
|  |       data-slot="dropdown-menu-separator" | ||||||
|  |       className={cn("bg-border -mx-1 my-1 h-px", className)} | ||||||
|  |       {...props} | ||||||
|  |     /> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuShortcut({ | ||||||
|  |   className, | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<"span">) { | ||||||
|  |   return ( | ||||||
|  |     <span | ||||||
|  |       data-slot="dropdown-menu-shortcut" | ||||||
|  |       className={cn( | ||||||
|  |         "text-muted-foreground ml-auto text-xs tracking-widest", | ||||||
|  |         className | ||||||
|  |       )} | ||||||
|  |       {...props} | ||||||
|  |     /> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuSub({ | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) { | ||||||
|  |   return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} /> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuSubTrigger({ | ||||||
|  |   className, | ||||||
|  |   inset, | ||||||
|  |   children, | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { | ||||||
|  |   inset?: boolean | ||||||
|  | }) { | ||||||
|  |   return ( | ||||||
|  |     <DropdownMenuPrimitive.SubTrigger | ||||||
|  |       data-slot="dropdown-menu-sub-trigger" | ||||||
|  |       data-inset={inset} | ||||||
|  |       className={cn( | ||||||
|  |         "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", | ||||||
|  |         className | ||||||
|  |       )} | ||||||
|  |       {...props} | ||||||
|  |     > | ||||||
|  |       {children} | ||||||
|  |       <ChevronRightIcon className="ml-auto size-4" /> | ||||||
|  |     </DropdownMenuPrimitive.SubTrigger> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function DropdownMenuSubContent({ | ||||||
|  |   className, | ||||||
|  |   ...props | ||||||
|  | }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) { | ||||||
|  |   return ( | ||||||
|  |     <DropdownMenuPrimitive.SubContent | ||||||
|  |       data-slot="dropdown-menu-sub-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 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg", | ||||||
|  |         className | ||||||
|  |       )} | ||||||
|  |       {...props} | ||||||
|  |     /> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export { | ||||||
|  |   DropdownMenu, | ||||||
|  |   DropdownMenuPortal, | ||||||
|  |   DropdownMenuTrigger, | ||||||
|  |   DropdownMenuContent, | ||||||
|  |   DropdownMenuGroup, | ||||||
|  |   DropdownMenuLabel, | ||||||
|  |   DropdownMenuItem, | ||||||
|  |   DropdownMenuCheckboxItem, | ||||||
|  |   DropdownMenuRadioGroup, | ||||||
|  |   DropdownMenuRadioItem, | ||||||
|  |   DropdownMenuSeparator, | ||||||
|  |   DropdownMenuShortcut, | ||||||
|  |   DropdownMenuSub, | ||||||
|  |   DropdownMenuSubTrigger, | ||||||
|  |   DropdownMenuSubContent, | ||||||
|  | } | ||||||
| @@ -1,20 +1,23 @@ | |||||||
| import type { FC } from "react" | import type { FC } from "react" | ||||||
| import { Outlet } from "react-router-dom"; | import { Outlet } from "react-router-dom"; | ||||||
|  |  | ||||||
|  | import { ThemeProvider } from "@/components/theme/provider" | ||||||
| import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar" | import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar" | ||||||
| import { AppSidebar } from "@/components/sidebar"; | import { AppSidebar } from "@/components/sidebar"; | ||||||
|  |  | ||||||
| export const Layout: FC = () => ( | export const Layout: FC = () => ( | ||||||
|  |   <ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme"> | ||||||
|     <SidebarProvider> |     <SidebarProvider> | ||||||
|       <AppSidebar /> |       <AppSidebar /> | ||||||
|     <div className="p-4 flex flex-col w-full h-[100vh]"> |       <div className="p-4 flex flex-col w-full h-[100vh] overflow-hidden"> | ||||||
|         <nav className="flex items-center justify-between"> |         <nav className="flex items-center justify-between"> | ||||||
|           <SidebarTrigger className="size-10" /> |           <SidebarTrigger className="size-10" /> | ||||||
|           <div role="actions" /> |           <div role="actions" /> | ||||||
|         </nav> |         </nav> | ||||||
|       <main className="flex-1 overflow-auto p-4"> |         <main className="flex-1 overflow-auto p-4 overflow-hidden"> | ||||||
|           <Outlet /> |           <Outlet /> | ||||||
|         </main> |         </main> | ||||||
|       </div> |       </div> | ||||||
|     </SidebarProvider> |     </SidebarProvider> | ||||||
|  |   </ThemeProvider> | ||||||
| ); | ); | ||||||
| @@ -3,6 +3,7 @@ import { createRoot } from 'react-dom/client' | |||||||
|  |  | ||||||
| import { Toaster } from '@/components/ui/sonner' | import { Toaster } from '@/components/ui/sonner' | ||||||
|  |  | ||||||
|  |  | ||||||
| import './index.css' | import './index.css' | ||||||
| import { AppRouter } from './router' | import { AppRouter } from './router' | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user