mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-10-23 04:52:14 +00:00
wip
This commit is contained in:
24
apps/it-tools/.gitignore
vendored
Normal file
24
apps/it-tools/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
47
apps/it-tools/README.md
Normal file
47
apps/it-tools/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Astro Starter Kit: Minimal
|
||||
|
||||
```sh
|
||||
pnpm create astro@latest -- --template minimal
|
||||
```
|
||||
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
|
||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal)
|
||||
[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/minimal/devcontainer.json)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```text
|
||||
/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `pnpm install` | Installs dependencies |
|
||||
| `pnpm dev` | Starts local dev server at `localhost:4321` |
|
||||
| `pnpm build` | Build your production site to `./dist/` |
|
||||
| `pnpm preview` | Preview your build locally, before deploying |
|
||||
| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `pnpm astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
32
apps/it-tools/astro.config.mjs
Normal file
32
apps/it-tools/astro.config.mjs
Normal file
@@ -0,0 +1,32 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
import solidJs from '@astrojs/solid-js';
|
||||
import UnoCSS from 'unocss/astro'
|
||||
import { locales, defaultLocale } from './src/i18n/languages';
|
||||
import pagefind from "astro-pagefind";
|
||||
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [
|
||||
solidJs(),
|
||||
UnoCSS({ injectReset: true }),
|
||||
pagefind(),
|
||||
],
|
||||
i18n: {
|
||||
locales,
|
||||
defaultLocale,
|
||||
},
|
||||
markdown: {
|
||||
shikiConfig: {
|
||||
themes: {
|
||||
light: 'vitesse-light',
|
||||
dark: 'vitesse-dark',
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
|
||||
},
|
||||
});
|
@@ -3,15 +3,14 @@
|
||||
"uno": {
|
||||
"config": "uno.config.ts",
|
||||
"css": {
|
||||
"path": "src/client/app.css",
|
||||
"path": "src/assets/app.css",
|
||||
"variable": true
|
||||
},
|
||||
"color": "neutral",
|
||||
"prefix": ""
|
||||
},
|
||||
"alias": {
|
||||
"component": "@/modules/ui/components",
|
||||
"ui": "@/modules/ui/components",
|
||||
"cn": "@/modules/ui/utils/cn"
|
||||
"component": "@/components",
|
||||
"cn": "@/libs/cn"
|
||||
}
|
||||
}
|
||||
}
|
814
apps/it-tools/package-lock.json
generated
Normal file
814
apps/it-tools/package-lock.json
generated
Normal file
@@ -0,0 +1,814 @@
|
||||
{
|
||||
"name": "@it-tools/app",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@it-tools/app",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@astrojs/solid-js": "^5.1.0",
|
||||
"@kobalte/core": "^0.13.10",
|
||||
"astro": "^5.12.0",
|
||||
"astro-pagefind": "^1.8.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"solid-js": "^1.9.6",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"unocss-preset-animations": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/solar": "^1.2.2",
|
||||
"@iconify-json/tabler": "^1.2.19",
|
||||
"@unocss/reset": "^66.3.3",
|
||||
"unocss": "66.1.0-beta.13",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
},
|
||||
"../../node_modules/.pnpm/@astrojs+solid-js@5.1.0_@types+node@24.0.14_jiti@2.4.2_solid-js@1.9.6/node_modules/@astrojs/solid-js": {
|
||||
"version": "5.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-solid": "^2.11.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "5.8.0",
|
||||
"astro-scripts": "0.0.14",
|
||||
"solid-js": "^1.9.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18.20.8 || ^20.3.0 || >=22.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-devtools": "^0.30.1",
|
||||
"solid-js": "^1.8.5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"solid-devtools": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"../../node_modules/.pnpm/@iconify-json+solar@1.2.2/node_modules/@iconify-json/solar": {
|
||||
"version": "1.2.2",
|
||||
"dev": true,
|
||||
"license": "CC-BY-4.0",
|
||||
"dependencies": {
|
||||
"@iconify/types": "*"
|
||||
}
|
||||
},
|
||||
"../../node_modules/.pnpm/@iconify-json+tabler@1.2.19/node_modules/@iconify-json/tabler": {
|
||||
"version": "1.2.19",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iconify/types": "*"
|
||||
}
|
||||
},
|
||||
"../../node_modules/.pnpm/@unocss+reset@66.3.3/node_modules/@unocss/reset": {
|
||||
"version": "66.3.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@csstools/normalize.css": "^12.1.1",
|
||||
"sanitize.css": "^13.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"../../node_modules/.pnpm/astro-pagefind@1.8.3_astro@5.12.0_@types+node@24.0.14_jiti@2.4.2_rollup@4.40.1_typescript@5.8.3_/node_modules/astro-pagefind": {
|
||||
"version": "1.8.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pagefind/default-ui": "^1.2.0",
|
||||
"pagefind": "^1.2.0",
|
||||
"sirv": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "0.9.4",
|
||||
"@astrojs/markdown-remark": "6.3.0",
|
||||
"@semantic-release/changelog": "6.0.3",
|
||||
"@semantic-release/git": "10.0.1",
|
||||
"@types/semantic-release": "20.0.6",
|
||||
"astro": "5.5.2",
|
||||
"semantic-release": "24.2.3",
|
||||
"typescript": "5.8.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"astro": "^2.0.4 || ^3 || ^4 || ^5"
|
||||
}
|
||||
},
|
||||
"../../node_modules/.pnpm/astro@5.12.0_@types+node@24.0.14_jiti@2.4.2_rollup@4.40.1_typescript@5.8.3/node_modules/astro": {
|
||||
"version": "5.12.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@astrojs/compiler": "^2.12.2",
|
||||
"@astrojs/internal-helpers": "0.6.1",
|
||||
"@astrojs/markdown-remark": "6.3.3",
|
||||
"@astrojs/telemetry": "3.3.0",
|
||||
"@capsizecss/unpack": "^2.4.0",
|
||||
"@oslojs/encoding": "^1.1.0",
|
||||
"@rollup/pluginutils": "^5.1.4",
|
||||
"acorn": "^8.14.1",
|
||||
"aria-query": "^5.3.2",
|
||||
"axobject-query": "^4.1.0",
|
||||
"boxen": "8.0.1",
|
||||
"ci-info": "^4.2.0",
|
||||
"clsx": "^2.1.1",
|
||||
"common-ancestor-path": "^1.0.1",
|
||||
"cookie": "^1.0.2",
|
||||
"cssesc": "^3.0.0",
|
||||
"debug": "^4.4.0",
|
||||
"deterministic-object-hash": "^2.0.2",
|
||||
"devalue": "^5.1.1",
|
||||
"diff": "^5.2.0",
|
||||
"dlv": "^1.1.3",
|
||||
"dset": "^3.1.4",
|
||||
"es-module-lexer": "^1.6.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"estree-walker": "^3.0.3",
|
||||
"flattie": "^1.1.1",
|
||||
"fontace": "~0.3.0",
|
||||
"github-slugger": "^2.0.0",
|
||||
"html-escaper": "3.0.3",
|
||||
"http-cache-semantics": "^4.1.1",
|
||||
"import-meta-resolve": "^4.1.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"kleur": "^4.1.5",
|
||||
"magic-string": "^0.30.17",
|
||||
"magicast": "^0.3.5",
|
||||
"mrmime": "^2.0.1",
|
||||
"neotraverse": "^0.6.18",
|
||||
"p-limit": "^6.2.0",
|
||||
"p-queue": "^8.1.0",
|
||||
"package-manager-detector": "^1.1.0",
|
||||
"picomatch": "^4.0.2",
|
||||
"prompts": "^2.4.2",
|
||||
"rehype": "^13.0.2",
|
||||
"semver": "^7.7.1",
|
||||
"shiki": "^3.2.1",
|
||||
"smol-toml": "^1.3.4",
|
||||
"tinyexec": "^0.3.2",
|
||||
"tinyglobby": "^0.2.12",
|
||||
"tsconfck": "^3.1.5",
|
||||
"ultrahtml": "^1.6.0",
|
||||
"unifont": "~0.5.0",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"unstorage": "^1.15.0",
|
||||
"vfile": "^6.0.3",
|
||||
"vite": "^6.3.4",
|
||||
"vitefu": "^1.0.6",
|
||||
"xxhash-wasm": "^1.1.0",
|
||||
"yargs-parser": "^21.1.1",
|
||||
"yocto-spinner": "^0.2.1",
|
||||
"zod": "^3.24.2",
|
||||
"zod-to-json-schema": "^3.24.5",
|
||||
"zod-to-ts": "^1.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"astro": "astro.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@types/aria-query": "^5.0.4",
|
||||
"@types/common-ancestor-path": "^1.0.2",
|
||||
"@types/cssesc": "^3.0.2",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/diff": "^5.2.3",
|
||||
"@types/dlv": "^1.1.5",
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/html-escaper": "3.0.4",
|
||||
"@types/http-cache-semantics": "^4.0.4",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/picomatch": "^3.0.2",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/yargs-parser": "^21.0.3",
|
||||
"astro-scripts": "0.0.14",
|
||||
"cheerio": "1.0.0",
|
||||
"eol": "^0.10.0",
|
||||
"execa": "^8.0.1",
|
||||
"expect-type": "^1.2.0",
|
||||
"fs-fixture": "^2.7.1",
|
||||
"mdast-util-mdx": "^3.0.0",
|
||||
"mdast-util-mdx-jsx": "^3.2.0",
|
||||
"node-mocks-http": "^1.16.2",
|
||||
"parse-srcset": "^1.0.2",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"rehype-toc": "^3.0.2",
|
||||
"remark-code-titles": "^0.1.2",
|
||||
"rollup": "^4.37.0",
|
||||
"sass": "^1.86.0",
|
||||
"typescript": "^5.8.3",
|
||||
"undici": "^7.5.0",
|
||||
"unified": "^11.0.5",
|
||||
"vitest": "^3.0.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18.20.8 || ^20.3.0 || >=22.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/astrodotbuild"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"sharp": "^0.33.3"
|
||||
}
|
||||
},
|
||||
"../../node_modules/.pnpm/class-variance-authority@0.7.1/node_modules/class-variance-authority": {
|
||||
"version": "0.7.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/cli": "0.3.12",
|
||||
"@swc/core": "1.4.16",
|
||||
"@types/node": "20.12.7",
|
||||
"@types/react": "18.2.79",
|
||||
"@types/react-dom": "18.2.25",
|
||||
"bundlesize": "0.18.2",
|
||||
"npm-run-all": "4.1.5",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.4.5"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://polar.sh/cva"
|
||||
}
|
||||
},
|
||||
"../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"esm": "3.2.25",
|
||||
"terser": "4.8.0",
|
||||
"uvu": "0.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"../../node_modules/.pnpm/solid-js@1.9.6/node_modules/solid-js": {
|
||||
"version": "1.9.6",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.1.0",
|
||||
"seroval": "^1.1.0",
|
||||
"seroval-plugins": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"../../node_modules/.pnpm/tailwind-merge@3.2.0/node_modules/tailwind-merge": {
|
||||
"version": "3.2.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.10",
|
||||
"@babel/preset-env": "^7.26.9",
|
||||
"@codspeed/vitest-plugin": "^4.0.1",
|
||||
"@rollup/plugin-babel": "^6.0.4",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@vitest/coverage-v8": "^3.1.1",
|
||||
"@vitest/eslint-plugin": "^1.1.38",
|
||||
"babel-plugin-annotate-pure-calls": "^0.5.0",
|
||||
"babel-plugin-polyfill-regenerator": "^0.6.4",
|
||||
"eslint": "^9.23.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"globby": "^11.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"rollup": "^4.38.0",
|
||||
"rollup-plugin-delete": "^3.0.1",
|
||||
"rollup-plugin-dts": "^6.2.1",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.29.0",
|
||||
"vitest": "^3.1.1",
|
||||
"zx": "^8.4.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"../../node_modules/.pnpm/unocss-preset-animations@1.2.1_unocss@66.1.0-beta.13_postcss@8.5.3_vite@6.3.4_@types+node@24._pi5ihopp7lbxzhd777cycsuybi/node_modules/unocss-preset-animations": {
|
||||
"version": "1.2.1",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@aelita-dev/eslint-config": "3.19.0",
|
||||
"@iconify/json": "^2.2.330",
|
||||
"@types/dom-view-transitions": "^1.0.6",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/node": "^20.17.30",
|
||||
"@unocss/core": "^66.0.0",
|
||||
"@unocss/eslint-plugin": "^66.0.0",
|
||||
"@unocss/preset-mini": "^66.0.0",
|
||||
"@vitest/coverage-v8": "^3.1.2",
|
||||
"@vitest/eslint-plugin": "^1.1.43",
|
||||
"@vue/language-server": "^2.2.10",
|
||||
"bumpp": "^10.1.0",
|
||||
"bundle-require": "^5.1.0",
|
||||
"changelogithub": "^13.13.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint-import-resolver-typescript": "^4.3.4",
|
||||
"eslint-plugin-import-x": "^4.10.6",
|
||||
"eslint-plugin-vue": "^10.0.0",
|
||||
"eslint-plugin-vuejs-accessibility": "^2.4.1",
|
||||
"eslint-processor-vue-blocks": "^2.0.0",
|
||||
"lint-staged": "^15.5.1",
|
||||
"markdown-it": "^14.1.0",
|
||||
"sass-embedded": "^1.87.0",
|
||||
"simple-git-hooks": "^2.12.1",
|
||||
"typescript": "~5.8.3",
|
||||
"unbuild": "3.5.0",
|
||||
"unocss": "^66.0.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitepress": "1.6.3",
|
||||
"vitest": "^3.1.2",
|
||||
"vue": "^3.5.13",
|
||||
"vue-eslint-parser": "^10.1.3",
|
||||
"vue-tsc": "^2.2.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@unocss/preset-wind3": ">=0.56.0 < 101",
|
||||
"unocss": ">=0.56.0 < 101"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@unocss/preset-wind3": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"../../node_modules/.pnpm/unocss@66.1.0-beta.13_postcss@8.5.3_vite@6.3.4_@types+node@24.0.14_jiti@2.4.2__vue@3.5.13_typescript@5.8.3_/node_modules/unocss": {
|
||||
"version": "66.1.0-beta.13",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unocss/astro": "66.1.0-beta.13",
|
||||
"@unocss/cli": "66.1.0-beta.13",
|
||||
"@unocss/core": "66.1.0-beta.13",
|
||||
"@unocss/postcss": "66.1.0-beta.13",
|
||||
"@unocss/preset-attributify": "66.1.0-beta.13",
|
||||
"@unocss/preset-icons": "66.1.0-beta.13",
|
||||
"@unocss/preset-mini": "66.1.0-beta.13",
|
||||
"@unocss/preset-tagify": "66.1.0-beta.13",
|
||||
"@unocss/preset-typography": "66.1.0-beta.13",
|
||||
"@unocss/preset-uno": "66.1.0-beta.13",
|
||||
"@unocss/preset-web-fonts": "66.1.0-beta.13",
|
||||
"@unocss/preset-wind": "66.1.0-beta.13",
|
||||
"@unocss/preset-wind3": "66.1.0-beta.13",
|
||||
"@unocss/preset-wind4": "66.1.0-beta.13",
|
||||
"@unocss/transformer-attributify-jsx": "66.1.0-beta.13",
|
||||
"@unocss/transformer-compile-class": "66.1.0-beta.13",
|
||||
"@unocss/transformer-directives": "66.1.0-beta.13",
|
||||
"@unocss/transformer-variant-group": "66.1.0-beta.13",
|
||||
"@unocss/vite": "66.1.0-beta.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@unocss/webpack": "66.1.0-beta.13",
|
||||
"vite": "^6.2.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@unocss/webpack": "66.1.0-beta.13",
|
||||
"vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@unocss/webpack": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"../../node_modules/.pnpm/vitest@3.2.4_@types+debug@4.1.12_@types+node@24.0.14_jiti@2.4.2/node_modules/vitest": {
|
||||
"version": "3.2.4",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.4",
|
||||
"@vitest/mocker": "3.2.4",
|
||||
"@vitest/pretty-format": "^3.2.4",
|
||||
"@vitest/runner": "3.2.4",
|
||||
"@vitest/snapshot": "3.2.4",
|
||||
"@vitest/spy": "3.2.4",
|
||||
"@vitest/utils": "3.2.4",
|
||||
"chai": "^5.2.0",
|
||||
"debug": "^4.4.1",
|
||||
"expect-type": "^1.2.1",
|
||||
"magic-string": "^0.30.17",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.2",
|
||||
"std-env": "^3.9.0",
|
||||
"tinybench": "^2.9.0",
|
||||
"tinyexec": "^0.3.2",
|
||||
"tinyglobby": "^0.2.14",
|
||||
"tinypool": "^1.1.1",
|
||||
"tinyrainbow": "^2.0.0",
|
||||
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
|
||||
"vite-node": "3.2.4",
|
||||
"why-is-node-running": "^2.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"vitest": "vitest.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ampproject/remapping": "^2.3.0",
|
||||
"@antfu/install-pkg": "^1.1.0",
|
||||
"@edge-runtime/vm": "^5.0.0",
|
||||
"@sinonjs/fake-timers": "14.0.0",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/estree": "^1.0.8",
|
||||
"@types/istanbul-lib-coverage": "^2.0.6",
|
||||
"@types/istanbul-reports": "^3.0.4",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/mime": "^4.0.0",
|
||||
"@types/node": "^22.15.32",
|
||||
"@types/picomatch": "^4.0.0",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@types/sinonjs__fake-timers": "^8.1.5",
|
||||
"acorn-walk": "^8.3.4",
|
||||
"birpc": "2.4.0",
|
||||
"cac": "^6.7.14",
|
||||
"chai-subset": "^1.6.0",
|
||||
"find-up": "^6.3.0",
|
||||
"flatted": "^3.3.3",
|
||||
"happy-dom": "^17.6.3",
|
||||
"jsdom": "^26.1.0",
|
||||
"local-pkg": "^1.1.1",
|
||||
"mime": "^4.0.7",
|
||||
"pretty-format": "^29.7.0",
|
||||
"prompts": "^2.4.2",
|
||||
"strip-literal": "^3.0.0",
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edge-runtime/vm": "*",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
||||
"@vitest/browser": "3.2.4",
|
||||
"@vitest/ui": "3.2.4",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edge-runtime/vm": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/debug": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/ui": {
|
||||
"optional": true
|
||||
},
|
||||
"happy-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"jsdom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/solid-js": {
|
||||
"resolved": "../../node_modules/.pnpm/@astrojs+solid-js@5.1.0_@types+node@24.0.14_jiti@2.4.2_solid-js@1.9.6/node_modules/@astrojs/solid-js",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@corvu/utils": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@corvu/utils/-/utils-0.4.2.tgz",
|
||||
"integrity": "sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz",
|
||||
"integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz",
|
||||
"integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.7.2",
|
||||
"@floating-ui/utils": "^0.2.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
|
||||
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@iconify-json/solar": {
|
||||
"resolved": "../../node_modules/.pnpm/@iconify-json+solar@1.2.2/node_modules/@iconify-json/solar",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@iconify-json/tabler": {
|
||||
"resolved": "../../node_modules/.pnpm/@iconify-json+tabler@1.2.19/node_modules/@iconify-json/tabler",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@internationalized/date": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.8.2.tgz",
|
||||
"integrity": "sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@internationalized/number": {
|
||||
"version": "3.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.3.tgz",
|
||||
"integrity": "sha512-p+Zh1sb6EfrfVaS86jlHGQ9HA66fJhV9x5LiE5vCbZtXEHAuhcmUZUdZ4WrFpUBfNalr2OkAJI5AcKEQF+Lebw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@kobalte/core": {
|
||||
"version": "0.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@kobalte/core/-/core-0.13.10.tgz",
|
||||
"integrity": "sha512-lzP64ThxZqZB6O6MnMq6w7DxK38o2ClbW3Ob6afUI6p86cUMz5Hb4rdysvYI6m1TKYlOAlFODKkoRznqybQohw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.5.1",
|
||||
"@internationalized/date": "^3.4.0",
|
||||
"@internationalized/number": "^3.2.1",
|
||||
"@kobalte/utils": "^0.9.1",
|
||||
"@solid-primitives/props": "^3.1.8",
|
||||
"@solid-primitives/resize-observer": "^2.0.26",
|
||||
"solid-presence": "^0.1.8",
|
||||
"solid-prevent-scroll": "^0.1.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.8.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@kobalte/utils": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@kobalte/utils/-/utils-0.9.1.tgz",
|
||||
"integrity": "sha512-eeU60A3kprIiBDAfv9gUJX1tXGLuZiKMajUfSQURAF2pk4ZoMYiqIzmrMBvzcxP39xnYttgTyQEVLwiTZnrV4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/event-listener": "^2.2.14",
|
||||
"@solid-primitives/keyed": "^1.2.0",
|
||||
"@solid-primitives/map": "^0.4.7",
|
||||
"@solid-primitives/media": "^2.2.4",
|
||||
"@solid-primitives/props": "^3.1.8",
|
||||
"@solid-primitives/refs": "^1.0.5",
|
||||
"@solid-primitives/utils": "^6.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.8.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/event-listener": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/event-listener/-/event-listener-2.4.3.tgz",
|
||||
"integrity": "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/utils": "^6.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/keyed": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/keyed/-/keyed-1.5.2.tgz",
|
||||
"integrity": "sha512-BgoEdqPw48URnI+L5sZIHdF4ua4Las1eWEBBPaoSFs42kkhnHue+rwCBPL2Z9ebOyQ75sUhUfOETdJfmv0D6Kg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/map": {
|
||||
"version": "0.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/map/-/map-0.4.13.tgz",
|
||||
"integrity": "sha512-B1zyFbsiTQvqPr+cuPCXO72sRuczG9Swncqk5P74NCGw1VE8qa/Ry9GlfI1e/VdeQYHjan+XkbE3rO2GW/qKew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/trigger": "^1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/media": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/media/-/media-2.3.3.tgz",
|
||||
"integrity": "sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/event-listener": "^2.4.3",
|
||||
"@solid-primitives/rootless": "^1.5.2",
|
||||
"@solid-primitives/static-store": "^0.1.2",
|
||||
"@solid-primitives/utils": "^6.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/props": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/props/-/props-3.2.2.tgz",
|
||||
"integrity": "sha512-lZOTwFJajBrshSyg14nBMEP0h8MXzPowGO0s3OeiR3z6nXHTfj0FhzDtJMv+VYoRJKQHG2QRnJTgCzK6erARAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/utils": "^6.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/refs": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/refs/-/refs-1.1.2.tgz",
|
||||
"integrity": "sha512-K7tf2thy7L+YJjdqXspXOg5xvNEOH8tgEWsp0+1mQk3obHBRD6hEjYZk7p7FlJphSZImS35je3UfmWuD7MhDfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/utils": "^6.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/resize-observer": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/resize-observer/-/resize-observer-2.1.3.tgz",
|
||||
"integrity": "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/event-listener": "^2.4.3",
|
||||
"@solid-primitives/rootless": "^1.5.2",
|
||||
"@solid-primitives/static-store": "^0.1.2",
|
||||
"@solid-primitives/utils": "^6.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/rootless": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/rootless/-/rootless-1.5.2.tgz",
|
||||
"integrity": "sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/utils": "^6.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/static-store": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/static-store/-/static-store-0.1.2.tgz",
|
||||
"integrity": "sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/utils": "^6.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/trigger": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/trigger/-/trigger-1.2.2.tgz",
|
||||
"integrity": "sha512-IWoptVc0SWYgmpBPpCMehS5b07+tpFcvw15tOQ3QbXedSYn6KP8zCjPkHNzMxcOvOicTneleeZDP7lqmz+PQ6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/utils": "^6.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/utils": {
|
||||
"version": "6.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.3.2.tgz",
|
||||
"integrity": "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
||||
"integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@unocss/reset": {
|
||||
"resolved": "../../node_modules/.pnpm/@unocss+reset@66.3.3/node_modules/@unocss/reset",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/astro": {
|
||||
"resolved": "../../node_modules/.pnpm/astro@5.12.0_@types+node@24.0.14_jiti@2.4.2_rollup@4.40.1_typescript@5.8.3/node_modules/astro",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/astro-pagefind": {
|
||||
"resolved": "../../node_modules/.pnpm/astro-pagefind@1.8.3_astro@5.12.0_@types+node@24.0.14_jiti@2.4.2_rollup@4.40.1_typescript@5.8.3_/node_modules/astro-pagefind",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/class-variance-authority": {
|
||||
"resolved": "../../node_modules/.pnpm/class-variance-authority@0.7.1/node_modules/class-variance-authority",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"resolved": "../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/solid-js": {
|
||||
"resolved": "../../node_modules/.pnpm/solid-js@1.9.6/node_modules/solid-js",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/solid-presence": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/solid-presence/-/solid-presence-0.1.8.tgz",
|
||||
"integrity": "sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@corvu/utils": "~0.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/solid-prevent-scroll": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/solid-prevent-scroll/-/solid-prevent-scroll-0.1.10.tgz",
|
||||
"integrity": "sha512-KplGPX2GHiWJLZ6AXYRql4M127PdYzfwvLJJXMkO+CMb8Np4VxqDAg5S8jLdwlEuBis/ia9DKw2M8dFx5u8Mhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@corvu/utils": "~0.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"resolved": "../../node_modules/.pnpm/tailwind-merge@3.2.0/node_modules/tailwind-merge",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/unocss": {
|
||||
"resolved": "../../node_modules/.pnpm/unocss@66.1.0-beta.13_postcss@8.5.3_vite@6.3.4_@types+node@24.0.14_jiti@2.4.2__vue@3.5.13_typescript@5.8.3_/node_modules/unocss",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/unocss-preset-animations": {
|
||||
"resolved": "../../node_modules/.pnpm/unocss-preset-animations@1.2.1_unocss@66.1.0-beta.13_postcss@8.5.3_vite@6.3.4_@types+node@24._pi5ihopp7lbxzhd777cycsuybi/node_modules/unocss-preset-animations",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"resolved": "../../node_modules/.pnpm/vitest@3.2.4_@types+debug@4.1.12_@types+node@24.0.14_jiti@2.4.2/node_modules/vitest",
|
||||
"link": true
|
||||
}
|
||||
}
|
||||
}
|
31
apps/it-tools/package.json
Normal file
31
apps/it-tools/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@it-tools/app",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/solid-js": "^5.1.0",
|
||||
"@kobalte/core": "^0.13.10",
|
||||
"astro": "^5.12.0",
|
||||
"astro-pagefind": "^1.8.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"solid-js": "^1.9.6",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"unocss-preset-animations": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/solar": "^1.2.2",
|
||||
"@iconify-json/tabler": "^1.2.19",
|
||||
"@unocss/reset": "^66.3.3",
|
||||
"unocss": "66.1.0-beta.13",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
BIN
apps/it-tools/public/favicon.ico
Normal file
BIN
apps/it-tools/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
9
apps/it-tools/public/favicon.svg
Normal file
9
apps/it-tools/public/favicon.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
After Width: | Height: | Size: 749 B |
75
apps/it-tools/src/assets/app.css
Normal file
75
apps/it-tools/src/assets/app.css
Normal file
@@ -0,0 +1,75 @@
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
[data-kb-theme="dark"] {
|
||||
--background: 0 0% 6%;
|
||||
--foreground: 0 0% 96%;
|
||||
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
|
||||
--primary: 0 0% 96%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
}
|
||||
|
||||
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
|
||||
|
||||
[data-kb-theme="dark"] .astro-code,
|
||||
[data-kb-theme="dark"] .astro-code span {
|
||||
background-color: var(--shiki-dark-bg) !important;
|
||||
}
|
103
apps/it-tools/src/components/footer.astro
Normal file
103
apps/it-tools/src/components/footer.astro
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
import { cn } from '@/libs/cn';
|
||||
|
||||
const socials = [
|
||||
{
|
||||
label: 'Bluesky',
|
||||
url: 'https://bsky.app/profile/it-tools.tech',
|
||||
icon: 'i-tabler-brand-bluesky',
|
||||
},
|
||||
{
|
||||
label: 'GitHub',
|
||||
url: 'https://github.com/CorentinTh/it-tools',
|
||||
icon: 'i-tabler-brand-github',
|
||||
},
|
||||
{
|
||||
label: 'X (Twitter)',
|
||||
url: 'https://x.com/ittoolsdottech',
|
||||
icon: 'i-tabler-brand-x',
|
||||
},
|
||||
|
||||
|
||||
];
|
||||
|
||||
const sections: {
|
||||
title: string;
|
||||
links: { label: string; url: string; target?: string; rel?: string }[];
|
||||
}[] = [
|
||||
{
|
||||
title: 'Community',
|
||||
links: socials,
|
||||
},
|
||||
{
|
||||
title: 'Open Source',
|
||||
links: [
|
||||
{
|
||||
label: 'Repository',
|
||||
url: 'https://github.com/CorentinTh/it-tools',
|
||||
},
|
||||
{
|
||||
label: 'Contributing',
|
||||
url: 'https://github.com/CorentinTh/it-tools/blob/main/CONTRIBUTING.md',
|
||||
},
|
||||
{
|
||||
label: 'Code of Conduct',
|
||||
url: 'https://github.com/CorentinTh/it-tools/blob/main/CODE_OF_CONDUCT.md',
|
||||
},
|
||||
{
|
||||
label: 'License',
|
||||
url: 'https://github.com/CorentinTh/it-tools/blob/main/LICENSE',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<footer class="bg-card border-t border-border py-8 text-muted-foreground light:bg-muted">
|
||||
<div class="max-w-screen-lg mx-auto p-6">
|
||||
<div class="flex justify-between flex-col md:flex-row gap-10">
|
||||
<div>
|
||||
<a href="/" class="text-xl inline-flex items-center group mb-2">
|
||||
<div class="i-solar-programming-line-duotone size-7 text-primary group-hover:(rotate-12deg) transition transform"></div>
|
||||
<span class="ml-2 text-foreground group-hover:text-foreground/80 transition">IT-Tools</span>
|
||||
</a>
|
||||
|
||||
<div class="flex gap-2">
|
||||
{
|
||||
socials.map(social => (
|
||||
<a href={social.url} class="hover:text-primary transition" target="_blank" rel="noopener noreferrer" aria-label={social.label}>
|
||||
<div class={`${social.icon} text-2xl`} aria-hidden="true" />
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<p class="mt-4 text-sm max-w-420px">
|
||||
IT-Tools is made in Europe with <span class="i-tabler-heart-filled size-3.5 mb--0.3 text-primary inline-block"></span>
|
||||
by <a href="https://corentin.tech" class="text-primary border-b hover:border-b-primary transition">Corentin Thomasset</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class={cn('grid gap-10 grid-cols-1', `sm:grid-cols-${sections.length}`)}>
|
||||
{
|
||||
sections.map(section => (
|
||||
<div>
|
||||
<div class="text-foreground font-semibold">{section.title}</div>
|
||||
<div class="mt-2">
|
||||
{section.links.map(link => (
|
||||
<a href={link.url} class="block hover:text-primary transition py-0.75 font-medium" target={link.target} rel={link.rel}>
|
||||
{link.label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 border-t border-border pt-4">
|
||||
© {new Date().getFullYear()} IT-Tools. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
60
apps/it-tools/src/components/header.astro
Normal file
60
apps/it-tools/src/components/header.astro
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
import { LanguagePicker } from "./language-picker";
|
||||
import { Button } from "./ui/button";
|
||||
|
||||
const { lang } = Astro.params;
|
||||
|
||||
---
|
||||
|
||||
<header class="fixed top-0 w-full z-50 bg-background/50 backdrop-blur-sm">
|
||||
<div class="max-w-screen-lg mx-auto px-6 py-3 flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<a href={lang ? `/${lang}` : "/"} class="flex items-center gap-2 group">
|
||||
<span class="i-solar-programming-line-duotone flex-shrink-0 size-6 group-hover:rotate-12 transition-transform"></span>
|
||||
<span class="font-semibold">IT-Tools</span>
|
||||
</a>
|
||||
|
||||
<span class="text-sm text-muted-foreground font-medium hidden sm:block">
|
||||
Handy online tools.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1">
|
||||
<Button variant="ghost" size="icon" class="size-8">
|
||||
<span class="i-tabler-search flex-shrink-0 size-4"></span>
|
||||
</Button>
|
||||
|
||||
<Button variant="ghost" size="icon" class="size-8 toggle-theme">
|
||||
<span class="i-tabler-moon flex-shrink-0 hidden dark:block size-4"></span>
|
||||
<span class="i-tabler-sun flex-shrink-0 block dark:hidden size-4"></span>
|
||||
</Button>
|
||||
|
||||
<LanguagePicker client:load />
|
||||
|
||||
<Button variant="ghost" size="icon" class="size-8" as="a" href="https://github.com/CorentinTh/it-tools" target="_blank" rel="noopener noreferrer">
|
||||
<span class="i-tabler-brand-github flex-shrink-0 size-4"></span>
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" size="sm" as="a" href="https://www.buymeacoffee.com/cthmsst" target="_blank" rel="noopener noreferrer">
|
||||
Buy me a coffee
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
<script>
|
||||
const themeButtons = document.querySelectorAll("button.toggle-theme");
|
||||
|
||||
themeButtons.forEach(button => {
|
||||
button.addEventListener("click", () => {
|
||||
const localStorageTheme = localStorage.getItem("it-tools-theme");
|
||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
const theme = localStorageTheme ?? systemTheme;
|
||||
const newTheme = theme === "dark" ? "light" : "dark";
|
||||
|
||||
localStorage.setItem("it-tools-theme", newTheme);
|
||||
document.documentElement.setAttribute("data-kb-theme", newTheme);
|
||||
});
|
||||
});
|
||||
</script>
|
30
apps/it-tools/src/components/language-picker.tsx
Normal file
30
apps/it-tools/src/components/language-picker.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./ui/dropdown-menu";
|
||||
import type { DropdownMenuSubTriggerProps } from "@kobalte/core/dropdown-menu";
|
||||
import { languages } from "@/i18n/languages";
|
||||
import { navigate } from "astro:transitions/client";
|
||||
|
||||
export const LanguagePicker = () => {
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<DropdownMenu placement="bottom">
|
||||
<DropdownMenuTrigger
|
||||
as={(props: DropdownMenuSubTriggerProps) => (
|
||||
<Button variant="ghost" size="icon" {...props}>
|
||||
<span class="i-tabler-language flex-shrink-0 size-4"></span>
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
<DropdownMenuContent class="w-42">
|
||||
{Object.entries(languages).map(([locale, name]) => (
|
||||
<DropdownMenuItem as="a" href={`/${locale}`}>
|
||||
{name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
)
|
||||
}
|
24
apps/it-tools/src/components/tool-card.astro
Normal file
24
apps/it-tools/src/components/tool-card.astro
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
import { buildLocalizedUrl } from "@/i18n/i18n.models";
|
||||
import { cn } from "@/libs/cn";
|
||||
|
||||
export type Props = {
|
||||
name: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
const { slug, name, description, icon }: Props = Astro.props;
|
||||
const {lang} = Astro.params;
|
||||
---
|
||||
|
||||
<a href={buildLocalizedUrl({lang, path: slug})} class="border rounded-lg p-5 hover:bg-muted transition-colors flex flex-col gap-2">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="size-10 flex-shrink-0 bg-muted rounded-lg flex items-center justify-center">
|
||||
<span class={cn(icon, "size-6 text-muted-foreground")}></span>
|
||||
</span>
|
||||
<h3 class="text-lg font-semibold truncate">{name}</h3>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">{description}</p>
|
||||
</a>
|
66
apps/it-tools/src/components/ui/button.tsx
Normal file
66
apps/it-tools/src/components/ui/button.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { cn } from "@/libs/cn";
|
||||
import type { ButtonRootProps } from "@kobalte/core/button";
|
||||
import { Button as ButtonPrimitive } from "@kobalte/core/button";
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
import type { VariantProps } from "class-variance-authority";
|
||||
import { cva } from "class-variance-authority";
|
||||
import type { ValidComponent } from "solid-js";
|
||||
import { splitProps } from "solid-js";
|
||||
|
||||
export const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-shadow focus-visible:(outline-none ring-1.5 ring-ring) disabled:(pointer-events-none opacity-50) bg-inherit",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||
ghost: "hover:(bg-accent text-accent-foreground)",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2",
|
||||
sm: "h-8 px-3 text-xs",
|
||||
lg: "h-10 px-8",
|
||||
icon: "h-9 w-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
type buttonProps<T extends ValidComponent = "button"> = ButtonRootProps<T> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const Button = <T extends ValidComponent = "button">(
|
||||
props: PolymorphicProps<T, buttonProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as buttonProps, [
|
||||
"class",
|
||||
"variant",
|
||||
"size",
|
||||
]);
|
||||
|
||||
return (
|
||||
<ButtonPrimitive
|
||||
class={cn(
|
||||
buttonVariants({
|
||||
size: local.size,
|
||||
variant: local.variant,
|
||||
}),
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
320
apps/it-tools/src/components/ui/dropdown-menu.tsx
Normal file
320
apps/it-tools/src/components/ui/dropdown-menu.tsx
Normal file
@@ -0,0 +1,320 @@
|
||||
import { cn } from "@/libs/cn";
|
||||
import type {
|
||||
DropdownMenuCheckboxItemProps,
|
||||
DropdownMenuContentProps,
|
||||
DropdownMenuGroupLabelProps,
|
||||
DropdownMenuItemLabelProps,
|
||||
DropdownMenuItemProps,
|
||||
DropdownMenuRadioItemProps,
|
||||
DropdownMenuRootProps,
|
||||
DropdownMenuSeparatorProps,
|
||||
DropdownMenuSubTriggerProps,
|
||||
} from "@kobalte/core/dropdown-menu";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "@kobalte/core/dropdown-menu";
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
import type { ComponentProps, ParentProps, ValidComponent } from "solid-js";
|
||||
import { mergeProps, splitProps } from "solid-js";
|
||||
|
||||
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
||||
export const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
||||
export const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
||||
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
||||
|
||||
export const DropdownMenu = (props: DropdownMenuRootProps) => {
|
||||
const merge = mergeProps<DropdownMenuRootProps[]>(
|
||||
{
|
||||
gutter: 4,
|
||||
flip: false,
|
||||
},
|
||||
props,
|
||||
);
|
||||
|
||||
return <DropdownMenuPrimitive {...merge} />;
|
||||
};
|
||||
|
||||
type dropdownMenuContentProps<T extends ValidComponent = "div"> =
|
||||
DropdownMenuContentProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const DropdownMenuContent = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, dropdownMenuContentProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as dropdownMenuContentProps, [
|
||||
"class",
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
class={cn(
|
||||
"min-w-8rem z-50 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[expanded]:(animate-in fade-in-0 zoom-in-95) data-[closed]:(animate-out fade-out-0 zoom-out-95) focus-visible:(outline-none ring-1.5 ring-ring) transition-shadow",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
|
||||
type dropdownMenuItemProps<T extends ValidComponent = "div"> =
|
||||
DropdownMenuItemProps<T> & {
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
};
|
||||
|
||||
export const DropdownMenuItem = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, dropdownMenuItemProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as dropdownMenuItemProps, [
|
||||
"class",
|
||||
"inset",
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.Item
|
||||
class={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:(bg-accent text-accent-foreground) data-[disabled]:(pointer-events-none opacity-50)",
|
||||
local.inset && "pl-8",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type dropdownMenuGroupLabelProps<T extends ValidComponent = "span"> =
|
||||
DropdownMenuGroupLabelProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const DropdownMenuGroupLabel = <T extends ValidComponent = "span">(
|
||||
props: PolymorphicProps<T, dropdownMenuGroupLabelProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as dropdownMenuGroupLabelProps, [
|
||||
"class",
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.GroupLabel
|
||||
as="div"
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold", local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type dropdownMenuItemLabelProps<T extends ValidComponent = "div"> =
|
||||
DropdownMenuItemLabelProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const DropdownMenuItemLabel = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, dropdownMenuItemLabelProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as dropdownMenuItemLabelProps, [
|
||||
"class",
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.ItemLabel
|
||||
as="div"
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold", local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type dropdownMenuSeparatorProps<T extends ValidComponent = "hr"> =
|
||||
DropdownMenuSeparatorProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const DropdownMenuSeparator = <T extends ValidComponent = "hr">(
|
||||
props: PolymorphicProps<T, dropdownMenuSeparatorProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as dropdownMenuSeparatorProps, [
|
||||
"class",
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
class={cn("-mx-1 my-1 h-px bg-muted", local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const DropdownMenuShortcut = (props: ComponentProps<"span">) => {
|
||||
const [local, rest] = splitProps(props, ["class"]);
|
||||
|
||||
return (
|
||||
<span
|
||||
class={cn("ml-auto text-xs tracking-widest opacity-60", local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type dropdownMenuSubTriggerProps<T extends ValidComponent = "div"> =
|
||||
ParentProps<
|
||||
DropdownMenuSubTriggerProps<T> & {
|
||||
class?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export const DropdownMenuSubTrigger = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, dropdownMenuSubTriggerProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as dropdownMenuSubTriggerProps, [
|
||||
"class",
|
||||
"children",
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
class={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[expanded]:bg-accent",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{local.children}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
class="ml-auto h-4 w-4"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m9 6l6 6l-6 6"
|
||||
/>
|
||||
<title>Arrow</title>
|
||||
</svg>
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
type dropdownMenuSubContentProps<T extends ValidComponent = "div"> =
|
||||
DropdownMenuSubTriggerProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const DropdownMenuSubContent = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, dropdownMenuSubContentProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as dropdownMenuSubContentProps, [
|
||||
"class",
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
class={cn(
|
||||
"min-w-8rem z-50 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[expanded]:(animate-in fade-in-0 zoom-in-95) data-[closed]:(animate-out fade-out-0 zoom-out-95)",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
|
||||
type dropdownMenuCheckboxItemProps<T extends ValidComponent = "div"> =
|
||||
ParentProps<
|
||||
DropdownMenuCheckboxItemProps<T> & {
|
||||
class?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export const DropdownMenuCheckboxItem = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, dropdownMenuCheckboxItemProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as dropdownMenuCheckboxItemProps, [
|
||||
"class",
|
||||
"children",
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
class={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:(bg-accent text-accent-foreground) data-[disabled]:(pointer-events-none opacity-50)",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<DropdownMenuPrimitive.ItemIndicator class="absolute left-2 inline-flex h-4 w-4 items-center justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="h-4 w-4"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m5 12l5 5L20 7"
|
||||
/>
|
||||
<title>Checkbox</title>
|
||||
</svg>
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
{props.children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
);
|
||||
};
|
||||
|
||||
type dropdownMenuRadioItemProps<T extends ValidComponent = "div"> = ParentProps<
|
||||
DropdownMenuRadioItemProps<T> & {
|
||||
class?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export const DropdownMenuRadioItem = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, dropdownMenuRadioItemProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as dropdownMenuRadioItemProps, [
|
||||
"class",
|
||||
"children",
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
class={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:(bg-accent text-accent-foreground) data-[disabled]:(pointer-events-none opacity-50)",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<DropdownMenuPrimitive.ItemIndicator class="absolute left-2 inline-flex h-4 w-4 items-center justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="h-2 w-2"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M0 0h24v24H0z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M7 3.34a10 10 0 1 1-4.995 8.984L2 12l.005-.324A10 10 0 0 1 7 3.34"
|
||||
/>
|
||||
</g>
|
||||
<title>Radio</title>
|
||||
</svg>
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
{props.children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
);
|
||||
};
|
19
apps/it-tools/src/components/ui/label.tsx
Normal file
19
apps/it-tools/src/components/ui/label.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Component, ComponentProps } from "solid-js"
|
||||
import { splitProps } from "solid-js"
|
||||
|
||||
import { cn } from "@/libs/cn"
|
||||
|
||||
const Label: Component<ComponentProps<"label">> = (props) => {
|
||||
const [local, others] = splitProps(props, ["class"])
|
||||
return (
|
||||
<label
|
||||
class={cn(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Label }
|
214
apps/it-tools/src/components/ui/number-field.tsx
Normal file
214
apps/it-tools/src/components/ui/number-field.tsx
Normal file
@@ -0,0 +1,214 @@
|
||||
import { cn } from "@/libs/cn";
|
||||
import type {
|
||||
NumberFieldDecrementTriggerProps,
|
||||
NumberFieldDescriptionProps,
|
||||
NumberFieldErrorMessageProps,
|
||||
NumberFieldIncrementTriggerProps,
|
||||
NumberFieldInputProps,
|
||||
NumberFieldLabelProps,
|
||||
NumberFieldRootProps,
|
||||
} from "@kobalte/core/number-field";
|
||||
import { NumberField as NumberFieldPrimitive } from "@kobalte/core/number-field";
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
import type { ComponentProps, ValidComponent, VoidProps } from "solid-js";
|
||||
import { splitProps } from "solid-js";
|
||||
import { textfieldLabel } from "./textfield";
|
||||
|
||||
export const NumberFieldHiddenInput = NumberFieldPrimitive.HiddenInput;
|
||||
|
||||
type numberFieldLabelProps<T extends ValidComponent = "div"> =
|
||||
NumberFieldLabelProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const NumberFieldLabel = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, numberFieldLabelProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as numberFieldLabelProps, ["class"]);
|
||||
|
||||
return (
|
||||
<NumberFieldPrimitive.Label
|
||||
class={cn(textfieldLabel({ label: true }), local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type numberFieldDescriptionProps<T extends ValidComponent = "div"> =
|
||||
NumberFieldDescriptionProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const NumberFieldDescription = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, numberFieldDescriptionProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as numberFieldDescriptionProps, [
|
||||
"class",
|
||||
]);
|
||||
|
||||
return (
|
||||
<NumberFieldPrimitive.Description
|
||||
class={cn(
|
||||
textfieldLabel({ description: true, label: false }),
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type numberFieldErrorMessageProps<T extends ValidComponent = "div"> =
|
||||
NumberFieldErrorMessageProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const NumberFieldErrorMessage = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, numberFieldErrorMessageProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as numberFieldErrorMessageProps, [
|
||||
"class",
|
||||
]);
|
||||
|
||||
return (
|
||||
<NumberFieldPrimitive.ErrorMessage
|
||||
class={cn(textfieldLabel({ error: true }), local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type numberFieldProps<T extends ValidComponent = "div"> =
|
||||
NumberFieldRootProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const NumberField = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, numberFieldProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as numberFieldProps, ["class"]);
|
||||
|
||||
return (
|
||||
<NumberFieldPrimitive class={cn("grid gap-1.5", local.class)} {...rest} />
|
||||
);
|
||||
};
|
||||
|
||||
export const NumberFieldGroup = (props: ComponentProps<"div">) => {
|
||||
const [local, rest] = splitProps(props, ["class"]);
|
||||
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
"relative focus-within:(outline-none ring-1.5 ring-ring) transition-shadow rounded-md",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type numberFieldInputProps<T extends ValidComponent = "input"> =
|
||||
NumberFieldInputProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const NumberFieldInput = <T extends ValidComponent = "input">(
|
||||
props: PolymorphicProps<T, VoidProps<numberFieldInputProps<T>>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as numberFieldInputProps, ["class"]);
|
||||
|
||||
return (
|
||||
<NumberFieldPrimitive.Input
|
||||
class={cn(
|
||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-10 py-1 text-sm text-center shadow-sm placeholder:text-muted-foreground disabled:(cursor-not-allowed opacity-50) focus-visible:outline-none",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type numberFieldDecrementTriggerProps<T extends ValidComponent = "button"> =
|
||||
VoidProps<
|
||||
NumberFieldDecrementTriggerProps<T> & {
|
||||
class?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export const NumberFieldDecrementTrigger = <
|
||||
T extends ValidComponent = "button",
|
||||
>(
|
||||
props: PolymorphicProps<T, VoidProps<numberFieldDecrementTriggerProps<T>>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as numberFieldDecrementTriggerProps, [
|
||||
"class",
|
||||
]);
|
||||
|
||||
return (
|
||||
<NumberFieldPrimitive.DecrementTrigger
|
||||
class={cn(
|
||||
"absolute top-1/2 -translate-y-1/2 left-0 p-3 disabled:(cursor-not-allowed opacity-20)",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="size-4"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 12h14"
|
||||
/>
|
||||
<title>Decrease number</title>
|
||||
</svg>
|
||||
</NumberFieldPrimitive.DecrementTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
type numberFieldIncrementTriggerProps<T extends ValidComponent = "button"> =
|
||||
VoidProps<
|
||||
NumberFieldIncrementTriggerProps<T> & {
|
||||
class?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export const NumberFieldIncrementTrigger = <
|
||||
T extends ValidComponent = "button",
|
||||
>(
|
||||
props: PolymorphicProps<T, numberFieldIncrementTriggerProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as numberFieldIncrementTriggerProps, [
|
||||
"class",
|
||||
]);
|
||||
|
||||
return (
|
||||
<NumberFieldPrimitive.IncrementTrigger
|
||||
class={cn(
|
||||
"absolute top-1/2 -translate-y-1/2 right-0 disabled:(cursor-not-allowed opacity-20) p-3",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="size-4"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 5v14m-7-7h14"
|
||||
/>
|
||||
<title>Increase number</title>
|
||||
</svg>
|
||||
</NumberFieldPrimitive.IncrementTrigger>
|
||||
);
|
||||
};
|
92
apps/it-tools/src/components/ui/slider.tsx
Normal file
92
apps/it-tools/src/components/ui/slider.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import type { JSX, ValidComponent } from "solid-js"
|
||||
import { splitProps } from "solid-js"
|
||||
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic"
|
||||
import * as SliderPrimitive from "@kobalte/core/slider"
|
||||
|
||||
import { cn } from "@/libs/cn";
|
||||
import { Label } from "@/components/ui/label"
|
||||
|
||||
type SliderRootProps<T extends ValidComponent = "div"> = SliderPrimitive.SliderRootProps<T> & {
|
||||
class?: string | undefined
|
||||
}
|
||||
|
||||
const Slider = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, SliderRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SliderRootProps, ["class"])
|
||||
return (
|
||||
<SliderPrimitive.Root
|
||||
class={cn("relative flex w-full touch-none select-none flex-col items-center", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SliderTrackProps<T extends ValidComponent = "div"> = SliderPrimitive.SliderTrackProps<T> & {
|
||||
class?: string | undefined
|
||||
}
|
||||
|
||||
const SliderTrack = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, SliderTrackProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SliderTrackProps, ["class"])
|
||||
return (
|
||||
<SliderPrimitive.Track
|
||||
class={cn("relative h-2 w-full grow rounded-full bg-secondary", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SliderFillProps<T extends ValidComponent = "div"> = SliderPrimitive.SliderFillProps<T> & {
|
||||
class?: string | undefined
|
||||
}
|
||||
|
||||
const SliderFill = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, SliderFillProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SliderFillProps, ["class"])
|
||||
return (
|
||||
<SliderPrimitive.Fill
|
||||
class={cn("absolute h-full rounded-full bg-primary", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SliderThumbProps<T extends ValidComponent = "span"> = SliderPrimitive.SliderThumbProps<T> & {
|
||||
class?: string | undefined
|
||||
children?: JSX.Element
|
||||
}
|
||||
|
||||
const SliderThumb = <T extends ValidComponent = "span">(
|
||||
props: PolymorphicProps<T, SliderThumbProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SliderThumbProps, ["class", "children"])
|
||||
return (
|
||||
<SliderPrimitive.Thumb
|
||||
class={cn(
|
||||
"top-[-6px] block size-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<SliderPrimitive.Input />
|
||||
</SliderPrimitive.Thumb>
|
||||
)
|
||||
}
|
||||
|
||||
const SliderLabel = <T extends ValidComponent = "label">(
|
||||
props: PolymorphicProps<T, SliderPrimitive.SliderLabelProps<T>>
|
||||
) => {
|
||||
return <SliderPrimitive.Label as={Label} {...props} />
|
||||
}
|
||||
|
||||
const SliderValueLabel = <T extends ValidComponent = "label">(
|
||||
props: PolymorphicProps<T, SliderPrimitive.SliderValueLabelProps<T>>
|
||||
) => {
|
||||
return <SliderPrimitive.ValueLabel as={Label} {...props} />
|
||||
}
|
||||
|
||||
export { Slider, SliderTrack, SliderFill, SliderThumb, SliderLabel, SliderValueLabel }
|
84
apps/it-tools/src/components/ui/switch.tsx
Normal file
84
apps/it-tools/src/components/ui/switch.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { cn } from "@/libs/cn";
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
import type {
|
||||
SwitchControlProps,
|
||||
SwitchThumbProps,
|
||||
} from "@kobalte/core/switch";
|
||||
import { Switch as SwitchPrimitive } from "@kobalte/core/switch";
|
||||
import type { ParentProps, ValidComponent, VoidProps } from "solid-js";
|
||||
import { splitProps } from "solid-js";
|
||||
import { Label } from "./label";
|
||||
|
||||
export const SwitchLabel = SwitchPrimitive.Label;
|
||||
export const Switch = SwitchPrimitive;
|
||||
export const SwitchErrorMessage = SwitchPrimitive.ErrorMessage;
|
||||
export const SwitchDescription = SwitchPrimitive.Description;
|
||||
|
||||
type switchControlProps<T extends ValidComponent = "input"> = ParentProps<
|
||||
SwitchControlProps<T> & { class?: string }
|
||||
>;
|
||||
|
||||
export const SwitchControl = <T extends ValidComponent = "input">(
|
||||
props: PolymorphicProps<T, switchControlProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as switchControlProps, [
|
||||
"class",
|
||||
"children",
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SwitchPrimitive.Input class="[&:focus-visible+div]:(outline-none ring-1.5 ring-ring ring-offset-2 ring-offset-background)" />
|
||||
<SwitchPrimitive.Control
|
||||
class={cn(
|
||||
"inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent bg-input shadow-sm transition-shadow data-[disabled]:(cursor-not-allowed opacity-50) data-[checked]:bg-primary transition-property-[box-shadow,color,background-color]",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{local.children}
|
||||
</SwitchPrimitive.Control>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type switchThumbProps<T extends ValidComponent = "div"> = VoidProps<
|
||||
SwitchThumbProps<T> & { class?: string }
|
||||
>;
|
||||
|
||||
export const SwitchThumb = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, switchThumbProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as switchThumbProps, ["class"]);
|
||||
|
||||
return (
|
||||
<SwitchPrimitive.Thumb
|
||||
class={cn(
|
||||
"pointer-events-none block h-4 w-4 translate-x-0 rounded-full bg-background shadow-lg transition-transform data-[checked]:translate-x-4",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type SwitchCardProps = {
|
||||
label: string;
|
||||
description: string;
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
}
|
||||
|
||||
export const SwitchCard = (props: SwitchCardProps) => {
|
||||
|
||||
return <Switch checked={props.checked} onChange={props.onChange} class="flex items-center justify-between gap-2 border rounded-md py-2 px-4">
|
||||
<div >
|
||||
<SwitchLabel class="text-sm font-medium">{props.label}</SwitchLabel>
|
||||
<SwitchDescription class="text-sm text-muted-foreground">{props.description}</SwitchDescription>
|
||||
</div>
|
||||
<SwitchControl >
|
||||
<SwitchThumb />
|
||||
</SwitchControl>
|
||||
</Switch>
|
||||
|
||||
}
|
28
apps/it-tools/src/components/ui/textarea.tsx
Normal file
28
apps/it-tools/src/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { cn } from "@/libs/cn";
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
import type { TextFieldTextAreaProps } from "@kobalte/core/text-field";
|
||||
import { TextArea as TextFieldPrimitive } from "@kobalte/core/text-field";
|
||||
import type { ValidComponent, VoidProps } from "solid-js";
|
||||
import { splitProps } from "solid-js";
|
||||
|
||||
type textAreaProps<T extends ValidComponent = "textarea"> = VoidProps<
|
||||
TextFieldTextAreaProps<T> & {
|
||||
class?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export const TextArea = <T extends ValidComponent = "textarea">(
|
||||
props: PolymorphicProps<T, textAreaProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as textAreaProps, ["class"]);
|
||||
|
||||
return (
|
||||
<TextFieldPrimitive
|
||||
class={cn(
|
||||
"flex min-h-[30px] w-full rounded-md border border-input bg-inherit px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:(outline-none ring-1.5 ring-ring) disabled:(cursor-not-allowed opacity-50) transition-shadow",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
126
apps/it-tools/src/components/ui/textfield.tsx
Normal file
126
apps/it-tools/src/components/ui/textfield.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import { cn } from "@/libs/cn";
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
import type {
|
||||
TextFieldDescriptionProps,
|
||||
TextFieldErrorMessageProps,
|
||||
TextFieldInputProps,
|
||||
TextFieldLabelProps,
|
||||
TextFieldRootProps,
|
||||
} from "@kobalte/core/text-field";
|
||||
import { TextField as TextFieldPrimitive } from "@kobalte/core/text-field";
|
||||
import { cva } from "class-variance-authority";
|
||||
import type { ValidComponent, VoidProps } from "solid-js";
|
||||
import { splitProps } from "solid-js";
|
||||
|
||||
type textFieldProps<T extends ValidComponent = "div"> =
|
||||
TextFieldRootProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const TextFieldRoot = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, textFieldProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as textFieldProps, ["class"]);
|
||||
|
||||
return <TextFieldPrimitive class={cn("space-y-1", local.class)} {...rest} />;
|
||||
};
|
||||
|
||||
export const textfieldLabel = cva(
|
||||
"text-sm data-[disabled]:(cursor-not-allowed opacity-70) font-medium",
|
||||
{
|
||||
variants: {
|
||||
label: {
|
||||
true: "data-[invalid]:text-destructive",
|
||||
},
|
||||
error: {
|
||||
true: "text-destructive text-xs",
|
||||
},
|
||||
description: {
|
||||
true: "font-normal text-muted-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
label: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
type textFieldLabelProps<T extends ValidComponent = "label"> =
|
||||
TextFieldLabelProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const TextFieldLabel = <T extends ValidComponent = "label">(
|
||||
props: PolymorphicProps<T, textFieldLabelProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as textFieldLabelProps, ["class"]);
|
||||
|
||||
return (
|
||||
<TextFieldPrimitive.Label
|
||||
class={cn(textfieldLabel(), local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type textFieldErrorMessageProps<T extends ValidComponent = "div"> =
|
||||
TextFieldErrorMessageProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const TextFieldErrorMessage = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, textFieldErrorMessageProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as textFieldErrorMessageProps, [
|
||||
"class",
|
||||
]);
|
||||
|
||||
return (
|
||||
<TextFieldPrimitive.ErrorMessage
|
||||
class={cn(textfieldLabel({ error: true }), local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type textFieldDescriptionProps<T extends ValidComponent = "div"> =
|
||||
TextFieldDescriptionProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export const TextFieldDescription = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, textFieldDescriptionProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as textFieldDescriptionProps, [
|
||||
"class",
|
||||
]);
|
||||
|
||||
return (
|
||||
<TextFieldPrimitive.Description
|
||||
class={cn(textfieldLabel({ description: true }), local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type textFieldInputProps<T extends ValidComponent = "input"> = VoidProps<
|
||||
TextFieldInputProps<T> & {
|
||||
class?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export const TextField = <T extends ValidComponent = "input">(
|
||||
props: PolymorphicProps<T, textFieldInputProps<T>>,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props as textFieldInputProps, ["class"]);
|
||||
|
||||
return (
|
||||
<TextFieldPrimitive.Input
|
||||
class={cn(
|
||||
"flex h-9 w-full rounded-md border border-input bg-inherit px-3 py-1 text-sm shadow-sm file:(border-0 bg-transparent text-sm font-medium) placeholder:text-muted-foreground focus-visible:(outline-none ring-1.5 ring-ring) disabled:(cursor-not-allowed opacity-50) transition-shadow",
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
19
apps/it-tools/src/i18n/i18n.models.test.ts
Normal file
19
apps/it-tools/src/i18n/i18n.models.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { buildLocalizedUrl } from './i18n.models';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
|
||||
describe('i18n models', () => {
|
||||
describe('buildLocalizedUrl', () => {
|
||||
test('build an url prefixed with the language', () => {
|
||||
expect(buildLocalizedUrl({lang: 'fr', path: '/tools/token-generator'})).toBe('/fr/tools/token-generator');
|
||||
expect(buildLocalizedUrl({lang: 'en', path: '/token-generator'})).toBe('/en/token-generator');
|
||||
expect(buildLocalizedUrl({path: '/token-generator'})).toBe('/token-generator');
|
||||
});
|
||||
|
||||
test('the path may not start with a slash', () => {
|
||||
expect(buildLocalizedUrl({lang: 'fr', path: 'tools/token-generator'})).toBe('/fr/tools/token-generator');
|
||||
expect(buildLocalizedUrl({lang: 'en', path: 'token-generator'})).toBe('/en/token-generator');
|
||||
expect(buildLocalizedUrl({path: 'token-generator'})).toBe('/token-generator');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
9
apps/it-tools/src/i18n/i18n.models.ts
Normal file
9
apps/it-tools/src/i18n/i18n.models.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export function buildLocalizedUrl({lang, path}: {lang?: string, path: string}) {
|
||||
const slashlessPath = path.replace(/^\//, "");
|
||||
|
||||
if (lang) {
|
||||
return `/${lang}/${slashlessPath}`;
|
||||
}
|
||||
|
||||
return `/${slashlessPath}`;
|
||||
}
|
8
apps/it-tools/src/i18n/i18n.routing.ts
Normal file
8
apps/it-tools/src/i18n/i18n.routing.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { locales } from "./languages";
|
||||
|
||||
export function getStaticPaths() {
|
||||
return [
|
||||
{ params: { lang: undefined }, },
|
||||
...locales.map((lang) => ({params: { lang },})),
|
||||
];
|
||||
}
|
9
apps/it-tools/src/i18n/languages.ts
Normal file
9
apps/it-tools/src/i18n/languages.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const languages = {
|
||||
en: "English",
|
||||
fr: "Français",
|
||||
} as const;
|
||||
|
||||
export type LocaleKey = keyof typeof languages;
|
||||
|
||||
export const locales = Object.keys(languages) as LocaleKey[];
|
||||
export const defaultLocale: LocaleKey = "en";
|
39
apps/it-tools/src/layouts/base.layout.astro
Normal file
39
apps/it-tools/src/layouts/base.layout.astro
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
import { ColorModeScript } from "@kobalte/core";
|
||||
import Header from "@/components/header.astro";
|
||||
import '@/assets/app.css'
|
||||
import Footer from "@/components/footer.astro";
|
||||
import { ClientRouter } from "astro:transitions";
|
||||
import { defaultLocale } from "@/i18n/languages";
|
||||
|
||||
const info = {
|
||||
title: 'IT-Tools',
|
||||
description: 'Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT.',
|
||||
};
|
||||
|
||||
const { lang } = Astro.params;
|
||||
const locale = lang ?? defaultLocale;
|
||||
---
|
||||
|
||||
<html lang={locale}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{info.title}</title>
|
||||
<meta name="description" content={info.description} />
|
||||
<ClientRouter />
|
||||
|
||||
<script is:inline>function e(){document.documentElement.setAttribute("data-kb-theme",localStorage.getItem("it-tools-theme")??(window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"))}e(),document.addEventListener("astro:page-load",e)</script>
|
||||
</head>
|
||||
<body class="bg-background text-foreground font-sans min-h-screen text-sm antialiased flex flex-col">
|
||||
|
||||
|
||||
<Header />
|
||||
<main class="flex-1">
|
||||
<slot />
|
||||
</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
5
apps/it-tools/src/libs/cn.ts
Normal file
5
apps/it-tools/src/libs/cn.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { ClassValue } from "clsx";
|
||||
import clsx from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const cn = (...classLists: ClassValue[]) => twMerge(clsx(classLists));
|
21
apps/it-tools/src/pages/[...lang]/404.astro
Normal file
21
apps/it-tools/src/pages/[...lang]/404.astro
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
import { getStaticPaths } from "@/i18n/i18n.routing";
|
||||
import { defaultLocale } from "@/i18n/languages";
|
||||
import BaseLayout from "@/layouts/base.layout.astro";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toolDefinitions } from "@/tools/definitions/tools.registry";
|
||||
|
||||
export { getStaticPaths };
|
||||
|
||||
const { lang= defaultLocale } = Astro.params;
|
||||
|
||||
---
|
||||
|
||||
<BaseLayout>
|
||||
<div class="max-w-screen-md mx-auto px-6 py-12">
|
||||
<h1 class="text-2xl font-bold text-center">404</h1>
|
||||
<p class="text-sm text-muted-foreground text-center">
|
||||
Page not found
|
||||
</p>
|
||||
</div>
|
||||
</BaseLayout>
|
22
apps/it-tools/src/pages/[...lang]/[toolSlug].astro
Normal file
22
apps/it-tools/src/pages/[...lang]/[toolSlug].astro
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
|
||||
import BaseLayout from "@/layouts/base.layout.astro";
|
||||
import { toolDefinitions } from "@/tools/definitions/tools.registry";
|
||||
import { defaultLocale, locales } from "@/i18n/languages";
|
||||
|
||||
export function getStaticPaths() {
|
||||
return [...locales, undefined].flatMap((lang) => toolDefinitions.map((tool) => ({
|
||||
params: { lang: lang , toolSlug: tool.getLocalizedInfo({locale: lang ?? defaultLocale}).slug },
|
||||
})));
|
||||
}
|
||||
|
||||
const { toolSlug, lang = defaultLocale } = Astro.params;
|
||||
|
||||
const toolDefinition = toolDefinitions.find((tool) => tool.getLocalizedInfo({locale: lang ?? defaultLocale}).slug === toolSlug)!;
|
||||
|
||||
|
||||
const { default: Tool } = await toolDefinition.entrypoint();
|
||||
|
||||
---
|
||||
|
||||
<Tool />
|
72
apps/it-tools/src/pages/[...lang]/index.astro
Normal file
72
apps/it-tools/src/pages/[...lang]/index.astro
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
import { getStaticPaths } from "@/i18n/i18n.routing";
|
||||
import { defaultLocale } from "@/i18n/languages";
|
||||
import BaseLayout from "@/layouts/base.layout.astro";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toolDefinitions } from "@/tools/definitions/tools.registry";
|
||||
import ToolCard from "@/components/tool-card.astro";
|
||||
import { cn } from "@/libs/cn";
|
||||
|
||||
export { getStaticPaths };
|
||||
|
||||
const { lang } = Astro.params;
|
||||
const locale = lang ?? defaultLocale;
|
||||
|
||||
const stats = [
|
||||
{
|
||||
label: "Tools",
|
||||
value: toolDefinitions.length,
|
||||
},
|
||||
{
|
||||
label: "Contributors",
|
||||
value: Intl.NumberFormat('en-US').format(10),
|
||||
},
|
||||
{
|
||||
label: "Self hosted instances",
|
||||
value: Intl.NumberFormat('en-US').format(1000),
|
||||
},
|
||||
{
|
||||
label: "GitHub Stars",
|
||||
value: Intl.NumberFormat('en-US').format(1000),
|
||||
},
|
||||
]
|
||||
---
|
||||
|
||||
<BaseLayout>
|
||||
|
||||
<div class="max-w-screen-md mx-auto px-6 mt-32 flex items-center gap-12">
|
||||
<div class="max-w-md">
|
||||
<h1 class="text-4xl font-semibold">IT-Tools</h1>
|
||||
<p class="text-lg text-muted-foreground mt-4">
|
||||
The open-source and self-hostable collection of handy online tools for developers and people working in IT.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class={cn('i-solar-programming-line-duotone size-42 text-muted-foreground flex-shrink-0')} />
|
||||
</div>
|
||||
|
||||
<!-- Stats section -->
|
||||
<div class="px-6 mt-24 bg-muted/20 light:border-y">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 max-w-screen-lg mx-auto">
|
||||
{stats.map((stat) => {
|
||||
return <div class="flex flex-col gap-2 items-center justify-center py-12">
|
||||
<span class="text-2xl font-semibold">{stat.value}</span>
|
||||
<span class="text-sm text-muted-foreground">{stat.label}</span>
|
||||
</div>;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-screen-lg mx-auto px-6 py-12">
|
||||
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-3 gap-4">
|
||||
{toolDefinitions.map((tool) => {
|
||||
const { icon, getLocalizedInfo } = tool;
|
||||
const { slug, title, description } = getLocalizedInfo({locale});
|
||||
|
||||
return <ToolCard slug={slug} name={title} description={description} icon={icon} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</BaseLayout>
|
@@ -0,0 +1,54 @@
|
||||
## What is a Token Generator?
|
||||
|
||||
A token generator creates random strings of characters used as unique identifiers for authentication, API keys, session tokens, and other security purposes.
|
||||
|
||||
## Features
|
||||
|
||||
### Token Generation
|
||||
- Adjustable length from 1 to 512 characters
|
||||
- Customizable character sets (uppercase, lowercase, numbers, symbols)
|
||||
- Generate multiple tokens at once
|
||||
- Real-time generation as settings change
|
||||
|
||||
### Security
|
||||
- Uses secure random number generation
|
||||
- Client-side generation for privacy
|
||||
- Configurable complexity levels
|
||||
|
||||
### Interface
|
||||
- Visual controls for easy configuration
|
||||
- Copy functionality for generated tokens
|
||||
- Refresh option for new tokens
|
||||
- Responsive design
|
||||
|
||||
## Use Cases
|
||||
|
||||
- API keys for web services and applications
|
||||
- Password reset verification tokens
|
||||
- Session identifiers for authentication
|
||||
- File upload and sharing tokens
|
||||
- Database record identifiers
|
||||
- Testing and development tokens
|
||||
|
||||
## How to Use
|
||||
|
||||
1. Set token length using the slider or number input (1-512 characters)
|
||||
2. Choose character sets by toggling the switches
|
||||
3. Tokens are generated automatically based on your settings
|
||||
4. Copy tokens to clipboard or refresh to generate new ones
|
||||
|
||||
## Technical Details
|
||||
|
||||
- Character sets: uppercase letters (A-Z), lowercase letters (a-z), numbers (0-9), symbols
|
||||
- Length range: 1 to 512 characters (or more)
|
||||
- Generation method: secure random sampling
|
||||
- Output format: plain text
|
||||
|
||||
## Benefits
|
||||
|
||||
- Client-side generation for privacy
|
||||
- Fast and responsive interface
|
||||
- Highly customizable settings
|
||||
- Simple integration with development workflows
|
||||
|
||||
> **Security Notice**: For high-value production systems, always rotate tokens regularly and store them using secure hashing algorithms (bcrypt/scrypt).
|
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
title: "Token Generator",
|
||||
description: "A token is a random string of characters that is often used for unique identifiers, such as API keys or verification URLs.",
|
||||
slug: "token-generator",
|
||||
};
|
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
title: "Générateur de tokens aléatoires",
|
||||
description: "Générez des tokens aléatoires pour vos API, vos clés d'accès, ou tout autre cas où vous avez besoin d'un identifiant unique.",
|
||||
slug: "generateur-de-tokens",
|
||||
};
|
@@ -0,0 +1,14 @@
|
||||
import type { LocaleKey } from "@/i18n/languages";
|
||||
import type { ToolDefinition } from "../tools.types";
|
||||
import { locales } from "./token-generator.locales";
|
||||
|
||||
export default {
|
||||
id: "token-generator",
|
||||
icon: "i-tabler-key",
|
||||
entrypoint: () => import("./token-generator.entry.astro"),
|
||||
getLocalizedInfo: ({locale}: {locale: LocaleKey}) => ({
|
||||
slug: locales[locale].slug,
|
||||
title: locales[locale].title,
|
||||
description: locales[locale].description,
|
||||
})
|
||||
} satisfies ToolDefinition;
|
@@ -0,0 +1,49 @@
|
||||
---
|
||||
import { defaultLocale, locales, type LocaleKey } from "@/i18n/languages";
|
||||
import BaseLayout from "@/layouts/base.layout.astro";
|
||||
import { TokenGenerator as TokenGeneratorComponent } from "@/tools/definitions/token-generator/token-generator";
|
||||
|
||||
|
||||
export function getStaticPaths() {
|
||||
return [
|
||||
{ params: { lang: undefined } },
|
||||
...locales.map((lang) => ({params: { lang },}))
|
||||
];
|
||||
}
|
||||
|
||||
const { lang = defaultLocale } = Astro.params;
|
||||
const { default: toolTranslations } = await import(`./i18n/${lang}.ts`);
|
||||
|
||||
async function getContent(lang: LocaleKey) {
|
||||
try {
|
||||
return await import(`./i18n/${lang}.md`).then(module => module.default);
|
||||
} catch (error) {
|
||||
return await import(`./i18n/en.md`).then(module => module.default);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const Content = await getContent(lang);
|
||||
|
||||
|
||||
---
|
||||
<BaseLayout>
|
||||
<div class="max-w-screen-md mx-auto px-6 py-12 mt-12">
|
||||
<div class="flex flex-col sm:items-center gap-4 mb-2">
|
||||
<span class="flex-shrink-0 size-10 flex items-center justify-center bg-muted rounded-md p-2">
|
||||
<span class="i-tabler-key flex-shrink-0 size-6"></span>
|
||||
</span>
|
||||
<h1 class="text-3xl font-bold text-center">{toolTranslations.title}</h1>
|
||||
</div>
|
||||
<!-- <p class="text-sm text-muted-foreground mb-6 text-center text-balance">{toolTranslations.description}</p> -->
|
||||
|
||||
<TokenGeneratorComponent client:load data={toolTranslations} />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="border-b w-full my-12"></div>
|
||||
<div class="prose max-w-screen-md mx-auto pb-24 dark:prose-invert px-6">
|
||||
<Content />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
@@ -0,0 +1,7 @@
|
||||
import en from "./i18n/en.ts";
|
||||
import fr from "./i18n/fr.ts";
|
||||
|
||||
export const locales = {
|
||||
en,
|
||||
fr,
|
||||
} as const;
|
@@ -0,0 +1,33 @@
|
||||
export function generateToken({
|
||||
withUppercase = true,
|
||||
withLowercase = true,
|
||||
withNumbers = true,
|
||||
withSymbols = true,
|
||||
length = 64,
|
||||
sample = (corpus) => corpus[Math.floor(Math.random() * corpus.length)],
|
||||
}: {
|
||||
withUppercase?: boolean;
|
||||
withLowercase?: boolean;
|
||||
withNumbers?: boolean;
|
||||
withSymbols?: boolean;
|
||||
length?: number;
|
||||
sample?: (corpus: string[]) => string;
|
||||
}) {
|
||||
const corpus = [
|
||||
...(withUppercase ? "ABCDEFGHIJKLMNOPQRSTUVWXYZ" : []),
|
||||
...(withLowercase ? "abcdefghijklmnopqrstuvwxyz" : []),
|
||||
...(withNumbers ? "0123456789" : []),
|
||||
...(withSymbols ? '.,;:!?./-"\'#{([-|\\@)]=}*+' : []),
|
||||
];
|
||||
|
||||
if(corpus.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let token = "";
|
||||
// imperative for loop for performance
|
||||
for (let i = 0; i < length; i++) {
|
||||
token += sample(corpus);
|
||||
}
|
||||
return token;
|
||||
}
|
@@ -0,0 +1,111 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { TextArea } from "@/components/ui/textarea";
|
||||
import { TextField, TextFieldRoot } from "@/components/ui/textfield";
|
||||
import type { Component } from "solid-js";
|
||||
import { createEffect, createSignal, on, onMount } from "solid-js";
|
||||
import { generateToken } from "./token-generator.models";
|
||||
import { SwitchCard } from "@/components/ui/switch";
|
||||
import { NumberField, NumberFieldDecrementTrigger, NumberFieldIncrementTrigger, NumberFieldGroup, NumberFieldLabel, NumberFieldInput } from "@/components/ui/number-field";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { SliderLabel, SliderTrack, SliderValueLabel, SliderThumb, SliderFill } from "@/components/ui/slider";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
|
||||
export const TokenGenerator: Component<{ data: { title: string, description: string } }> = (props) => {
|
||||
const [getLength, setLength] = createSignal<number>(64);
|
||||
const [getTokenCount, setTokenCount] = createSignal<number>(1);
|
||||
const [getWithUppercase, setWithUppercase] = createSignal<boolean>(true);
|
||||
const [getWithLowercase, setWithLowercase] = createSignal<boolean>(true);
|
||||
const [getWithNumbers, setWithNumbers] = createSignal<boolean>(true);
|
||||
const [getWithSymbols, setWithSymbols] = createSignal<boolean>(false);
|
||||
const [getTokens, setTokens] = createSignal<string[]>(buildTokens());
|
||||
|
||||
function buildTokens() {
|
||||
return Array.from(
|
||||
{ length: getTokenCount() },
|
||||
() => generateToken({
|
||||
length: getLength(),
|
||||
withUppercase: getWithUppercase(),
|
||||
withLowercase: getWithLowercase(),
|
||||
withNumbers: getWithNumbers(),
|
||||
withSymbols: getWithSymbols(),
|
||||
}));
|
||||
}
|
||||
|
||||
const refreshTokens = () => {
|
||||
setTokens(buildTokens());
|
||||
}
|
||||
|
||||
const getRowCount = () => {
|
||||
const tokenCount = getTokenCount();
|
||||
|
||||
if(tokenCount < 10) {
|
||||
return tokenCount;
|
||||
}
|
||||
|
||||
return 10;
|
||||
}
|
||||
|
||||
createEffect(
|
||||
on([getWithUppercase, getWithLowercase, getWithNumbers, getWithSymbols, getLength, getTokenCount], () => refreshTokens())
|
||||
);
|
||||
|
||||
const copyTokens = () => {
|
||||
navigator.clipboard.writeText(getTokens().join("\n"));
|
||||
}
|
||||
|
||||
return <div>
|
||||
<TextFieldRoot class="w-full mb-4 mt-12">
|
||||
<TextArea value={getTokens().join("\n")} placeholder="Tokens will appear here" rows={getRowCount()} class="text-center font-mono" autoResize />
|
||||
</TextFieldRoot>
|
||||
|
||||
<div class="flex justify-center gap-2 mb-8">
|
||||
<Button variant="outline" onClick={refreshTokens} class="gap-2">
|
||||
<div class="i-tabler-refresh size-4 text-muted-foreground" />
|
||||
Refresh
|
||||
</Button>
|
||||
<Button onClick={copyTokens} class="gap-2" >
|
||||
<span class="i-tabler-copy size-4 text-muted" />
|
||||
Copy
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Label>Token length</Label>
|
||||
<div class="flex flex-row gap-8 items-center mb-4">
|
||||
|
||||
|
||||
|
||||
|
||||
<TextFieldRoot class="max-w-160px my-2">
|
||||
<NumberField value={getLength()} onRawValueChange={(value) => setLength(value)} >
|
||||
<NumberFieldGroup>
|
||||
<NumberFieldDecrementTrigger aria-label="Decrement" />
|
||||
<NumberFieldInput />
|
||||
<NumberFieldIncrementTrigger aria-label="Increment" />
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
</TextFieldRoot>
|
||||
|
||||
<Slider
|
||||
minValue={1}
|
||||
maxValue={512}
|
||||
class="flex-1"
|
||||
value={[Math.min(512, Math.max(1, getLength()))]}
|
||||
onChange={(value) => setLength(value[0])}
|
||||
>
|
||||
<SliderTrack>
|
||||
<SliderFill />
|
||||
<SliderThumb />
|
||||
<SliderThumb />
|
||||
</SliderTrack>
|
||||
</Slider>
|
||||
</div>
|
||||
|
||||
<Label>Character set</Label>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 my-2">
|
||||
<SwitchCard label="Uppercase" description="Include uppercase letters" checked={getWithUppercase()} onChange={setWithUppercase} />
|
||||
<SwitchCard label="Lowercase" description="Include lowercase letters" checked={getWithLowercase()} onChange={setWithLowercase} />
|
||||
<SwitchCard label="Numbers" description="Include numbers" checked={getWithNumbers()} onChange={setWithNumbers} />
|
||||
<SwitchCard label="Symbols" description="Include symbols" checked={getWithSymbols()} onChange={setWithSymbols} />
|
||||
</div>
|
||||
</div>;
|
||||
};
|
6
apps/it-tools/src/tools/definitions/tools.registry.ts
Normal file
6
apps/it-tools/src/tools/definitions/tools.registry.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import tokenGeneratorDefinition from "./token-generator/token-generator.definition";
|
||||
import type { ToolDefinition } from "./tools.types";
|
||||
|
||||
export const toolDefinitions: ToolDefinition[] = [
|
||||
tokenGeneratorDefinition,
|
||||
];
|
12
apps/it-tools/src/tools/definitions/tools.types.ts
Normal file
12
apps/it-tools/src/tools/definitions/tools.types.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { LocaleKey } from "@/i18n/languages";
|
||||
|
||||
export type ToolDefinition = {
|
||||
id: string;
|
||||
entrypoint: () => Promise<{ default: any }>;
|
||||
icon: string;
|
||||
getLocalizedInfo: ({locale}: {locale: LocaleKey}) => {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
}
|
18
apps/it-tools/tsconfig.json
Normal file
18
apps/it-tools/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [
|
||||
".astro/types.d.ts",
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"dist"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "solid-js",
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
99
apps/it-tools/uno.config.ts
Normal file
99
apps/it-tools/uno.config.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { defineConfig, presetUno, transformerDirectives, transformerVariantGroup, presetIcons, presetTypography, presetWebFonts } from "unocss";
|
||||
import presetAnimations from "unocss-preset-animations";
|
||||
import { toolDefinitions } from "./src/tools/definitions/tools.registry";
|
||||
|
||||
export default defineConfig({
|
||||
presets: [
|
||||
presetUno({
|
||||
dark: {
|
||||
dark: '[data-kb-theme="dark"]',
|
||||
light: '[data-kb-theme="light"]'
|
||||
}
|
||||
}),
|
||||
presetAnimations(),
|
||||
presetIcons(),
|
||||
presetTypography(),
|
||||
presetWebFonts({
|
||||
provider: 'bunny',
|
||||
fonts: {
|
||||
sans: 'Inter:300,400,500,600,700,800',
|
||||
},
|
||||
}),
|
||||
],
|
||||
transformers: [transformerVariantGroup(), transformerDirectives()],
|
||||
theme: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))"
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))"
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))"
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))"
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))"
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))"
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))"
|
||||
}
|
||||
},
|
||||
borderRadius: {
|
||||
lg: `var(--radius)`,
|
||||
md: `calc(var(--radius) - 2px)`,
|
||||
sm: "calc(var(--radius) - 4px)"
|
||||
},
|
||||
animation: {
|
||||
keyframes: {
|
||||
"accordion-down":
|
||||
"{ from { height: 0 } to { height: var(--kb-accordion-content-height) } }",
|
||||
"accordion-up": "{ from { height: var(--kb-accordion-content-height) } to { height: 0 } }",
|
||||
"collapsible-down":
|
||||
"{ from { height: 0 } to { height: var(--kb-collapsible-content-height) } }",
|
||||
"collapsible-up":
|
||||
"{ from { height: var(--kb-collapsible-content-height) } to { height: 0 } }"
|
||||
},
|
||||
timingFns: {
|
||||
"accordion-down": "ease-out",
|
||||
"accordion-up": "ease-out",
|
||||
"collapsible-down": "ease-out",
|
||||
"collapsible-up": "ease-out"
|
||||
},
|
||||
durations: {
|
||||
"accordion-down": "0.2s",
|
||||
"accordion-up": "0.2s",
|
||||
"collapsible-down": "0.2s",
|
||||
"collapsible-up": "0.2s"
|
||||
}
|
||||
}
|
||||
},
|
||||
safelist: [
|
||||
'sm:grid-cols-2',
|
||||
'sm:grid-cols-3',
|
||||
'sm:grid-cols-4',
|
||||
'sm:grid-cols-5',
|
||||
'sm:grid-cols-6',
|
||||
'sm:grid-cols-7',
|
||||
'sm:grid-cols-8',
|
||||
...toolDefinitions.map((tool) => tool.icon),
|
||||
]
|
||||
});
|
@@ -1,21 +0,0 @@
|
||||
import antfu from '@antfu/eslint-config';
|
||||
|
||||
export default antfu({
|
||||
stylistic: {
|
||||
semi: true,
|
||||
},
|
||||
|
||||
rules: {
|
||||
// To allow export on top of files
|
||||
'ts/no-use-before-define': ['error', { allowNamedExports: true, functions: false }],
|
||||
'curly': ['error', 'all'],
|
||||
'vitest/consistent-test-it': ['error', { fn: 'test' }],
|
||||
'ts/consistent-type-definitions': ['error', 'type'],
|
||||
'style/brace-style': ['error', '1tbs', { allowSingleLine: false }],
|
||||
'unused-imports/no-unused-vars': ['error', {
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
}],
|
||||
},
|
||||
});
|
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<title>IT Tools - Handy online tools for developers</title>
|
||||
|
||||
<meta name="title" content="IT Tools - Handy online tools for developers" />
|
||||
<meta name="description" content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT." />
|
||||
|
||||
<link rel="author" href="humans.txt" />
|
||||
<link rel="canonical" href="https://enclosed.cc/" />
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="/src/client.tsx" type="module"></script>
|
||||
</body>
|
||||
</html>
|
2381
packages/app/package-lock.json
generated
2381
packages/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,54 +0,0 @@
|
||||
{
|
||||
"name": "@it-tools/app",
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"packageManager": "pnpm@9.11.0",
|
||||
"description": "Collection of handy online tools for developers, with great UX.",
|
||||
"author": "Corentin Thomasset <corentinth@proton.me> (https://corentin.tech)",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/CorentinTh/it-tools"
|
||||
},
|
||||
"keywords": [],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "pnpm run test:unit",
|
||||
"test:unit": "vitest run",
|
||||
"test:unit:watch": "vitest watch",
|
||||
"create:tool": "HYGEN_TMPLS=templates hygen tools new"
|
||||
},
|
||||
"dependencies": {
|
||||
"@corentinth/chisels": "^1.1.0",
|
||||
"@kobalte/core": "^0.13.6",
|
||||
"@solid-primitives/i18n": "^2.1.1",
|
||||
"@solid-primitives/storage": "^4.2.1",
|
||||
"@solidjs/router": "^0.14.7",
|
||||
"@unocss/reset": "^0.62.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk-solid": "^1.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"solid-js": "^1.9.1",
|
||||
"solid-sonner": "^0.2.8",
|
||||
"tailwind-merge": "^2.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^3.7.3",
|
||||
"@iconify-json/tabler": "^1.2.3",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@vitest/coverage-v8": "2.1.2",
|
||||
"eslint": "^9.11.1",
|
||||
"hygen": "^6.2.11",
|
||||
"typescript": "^5.6.2",
|
||||
"unocss": "^0.62.4",
|
||||
"unocss-preset-animations": "^1.1.0",
|
||||
"vite": "^5.4.8",
|
||||
"vite-plugin-solid": "^2.10.2",
|
||||
"vitest": "^2.1.2"
|
||||
}
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
/* TEAM */
|
||||
Developer: Corentin Thomasset
|
||||
Site: https://corentin.tech
|
||||
Twitter: @cthmsst
|
@@ -1,2 +0,0 @@
|
||||
User-agent: *
|
||||
Disallow:
|
@@ -1,75 +0,0 @@
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--warning: 31 98% 50%;
|
||||
--warning-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
[data-kb-theme="dark"] {
|
||||
--background: 0 0% 9%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
--card: 0 0% 7%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
|
||||
--primary: 83 79% 55%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--warning: 31 98% 50%;
|
||||
--warning-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
}
|
||||
|
||||
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
import type { LocaleKey } from './modules/i18n/i18n.types';
|
||||
import { A, Navigate, type RouteDefinition, useParams } from '@solidjs/router';
|
||||
import { localeKeys } from './modules/i18n/i18n.constants';
|
||||
import { useI18n } from './modules/i18n/i18n.provider';
|
||||
import { HomePage } from './modules/pages/home.page';
|
||||
import { ToolPage } from './modules/tools/pages/tool.page';
|
||||
import { toolSlugs } from './modules/tools/tools.registry';
|
||||
import { Button } from './modules/ui/components/button';
|
||||
import { AppLayout } from './modules/ui/layouts/app.layout';
|
||||
|
||||
export const routes: RouteDefinition[] = [
|
||||
{
|
||||
path: '/',
|
||||
component: () => {
|
||||
const { getLocale } = useI18n();
|
||||
|
||||
return <Navigate href={`/${getLocale()}`} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: AppLayout,
|
||||
children: [
|
||||
{
|
||||
path: '/:localeKey',
|
||||
matchFilters: {
|
||||
localeKey: localeKeys,
|
||||
},
|
||||
component: (props) => {
|
||||
const params = useParams();
|
||||
const { setLocale } = useI18n();
|
||||
|
||||
setLocale(params.localeKey as LocaleKey);
|
||||
|
||||
return props.children;
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
component: HomePage,
|
||||
},
|
||||
{
|
||||
path: '/:toolSlug',
|
||||
matchFilters: {
|
||||
toolSlug: toolSlugs,
|
||||
},
|
||||
component: ToolPage,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '*404',
|
||||
component: () => (
|
||||
<div class="flex flex-col items-center justify-center mt-6">
|
||||
<div class="text-3xl font-light text-muted-foreground">404</div>
|
||||
<h1 class="font-semibold text-lg my-2">Page Not Found</h1>
|
||||
<p class="text-muted-foreground">The page you are looking for does not exist.</p>
|
||||
<p class="text-muted-foreground">Please check the URL and try again.</p>
|
||||
<Button as={A} href="/" class="mt-4" variant="secondary">
|
||||
<div class="i-tabler-arrow-left mr-2"></div>
|
||||
Go back home
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
];
|
@@ -1,43 +0,0 @@
|
||||
/* @refresh reload */
|
||||
|
||||
import { ColorModeProvider, ColorModeScript, createLocalStorageManager } from '@kobalte/core/color-mode';
|
||||
import { Router } from '@solidjs/router';
|
||||
import { render, Suspense } from 'solid-js/web';
|
||||
import { routes } from './client-routes';
|
||||
import { CommandPaletteProvider } from './modules/command-palette/command-palette.provider';
|
||||
import { RootI18nProvider } from './modules/i18n/i18n.provider';
|
||||
import { Toaster } from './modules/ui/components/sonner';
|
||||
import '@unocss/reset/tailwind.css';
|
||||
import 'virtual:uno.css';
|
||||
import './app.css';
|
||||
|
||||
render(
|
||||
() => {
|
||||
const initialColorMode = 'system';
|
||||
const colorModeStorageKey = 'it_tools_color_mode';
|
||||
const localStorageManager = createLocalStorageManager(colorModeStorageKey);
|
||||
|
||||
return (
|
||||
<Router
|
||||
children={routes}
|
||||
root={props => (
|
||||
<Suspense>
|
||||
<RootI18nProvider>
|
||||
<ColorModeScript storageType={localStorageManager.type} storageKey={colorModeStorageKey} initialColorMode={initialColorMode} />
|
||||
<ColorModeProvider
|
||||
initialColorMode={initialColorMode}
|
||||
storageManager={localStorageManager}
|
||||
>
|
||||
<CommandPaletteProvider>
|
||||
<Toaster />
|
||||
<div class="min-h-screen font-sans text-sm font-400">{props.children}</div>
|
||||
</CommandPaletteProvider>
|
||||
</ColorModeProvider>
|
||||
</RootI18nProvider>
|
||||
</Suspense>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
document.getElementById('root')!,
|
||||
);
|
@@ -1,73 +0,0 @@
|
||||
{
|
||||
"app": {
|
||||
"title": "IT-Tools",
|
||||
"description": "The open-source collection of handy online tools to help developers in their daily life."
|
||||
},
|
||||
"navbar": {
|
||||
"theme": {
|
||||
"theme": "Theme",
|
||||
"light-mode": "Light mode",
|
||||
"dark-mode": "Dark mode",
|
||||
"system-mode": "System"
|
||||
},
|
||||
"language": "Language",
|
||||
"contribute-to-i18n": "Contribute to i18n",
|
||||
"github": "GitHub",
|
||||
"support": "Support IT-Tools",
|
||||
"report-bug": "Report a bug"
|
||||
},
|
||||
"footer": {
|
||||
"resources": {
|
||||
"title": "Resources",
|
||||
"all-tools": "All the tools",
|
||||
"github": "GitHub repository",
|
||||
"support": "Support IT-Tools",
|
||||
"license": "License"
|
||||
},
|
||||
"support": {
|
||||
"title": "Support",
|
||||
"report-bug": "Report a bug",
|
||||
"request-feature": "Request a feature",
|
||||
"contribute": "Contribute to the project",
|
||||
"contact": "Contact me"
|
||||
},
|
||||
"friends": {
|
||||
"title": "Friends"
|
||||
}
|
||||
},
|
||||
"commandPalette": {
|
||||
"input-placeholder": "Type to search for a tool or a command...",
|
||||
"go-home": "Go to home",
|
||||
"sections": {
|
||||
"tools": "Tools",
|
||||
"navigation": "Navigation",
|
||||
"language": "Language",
|
||||
"theme": "Theme"
|
||||
},
|
||||
"theme": {
|
||||
"switch-to-light": "Switch to light theme",
|
||||
"switch-to-dark": "Switch to dark theme",
|
||||
"switch-to-system": "Use to system theme"
|
||||
},
|
||||
"trigger": {
|
||||
"search": "Search for a tool"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"all-tools": "All the tools",
|
||||
"search-tools": "Search for a tool",
|
||||
"open-source": "Open Source",
|
||||
"free": "Free",
|
||||
"self-hostable": "Self-hostable"
|
||||
},
|
||||
"tools": {
|
||||
"token-generator": {
|
||||
"name": "Token Generator",
|
||||
"description": "Generate random string with the characters you want, uppercase, lowercase letters, numbers and/or symbols."
|
||||
},
|
||||
"random-port-generator": {
|
||||
"name": "Random Port Generator",
|
||||
"description": "Generate a random port number outside of the reserved ports range (0-1023)."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
{
|
||||
"app": {
|
||||
"title": "IT-Tools",
|
||||
"description": "La collection open-source d'outils en ligne pour aider les devs dans leur vie quotidienne."
|
||||
},
|
||||
"navbar": {
|
||||
"theme": {
|
||||
"theme": "Thème",
|
||||
"light-mode": "Mode clair",
|
||||
"dark-mode": "Mode sombre",
|
||||
"system-mode": "Système"
|
||||
},
|
||||
"language": "Langue",
|
||||
"contribute-to-i18n": "Contribuer à l'i18n",
|
||||
"github": "GitHub",
|
||||
"support": "Soutenir IT-Tools",
|
||||
"report-bug": "Signaler un bug"
|
||||
},
|
||||
"footer": {
|
||||
"resources": {
|
||||
"title": "Ressources",
|
||||
"all-tools": "Tous les outils",
|
||||
"github": "Dépôt GitHub",
|
||||
"support": "Soutenir IT-Tools",
|
||||
"license": "Licence"
|
||||
},
|
||||
"support": {
|
||||
"title": "Support",
|
||||
"report-bug": "Signaler un bug",
|
||||
"request-feature": "Demander une fonctionnalité",
|
||||
"contribute": "Contribuer au projet",
|
||||
"contact": "Me contacter"
|
||||
},
|
||||
"friends": {
|
||||
"title": "Ami·e·s"
|
||||
}
|
||||
},
|
||||
"commandPalette": {
|
||||
"input-placeholder": "Tapez pour rechercher un outil...",
|
||||
"go-home": "Aller à l'accueil",
|
||||
"sections": {
|
||||
"tools": "Outils",
|
||||
"navigation": "Navigation",
|
||||
"theme": "Thème"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"all-tools": "Tous les outils",
|
||||
"open-source": "Open Source",
|
||||
"free": "Gratuit",
|
||||
"self-hostable": "Self-hostable"
|
||||
},
|
||||
"tools": {
|
||||
"token-generator": {
|
||||
"name": "Générateur de token",
|
||||
"description": "Générer des string aléatoires, contrôlez les caractères que vous voulez, lettres majuscules, minuscules, chiffres et/ou symboles."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,144 +0,0 @@
|
||||
import type { Accessor, ParentComponent } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import { createContext, createMemo, createSignal, For, onCleanup, onMount, useContext } from 'solid-js';
|
||||
import { locales } from '../i18n/i18n.constants';
|
||||
import { useI18n } from '../i18n/i18n.provider';
|
||||
import { useToolsStore } from '../tools/tools.store';
|
||||
import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '../ui/components/command';
|
||||
import { useThemeStore } from '../ui/themes/theme.store';
|
||||
import { cn } from '../ui/utils/cn';
|
||||
|
||||
const CommandPaletteContext = createContext<{
|
||||
getIsCommandPaletteOpen: Accessor<boolean>;
|
||||
openCommandPalette: () => void;
|
||||
closeCommandPalette: () => void;
|
||||
}>();
|
||||
|
||||
export function useCommandPalette() {
|
||||
const context = useContext(CommandPaletteContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('CommandPalette context not found');
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
export const CommandPaletteProvider: ParentComponent = (props) => {
|
||||
const [getIsCommandPaletteOpen, setIsCommandPaletteOpen] = createSignal(false);
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
setIsCommandPaletteOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
|
||||
onCleanup(() => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
|
||||
const { getTools } = useToolsStore();
|
||||
const navigate = useNavigate();
|
||||
const { t, createLocalizedUrl, changeLocale } = useI18n();
|
||||
const { setColorMode } = useThemeStore();
|
||||
|
||||
const getCommandData = createMemo(() => [
|
||||
{
|
||||
label: t('commandPalette.sections.tools'),
|
||||
options: [
|
||||
...getTools().map(tool => ({
|
||||
label: tool.name,
|
||||
icon: tool.icon,
|
||||
action: () => navigate(createLocalizedUrl({ path: tool.slug })),
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('commandPalette.sections.navigation'),
|
||||
options: [
|
||||
{
|
||||
label: t('commandPalette.go-home'),
|
||||
icon: 'i-tabler-home',
|
||||
action: () => navigate(createLocalizedUrl({ path: '' })),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('commandPalette.sections.language'),
|
||||
options: [
|
||||
...locales.map(locale => ({
|
||||
label: locale.switchToLabel,
|
||||
icon: 'i-custom-language',
|
||||
action: () => changeLocale(locale.key),
|
||||
keywords: [locale.name, locale.key],
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('commandPalette.sections.theme'),
|
||||
options: [
|
||||
{
|
||||
label: t('commandPalette.theme.switch-to-light'),
|
||||
icon: 'i-tabler-sun',
|
||||
action: () => setColorMode({ mode: 'light' }),
|
||||
},
|
||||
{
|
||||
label: t('commandPalette.theme.switch-to-dark'),
|
||||
icon: 'i-tabler-moon',
|
||||
action: () => setColorMode({ mode: 'dark' }),
|
||||
},
|
||||
{
|
||||
label: t('commandPalette.theme.switch-to-system'),
|
||||
icon: 'i-tabler-device-laptop',
|
||||
action: () => setColorMode({ mode: 'system' }),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const onCommandSelect = ({ action }: { action: () => void }) => {
|
||||
action();
|
||||
setIsCommandPaletteOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<CommandPaletteContext.Provider value={{
|
||||
getIsCommandPaletteOpen,
|
||||
openCommandPalette: () => setIsCommandPaletteOpen(true),
|
||||
closeCommandPalette: () => setIsCommandPaletteOpen(false),
|
||||
}}
|
||||
>
|
||||
<CommandDialog
|
||||
class="rounded-lg border shadow-md"
|
||||
open={getIsCommandPaletteOpen()}
|
||||
onOpenChange={setIsCommandPaletteOpen}
|
||||
>
|
||||
<CommandInput placeholder={t('commandPalette.input-placeholder')} />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<For each={getCommandData()}>
|
||||
{section => (
|
||||
<CommandGroup heading={section.label}>
|
||||
<For each={section.options}>
|
||||
{item => (
|
||||
<CommandItem onSelect={() => onCommandSelect(item)}>
|
||||
<span class={cn('mr-2 ml-1 size-4 text-muted-foreground', item.icon)} />
|
||||
<span>{item.label}</span>
|
||||
</CommandItem>
|
||||
)}
|
||||
</For>
|
||||
</CommandGroup>
|
||||
)}
|
||||
</For>
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
|
||||
{props.children}
|
||||
</CommandPaletteContext.Provider>
|
||||
);
|
||||
};
|
@@ -1,18 +0,0 @@
|
||||
import { map } from 'lodash-es';
|
||||
|
||||
export const locales = [
|
||||
{
|
||||
key: 'en',
|
||||
file: 'en',
|
||||
name: 'English',
|
||||
switchToLabel: 'Change language to English',
|
||||
},
|
||||
{
|
||||
key: 'fr',
|
||||
file: 'fr',
|
||||
name: 'Français',
|
||||
switchToLabel: 'Changer la langue en Français',
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const localeKeys = map(locales, 'key');
|
@@ -1,96 +0,0 @@
|
||||
import type { ParentComponent } from 'solid-js';
|
||||
import type { LocaleKey } from './i18n.types';
|
||||
import { joinUrlPaths } from '@corentinth/chisels';
|
||||
import * as i18n from '@solid-primitives/i18n';
|
||||
import { makePersisted } from '@solid-primitives/storage';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import { merge } from 'lodash-es';
|
||||
import { createContext, createResource, createSignal, Show, useContext } from 'solid-js';
|
||||
import defaultDict from '../../locales/en.json';
|
||||
import { locales } from './i18n.constants';
|
||||
|
||||
export {
|
||||
useI18n,
|
||||
};
|
||||
|
||||
type RawDictionary = typeof defaultDict;
|
||||
type Dictionary = i18n.Flatten<RawDictionary>;
|
||||
|
||||
const RootI18nContext = createContext<{
|
||||
t: i18n.Translator<Dictionary>;
|
||||
getLocale: () => LocaleKey;
|
||||
setLocale: (locale: LocaleKey) => void;
|
||||
locales: typeof locales;
|
||||
} | undefined>(undefined);
|
||||
|
||||
function useI18n() {
|
||||
const context = useContext(RootI18nContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!context) {
|
||||
throw new Error('I18n context not found');
|
||||
}
|
||||
|
||||
const { t, getLocale, setLocale, locales } = context;
|
||||
|
||||
return {
|
||||
t,
|
||||
getLocale,
|
||||
setLocale,
|
||||
locales,
|
||||
createLocalizedUrl: ({ path }: { path: string }) => {
|
||||
const newPath = joinUrlPaths(getLocale(), path);
|
||||
|
||||
return `/${newPath}`;
|
||||
},
|
||||
changeLocale: (locale: LocaleKey) => {
|
||||
setLocale(locale);
|
||||
|
||||
const pathWithoutLocale = location.pathname.split('/').slice(2).join('/');
|
||||
const newPath = joinUrlPaths(locale, pathWithoutLocale);
|
||||
navigate(`/${newPath}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchDictionary(locale: LocaleKey): Promise<Dictionary> {
|
||||
const dict: RawDictionary = (await import(`../../locales/${locale}.json`));
|
||||
const mergedDict = merge({}, defaultDict, dict);
|
||||
const flattened = i18n.flatten(mergedDict);
|
||||
|
||||
return flattened;
|
||||
}
|
||||
|
||||
export function getBrowserLocale(): LocaleKey {
|
||||
const browserLocale = navigator.language?.split('-')[0];
|
||||
|
||||
if (!browserLocale) {
|
||||
return 'en';
|
||||
}
|
||||
|
||||
return locales.find(locale => locale.key === browserLocale)?.key ?? 'en';
|
||||
}
|
||||
|
||||
export const RootI18nProvider: ParentComponent = (props) => {
|
||||
const browserLocale = getBrowserLocale();
|
||||
const [getLocale, setLocale] = makePersisted(createSignal<LocaleKey>(browserLocale), { name: 'it_tools_locale', storage: localStorage });
|
||||
|
||||
const [dict] = createResource(getLocale, fetchDictionary);
|
||||
|
||||
return (
|
||||
<Show when={dict()}>
|
||||
{dict => (
|
||||
<RootI18nContext.Provider
|
||||
value={{
|
||||
t: i18n.translator(dict),
|
||||
getLocale,
|
||||
setLocale,
|
||||
locales,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</RootI18nContext.Provider>
|
||||
)}
|
||||
</Show>
|
||||
);
|
||||
};
|
@@ -1,3 +0,0 @@
|
||||
import type { locales } from './i18n.constants';
|
||||
|
||||
export type LocaleKey = typeof locales[number]['key'];
|
@@ -1,87 +0,0 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { A } from '@solidjs/router';
|
||||
import { useCommandPalette } from '../command-palette/command-palette.provider';
|
||||
import { useI18n } from '../i18n/i18n.provider';
|
||||
import { useToolsStore } from '../tools/tools.store';
|
||||
import { Badge } from '../ui/components/badge';
|
||||
import { Button } from '../ui/components/button';
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from '../ui/components/card';
|
||||
import { cn } from '../ui/utils/cn';
|
||||
|
||||
export const HomePage: Component = () => {
|
||||
const { t } = useI18n();
|
||||
const { getTools } = useToolsStore();
|
||||
const { openCommandPalette } = useCommandPalette();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div class="w-full bg-[linear-gradient(to_right,#80808010_1px,transparent_1px),linear-gradient(to_bottom,#80808010_1px,transparent_1px)] bg-[size:48px_48px] pt-20">
|
||||
|
||||
<div class="flex justify-center gap-24 items-center p-6">
|
||||
<div class="max-w-xl flex flex-col gap-6 ">
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge class="text-primary bg-primary/10 hover:bg-primary/10">
|
||||
{t('home.open-source')}
|
||||
</Badge>
|
||||
|
||||
<Badge class="text-primary bg-primary/10 hover:bg-primary/10">
|
||||
{t('home.free')}
|
||||
</Badge>
|
||||
|
||||
<Badge class="text-primary bg-primary/10 hover:bg-primary/10">
|
||||
{t('home.self-hostable')}
|
||||
</Badge>
|
||||
</div>
|
||||
<h1 class="text-5xl font-semibold border-b border-transparent hover:no-underline h-auto py-0 px-1 ml--1 rounded-none !transition-border-color-250">
|
||||
<span class="font-bold ">IT</span>
|
||||
<span class="text-90% text-primary font-extrabold border border-5px leading-none border-current rounded-xl px-2 py-0.5 ml-3">TOOLS</span>
|
||||
</h1>
|
||||
|
||||
<p class="text-xl text-muted-foreground">
|
||||
{t('app.description')}
|
||||
</p>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<Button variant="default" as={A} href="tools">
|
||||
{t('home.all-tools')}
|
||||
<div class="i-tabler-arrow-right ml-2 text-base"></div>
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" onClick={openCommandPalette}>
|
||||
<div class="i-tabler-search mr-2 text-base" />
|
||||
{t('home.search-tools')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative hidden md:block">
|
||||
<div class="absolute top-4 left-0 w-full h-full flex items-center justify-center blur-2xl rounded-full opacity-20 bg-gradient-to-br from-primary to-transparent" />
|
||||
<div class="i-tabler-terminal text-9xl text-primary m-8" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gradient-to-t dark:from-background to-transparent h-24 mt-24"></div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 max-w-1200px mx-auto p-6">
|
||||
{getTools().map(tool => (
|
||||
<A href={tool.slug} class="h-full">
|
||||
<Card class="hover:(shadow-md transform scale-101) transition-transform h-full">
|
||||
<CardHeader>
|
||||
<div class={cn(tool.icon, 'size-12 text-muted-foreground/60')} />
|
||||
|
||||
<CardTitle class="text-base font-semibold">
|
||||
{tool.name}
|
||||
</CardTitle>
|
||||
|
||||
<CardDescription>
|
||||
{tool.description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</A>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@@ -1,37 +0,0 @@
|
||||
import type { Accessor, Component, ComponentProps } from 'solid-js';
|
||||
import { Button } from '@/modules/ui/components/button';
|
||||
import { omit } from 'lodash-es';
|
||||
import { Show, splitProps } from 'solid-js';
|
||||
import { useCopy } from './copy';
|
||||
|
||||
export const CopyButton: Component<{ textToCopy: Accessor<string | number>; toastMessage?: string } & ComponentProps<typeof Button>> = (props) => {
|
||||
const [localProps, buttonProps] = splitProps(props, ['textToCopy', 'toastMessage']);
|
||||
const { copy, getIsJustCopied } = useCopy(localProps.textToCopy, { toastMessage: localProps.toastMessage });
|
||||
|
||||
return (
|
||||
<Button onClick={copy} {...omit(buttonProps, ['textToCopy', 'toastMessage'])}>
|
||||
<Show
|
||||
when={buttonProps.children}
|
||||
fallback={(
|
||||
|
||||
getIsJustCopied()
|
||||
? (
|
||||
<>
|
||||
<div class="i-tabler-check mr-2 text-base" />
|
||||
Copied!
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<div class="i-tabler-copy mr-2 text-base" />
|
||||
Copy to clipboard
|
||||
</>
|
||||
)
|
||||
|
||||
)}
|
||||
>
|
||||
{buttonProps.children}
|
||||
</Show>
|
||||
</Button>
|
||||
);
|
||||
};
|
@@ -1,23 +0,0 @@
|
||||
import type { Accessor } from 'solid-js';
|
||||
import { createSignal } from 'solid-js';
|
||||
import { toast } from '../../ui/components/sonner';
|
||||
|
||||
export { useCopy, writeTextToClipboard };
|
||||
|
||||
function writeTextToClipboard({ text }: { text: string }) {
|
||||
return navigator.clipboard.writeText(text);
|
||||
}
|
||||
|
||||
function useCopy(getText: Accessor<string | number>, { toastMessage = 'Copied to clipboard' }: { toastMessage?: string } = {}) {
|
||||
const [getIsJustCopied, setIsJustCopied] = createSignal(false);
|
||||
|
||||
return {
|
||||
getIsJustCopied,
|
||||
copy: () => {
|
||||
writeTextToClipboard({ text: String(getText()) });
|
||||
setIsJustCopied(true);
|
||||
setTimeout(() => setIsJustCopied(false), 2000);
|
||||
toast(toastMessage);
|
||||
},
|
||||
};
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { createRefreshableSignal } from './signals';
|
||||
|
||||
describe('signals', () => {
|
||||
describe('createRefreshableSignal', () => {
|
||||
test('the state initially has the value returned by the getter', () => {
|
||||
const [getState] = createRefreshableSignal(() => 42);
|
||||
expect(getState()).to.eql(42);
|
||||
});
|
||||
|
||||
test('calling the refresh function updates the state', () => {
|
||||
let value = 0;
|
||||
const [getState, refresh] = createRefreshableSignal(() => value++);
|
||||
|
||||
expect(getState()).to.eql(0);
|
||||
|
||||
refresh();
|
||||
|
||||
expect(getState()).to.eql(1);
|
||||
expect(getState()).to.eql(1);
|
||||
});
|
||||
|
||||
test('the state can be muted using the setState function', () => {
|
||||
const [getState, , { setState }] = createRefreshableSignal(() => 0);
|
||||
|
||||
expect(getState()).to.eql(0);
|
||||
|
||||
setState(42);
|
||||
|
||||
expect(getState()).to.eql(42);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,13 +0,0 @@
|
||||
import { createSignal } from 'solid-js';
|
||||
|
||||
export { createRefreshableSignal };
|
||||
|
||||
function createRefreshableSignal<T>(getValue: () => T) {
|
||||
const [getState, setState] = createSignal<T>(getValue());
|
||||
|
||||
return [
|
||||
getState,
|
||||
() => setState(() => getValue()),
|
||||
{ setState },
|
||||
] as const;
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
import type { Component, ParentComponent } from 'solid-js';
|
||||
import { cn } from '@/modules/ui/utils/cn';
|
||||
|
||||
export const ToolHeader: ParentComponent<{ name: string; description: string; icon: string }> = (props) => {
|
||||
return (
|
||||
<div>
|
||||
<div class="w-full bg-[linear-gradient(to_right,#80808010_1px,transparent_1px),linear-gradient(to_bottom,#80808010_1px,transparent_1px)] bg-[size:48px_48px] pt-12">
|
||||
|
||||
<div class="flex gap-4 mb-8 max-w-1200px mx-auto px-6 items-start flex-col md:flex-row md:items-center">
|
||||
<div class="bg-card p-4 rounded-lg">
|
||||
<div class={cn(props.icon, 'size-8 md:size-12 text-muted-foreground')} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 class="text-xl font-semibold">
|
||||
{props.name}
|
||||
</h1>
|
||||
<div class="text-muted-foreground text-base">
|
||||
{props.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gradient-to-t dark:from-background to-transparent h-24 mt-12 mb--24"></div>
|
||||
</div>
|
||||
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "Random Port Generator",
|
||||
"description": "Generate a random port number outside of the reserved ports range (0-1023).",
|
||||
"refresh": "Refresh port",
|
||||
"copy-toast": "Port copied to clipboard"
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { CopyButton } from '@/modules/shared/copy/copy-button';
|
||||
import { createRefreshableSignal } from '@/modules/shared/signals';
|
||||
import { Button } from '@/modules/ui/components/button';
|
||||
import { Card, CardContent, CardHeader } from '@/modules/ui/components/card';
|
||||
import { ToolHeader } from '../../components/tool-header';
|
||||
import { useCurrentTool } from '../../tools.provider';
|
||||
import defaultDictionary from './locales/en.json';
|
||||
import { generateRandomPort } from './random-port-generator.services';
|
||||
|
||||
const RandomPortGenerator: Component = () => {
|
||||
const [getPort, refreshPort] = createRefreshableSignal(generateRandomPort);
|
||||
const { t, getTool } = useCurrentTool({ defaultDictionary });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ToolHeader {...getTool()} />
|
||||
|
||||
<div class="max-w-600px mx-auto px-6">
|
||||
<Card>
|
||||
<CardHeader class="flex justify-between items-center">
|
||||
<div class="my-6 text-center">
|
||||
|
||||
<div class="text-base text-muted-foreground mb-2">
|
||||
Random port:
|
||||
</div>
|
||||
|
||||
<div class="text-4xl font-mono">
|
||||
|
||||
{getPort()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 md:gap-4 mt-4 flex-col md:flex-row w-full justify-center">
|
||||
<Button onClick={refreshPort} variant="outline">
|
||||
<div class="i-tabler-refresh mr-2 text-base text-muted-foreground" />
|
||||
{t('refresh')}
|
||||
</Button>
|
||||
|
||||
<CopyButton textToCopy={getPort} toastMessage={t('copy-toast')} />
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RandomPortGenerator;
|
@@ -1,5 +0,0 @@
|
||||
import { random } from 'lodash-es';
|
||||
|
||||
export function generateRandomPort() {
|
||||
return random(1024, 65535);
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
import { defineTool } from '../../tools.models';
|
||||
|
||||
export const randomPortGeneratorTool = defineTool({
|
||||
slug: 'random-port-generator',
|
||||
entryFile: () => import('./random-port-generator.page'),
|
||||
icon: 'i-tabler-server',
|
||||
createdAt: new Date('2024-10-03'),
|
||||
dirName: 'random-port-generator',
|
||||
});
|
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "Token Generator",
|
||||
"description": "Generate random string with the characters you want, uppercase, lowercase letters, numbers and/or symbols.",
|
||||
"uppercase": "Uppercase letters (A-Z)",
|
||||
"lowercase": "Lowercase letters (a-z)",
|
||||
"numbers": "Numbers (0-9)",
|
||||
"symbols": "Special characters (!@#...)",
|
||||
"length": "Length",
|
||||
"result-placeholder": "Your token will appear here"
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "Générateur de token",
|
||||
"description": "Génère une chaîne de caractères aléatoire, contrôlez les caractères que vous voulez, lettres majuscules, minuscules, chiffres et/ou symboles.",
|
||||
"uppercase": "Lettres majuscules (A-Z)",
|
||||
"lowercase": "Lettres minuscules (a-z)",
|
||||
"numbers": "Chiffres (0-9)",
|
||||
"symbols": "Caractères spéciaux (!@#...)",
|
||||
"length": "Longueur",
|
||||
"result-placeholder": "Le token apparaîtra ici"
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
import { sample as sampleImpl, times } from 'lodash-es';
|
||||
|
||||
export function createToken({
|
||||
withUppercase = true,
|
||||
withLowercase = true,
|
||||
withNumbers = true,
|
||||
withSymbols = false,
|
||||
length = 64,
|
||||
alphabet,
|
||||
sample = sampleImpl,
|
||||
}: {
|
||||
withUppercase?: boolean;
|
||||
withLowercase?: boolean;
|
||||
withNumbers?: boolean;
|
||||
withSymbols?: boolean;
|
||||
length?: number;
|
||||
alphabet?: string;
|
||||
sample?: (str: string) => string;
|
||||
}) {
|
||||
const allAlphabet = alphabet ?? [
|
||||
withUppercase ? 'ABCDEFGHIJKLMOPQRSTUVWXYZ' : '',
|
||||
withLowercase ? 'abcdefghijklmopqrstuvwxyz' : '',
|
||||
withNumbers ? '0123456789' : '',
|
||||
withSymbols ? '.,;:!?./-"\'#{([-|\\@)]=}*+' : '',
|
||||
].join('');
|
||||
|
||||
return times(length, () => sample(allAlphabet)).join('');
|
||||
}
|
@@ -1,109 +0,0 @@
|
||||
import { CopyButton } from '@/modules/shared/copy/copy-button';
|
||||
import { createRefreshableSignal } from '@/modules/shared/signals';
|
||||
import { Button } from '@/modules/ui/components/button';
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/modules/ui/components/card';
|
||||
import { Switch, SwitchControl, SwitchLabel, SwitchThumb } from '@/modules/ui/components/switch';
|
||||
import { TextArea } from '@/modules/ui/components/textarea';
|
||||
import { TextFieldRoot } from '@/modules/ui/components/textfield';
|
||||
import { type Component, createSignal } from 'solid-js';
|
||||
import { ToolHeader } from '../../components/tool-header';
|
||||
import { useCurrentTool } from '../../tools.provider';
|
||||
import defaultDictionary from './locales/en.json';
|
||||
import { createToken } from './token-generator.models';
|
||||
|
||||
const TokenGeneratorTool: Component = () => {
|
||||
const [getUseUpperCase, setUseUpperCase] = createSignal(true);
|
||||
const [getUseLowerCase, setUseLowerCase] = createSignal(true);
|
||||
const [getUseNumbers, setUseNumbers] = createSignal(true);
|
||||
const [getUseSpecialCharacters, setUseSpecialCharacters] = createSignal(false);
|
||||
const [getLength] = createSignal(64);
|
||||
|
||||
const { t, getTool } = useCurrentTool({ defaultDictionary });
|
||||
|
||||
const [getToken, refreshToken] = createRefreshableSignal(() => createToken({
|
||||
withUppercase: getUseUpperCase(),
|
||||
withLowercase: getUseLowerCase(),
|
||||
withNumbers: getUseNumbers(),
|
||||
withSymbols: getUseSpecialCharacters(),
|
||||
length: getLength(),
|
||||
}));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ToolHeader {...getTool()} />
|
||||
|
||||
<div class="mx-auto max-w-1200px p-6 flex flex-col gap-4 md:flex-row items-start">
|
||||
<Card>
|
||||
<CardHeader class="border-b border-border">
|
||||
<CardTitle class="text-muted-foreground">
|
||||
Configuration
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent class="pt-6 flex flex-col gap-2">
|
||||
<Switch class="flex items-center gap-2" checked={getUseUpperCase()} onChange={setUseUpperCase}>
|
||||
<SwitchControl>
|
||||
<SwitchThumb />
|
||||
</SwitchControl>
|
||||
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
|
||||
{t('uppercase')}
|
||||
</SwitchLabel>
|
||||
</Switch>
|
||||
|
||||
<Switch class="flex items-center gap-2" checked={getUseLowerCase()} onChange={setUseLowerCase}>
|
||||
<SwitchControl>
|
||||
<SwitchThumb />
|
||||
</SwitchControl>
|
||||
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
|
||||
{t('lowercase')}
|
||||
</SwitchLabel>
|
||||
</Switch>
|
||||
|
||||
<Switch class="flex items-center gap-2" checked={getUseNumbers()} onChange={setUseNumbers}>
|
||||
<SwitchControl>
|
||||
<SwitchThumb />
|
||||
</SwitchControl>
|
||||
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
|
||||
{t('numbers')}
|
||||
</SwitchLabel>
|
||||
</Switch>
|
||||
|
||||
<Switch class="flex items-center gap-2" checked={getUseSpecialCharacters()} onChange={setUseSpecialCharacters}>
|
||||
<SwitchControl>
|
||||
<SwitchThumb />
|
||||
</SwitchControl>
|
||||
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
|
||||
{t('symbols')}
|
||||
</SwitchLabel>
|
||||
</Switch>
|
||||
</CardContent>
|
||||
|
||||
</Card>
|
||||
|
||||
<Card class="flex-1">
|
||||
<CardHeader class="border-b border-border flex justify-between flex-row py-3 items-center">
|
||||
<CardTitle class="text-muted-foreground">
|
||||
Your token
|
||||
</CardTitle>
|
||||
|
||||
<div class="flex justify-center items-center gap-2">
|
||||
<Button onClick={refreshToken} variant="outline">
|
||||
<div class="i-tabler-refresh mr-2 text-base text-muted-foreground" />
|
||||
Refresh token
|
||||
</Button>
|
||||
|
||||
<CopyButton textToCopy={getToken} toastMessage={t('copy-toast')} />
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent class="pt-6 text-center">
|
||||
{getToken()}
|
||||
</CardContent>
|
||||
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TokenGeneratorTool;
|
@@ -1,9 +0,0 @@
|
||||
import { defineTool } from '../../tools.models';
|
||||
|
||||
export const tokenGeneratorTool = defineTool({
|
||||
slug: 'token-generator',
|
||||
entryFile: () => import('./token-generator.page'),
|
||||
icon: 'i-tabler-key',
|
||||
createdAt: new Date('2024-02-13'),
|
||||
dirName: 'token-generator',
|
||||
});
|
@@ -1,43 +0,0 @@
|
||||
import { useI18n } from '@/modules/i18n/i18n.provider';
|
||||
import { safely } from '@corentinth/chisels';
|
||||
import { useParams } from '@solidjs/router';
|
||||
import { type Component, createResource, lazy, Show } from 'solid-js';
|
||||
import { CurrentToolProvider } from '../tools.provider';
|
||||
import { getToolDefinitionBySlug } from '../tools.registry';
|
||||
|
||||
export const ToolPage: Component = () => {
|
||||
const params = useParams();
|
||||
const { getLocale, t } = useI18n();
|
||||
|
||||
const toolDefinition = getToolDefinitionBySlug({ slug: params.toolSlug });
|
||||
const ToolComponent = lazy(toolDefinition.entryFile);
|
||||
|
||||
const [toolDict] = createResource(getLocale, async (locale) => {
|
||||
const [dict, error] = await safely(import(`../definitions/${toolDefinition.dirName}/locales/${locale}.json`));
|
||||
|
||||
if (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return dict ?? { default: {} };
|
||||
});
|
||||
|
||||
return (
|
||||
<Show when={toolDict()}>
|
||||
{toolLocaleDict => (
|
||||
<CurrentToolProvider
|
||||
toolLocaleDict={toolLocaleDict}
|
||||
tool={() => ({
|
||||
icon: toolDefinition.icon,
|
||||
dirName: toolDefinition.dirName,
|
||||
createdAt: toolDefinition.createdAt,
|
||||
name: t(`tools.${toolDefinition.slug}.name` as any),
|
||||
description: t(`tools.${toolDefinition.slug}.description` as any),
|
||||
})}
|
||||
>
|
||||
<ToolComponent />
|
||||
</CurrentToolProvider>
|
||||
)}
|
||||
</Show>
|
||||
);
|
||||
};
|
@@ -1,16 +0,0 @@
|
||||
import type { Component } from 'solid-js';
|
||||
|
||||
export { defineTool };
|
||||
|
||||
function defineTool(toolDefinition: {
|
||||
slug: string;
|
||||
entryFile: () => Promise<{ default: Component }>;
|
||||
dirName: string;
|
||||
icon: string;
|
||||
createdAt: Date;
|
||||
}) {
|
||||
return {
|
||||
...toolDefinition,
|
||||
key: toolDefinition.slug,
|
||||
};
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
import type { Accessor, ParentComponent } from 'solid-js';
|
||||
import type { ToolDefinition } from './tools.types';
|
||||
import { flatten, translator } from '@solid-primitives/i18n';
|
||||
import { merge } from 'lodash-es';
|
||||
import { createContext, useContext } from 'solid-js';
|
||||
|
||||
type ToolProviderContext = {
|
||||
toolLocaleDict: Accessor<Record<string, string>>;
|
||||
tool: Accessor<Pick<ToolDefinition, 'icon' | 'dirName' | 'createdAt'> & { name: string; description: string }>;
|
||||
};
|
||||
|
||||
const CurrentToolContext = createContext<ToolProviderContext>();
|
||||
|
||||
export function useCurrentTool<T>({ defaultDictionary }: { defaultDictionary: T }) {
|
||||
const context = useContext(CurrentToolContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useCurrentTool must be used within a CurrentToolProvider');
|
||||
}
|
||||
|
||||
const { toolLocaleDict, tool } = context;
|
||||
|
||||
return {
|
||||
t: translator(() => flatten(merge({}, defaultDictionary, toolLocaleDict()))),
|
||||
getTool: tool,
|
||||
};
|
||||
}
|
||||
|
||||
export const CurrentToolProvider: ParentComponent<ToolProviderContext> = (props) => {
|
||||
return (
|
||||
<CurrentToolContext.Provider value={props}>
|
||||
{props.children}
|
||||
</CurrentToolContext.Provider>
|
||||
);
|
||||
};
|
@@ -1,17 +0,0 @@
|
||||
import { keyBy, map } from 'lodash-es';
|
||||
import { randomPortGeneratorTool } from './definitions/random-port-generator/random-port-generator.tool';
|
||||
import { tokenGeneratorTool } from './definitions/token-generator/token-generator.tool';
|
||||
|
||||
export const toolDefinitions = [
|
||||
tokenGeneratorTool,
|
||||
randomPortGeneratorTool,
|
||||
];
|
||||
|
||||
export const toolSlugs = map(toolDefinitions, 'slug');
|
||||
export const toolDefinitionBySlug = keyBy(toolDefinitions, 'slug');
|
||||
|
||||
export { getToolDefinitionBySlug };
|
||||
|
||||
function getToolDefinitionBySlug({ slug }: { slug: string }) {
|
||||
return toolDefinitionBySlug[slug];
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
import { createMemo } from 'solid-js';
|
||||
import { useI18n } from '../i18n/i18n.provider';
|
||||
import { toolDefinitions } from './tools.registry';
|
||||
|
||||
export { useToolsStore };
|
||||
|
||||
function useToolsStore() {
|
||||
const { t } = useI18n();
|
||||
|
||||
const getTools = createMemo(() => toolDefinitions.map((tool) => {
|
||||
return {
|
||||
...tool,
|
||||
name: t(`tools.${tool.slug}.name` as any) ?? tool.slug,
|
||||
description: t(`tools.${tool.slug}.description` as any) ?? tool.slug,
|
||||
};
|
||||
}));
|
||||
|
||||
return { getTools };
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
import type { Flatten, Translator } from '@solid-primitives/i18n';
|
||||
import type { defineTool } from './tools.models';
|
||||
|
||||
export type ToolI18nFactory = <T extends Record<string, string>>(args: { defaultDictionary: T }) => { t: Translator<Flatten<T>> };
|
||||
|
||||
export type ToolDefinition = ReturnType<typeof defineTool>;
|
@@ -1,37 +0,0 @@
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '@/modules/ui/utils/cn';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import { type ComponentProps, splitProps } from 'solid-js';
|
||||
|
||||
export const badgeVariants = cva(
|
||||
'inline-flex items-center rounded-md px-2.5 py-0.5 text-xs font-semibold transition-shadow focus-visible:(outline-none ring-1.5 ring-ring)',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/80',
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
destructive: 'bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
|
||||
outline: 'border text-foreground',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export function Badge(props: ComponentProps<'div'> & VariantProps<typeof badgeVariants>) {
|
||||
const [local, rest] = splitProps(props, ['class', 'variant']);
|
||||
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
badgeVariants({
|
||||
variant: local.variant,
|
||||
}),
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
import type { ButtonRootProps } from '@kobalte/core/button';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import type { ValidComponent } from 'solid-js';
|
||||
import { cn } from '@/modules/ui/utils/cn';
|
||||
import { Button as ButtonPrimitive } from '@kobalte/core/button';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import { splitProps } from 'solid-js';
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-shadow focus-visible:(outline-none ring-1.5 ring-ring) disabled:(pointer-events-none opacity-50) bg-inherit',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
||||
outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
||||
ghost: 'hover:(bg-accent text-accent-foreground)',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2',
|
||||
sm: 'h-8 px-3 text-xs',
|
||||
lg: 'h-10 px-8',
|
||||
icon: 'h-9 w-9',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
type buttonProps<T extends ValidComponent = 'button'> = ButtonRootProps<T> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export function Button<T extends ValidComponent = 'button'>(props: PolymorphicProps<T, buttonProps<T>>) {
|
||||
const [local, rest] = splitProps(props as buttonProps, [
|
||||
'class',
|
||||
'variant',
|
||||
'size',
|
||||
]);
|
||||
|
||||
return (
|
||||
<ButtonPrimitive
|
||||
class={cn(
|
||||
buttonVariants({
|
||||
size: local.size,
|
||||
variant: local.variant,
|
||||
}),
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
import type { ComponentProps, ParentComponent } from 'solid-js';
|
||||
import { cn } from '@/modules/ui/utils/cn';
|
||||
import { splitProps } from 'solid-js';
|
||||
|
||||
export function Card(props: ComponentProps<'div'>) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'rounded-xl border bg-card text-card-foreground shadow',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function CardHeader(props: ComponentProps<'div'>) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<div class={cn('flex flex-col space-y-1.5 p-6', local.class)} {...rest} />
|
||||
);
|
||||
}
|
||||
|
||||
export const CardTitle: ParentComponent<ComponentProps<'h1'>> = (props) => {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<h1
|
||||
class={cn('font-semibold leading-none tracking-tight', local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const CardDescription: ParentComponent<ComponentProps<'h3'>> = (
|
||||
props,
|
||||
) => {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<h3 class={cn('text-sm text-muted-foreground', local.class)} {...rest} />
|
||||
);
|
||||
};
|
||||
|
||||
export function CardContent(props: ComponentProps<'div'>) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return <div class={cn('p-6 pt-0', local.class)} {...rest} />;
|
||||
}
|
||||
|
||||
export function CardFooter(props: ComponentProps<'div'>) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<div class={cn('flex items-center p-6 pt-0', local.class)} {...rest} />
|
||||
);
|
||||
}
|
@@ -1,151 +0,0 @@
|
||||
import type {
|
||||
CommandDialogProps,
|
||||
CommandEmptyProps,
|
||||
CommandGroupProps,
|
||||
CommandInputProps,
|
||||
CommandItemProps,
|
||||
CommandListProps,
|
||||
CommandRootProps,
|
||||
} from 'cmdk-solid';
|
||||
import type { ComponentProps, VoidProps } from 'solid-js';
|
||||
import { cn } from '@/modules/ui/utils/cn';
|
||||
import { Command as CommandPrimitive } from 'cmdk-solid';
|
||||
import { splitProps } from 'solid-js';
|
||||
import { Dialog, DialogContent } from './dialog';
|
||||
|
||||
export function Command(props: CommandRootProps) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<CommandPrimitive
|
||||
class={cn(
|
||||
'flex size-full flex-col overflow-hidden bg-popover text-popover-foreground',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommandList(props: CommandListProps) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<CommandPrimitive.List
|
||||
class={cn(
|
||||
'max-h-[300px] overflow-y-auto overflow-x-hidden p-1',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommandInput(props: VoidProps<CommandInputProps>) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<div class="flex items-center border-b px-3" cmdk-input-wrapper="">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="mr-2 h-4 w-4 shrink-0 opacity-50"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 10a7 7 0 1 0 14 0a7 7 0 1 0-14 0m18 11l-6-6"
|
||||
/>
|
||||
<title>Search</title>
|
||||
</svg>
|
||||
<CommandPrimitive.Input
|
||||
class={cn(
|
||||
'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:(cursor-not-allowed opacity-50)',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommandItem(props: CommandItemProps) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<CommandPrimitive.Item
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5! text-sm outline-none aria-selected:(bg-accent text-accent-foreground) aria-disabled:(pointer-events-none opacity-50)',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommandShortcut(props: ComponentProps<'span'>) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<span
|
||||
class={cn(
|
||||
'ml-auto text-xs tracking-widest text-muted-foreground',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommandDialog(props: CommandDialogProps) {
|
||||
const [local, rest] = splitProps(props, ['children']);
|
||||
|
||||
return (
|
||||
<Dialog {...rest}>
|
||||
<DialogContent class="overflow-hidden p-0">
|
||||
<Command class="[&_[cmdk-group-heading]]:(px-2 font-medium text-muted-foreground) [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:size-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:(px-2 py-3) [&_[cmdk-item]_svg]:size-5">
|
||||
{local.children}
|
||||
</Command>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommandEmpty(props: CommandEmptyProps) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<CommandPrimitive.Empty
|
||||
class={cn('py-6 text-center text-sm', local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommandGroup(props: CommandGroupProps) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<CommandPrimitive.Group
|
||||
class={cn(
|
||||
'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:(px-2 py-1.5 text-xs font-medium text-muted-foreground)',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommandSeparator(props: CommandEmptyProps) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<CommandPrimitive.Separator
|
||||
class={cn('-mx-1 h-px bg-border', local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -1,122 +0,0 @@
|
||||
import type {
|
||||
DialogContentProps,
|
||||
DialogDescriptionProps,
|
||||
DialogTitleProps,
|
||||
} from '@kobalte/core/dialog';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import type { ComponentProps, ParentProps, ValidComponent } from 'solid-js';
|
||||
import { cn } from '@/modules/ui/utils/cn';
|
||||
import { Dialog as DialogPrimitive } from '@kobalte/core/dialog';
|
||||
import { splitProps } from 'solid-js';
|
||||
|
||||
export const Dialog = DialogPrimitive;
|
||||
export const DialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
type dialogContentProps<T extends ValidComponent = 'div'> = ParentProps<
|
||||
DialogContentProps<T> & {
|
||||
class?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export function DialogContent<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dialogContentProps<T>>) {
|
||||
const [local, rest] = splitProps(props as dialogContentProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
|
||||
return (
|
||||
<DialogPrimitive.Portal>
|
||||
<DialogPrimitive.Overlay
|
||||
class={cn(
|
||||
'fixed inset-0 z-50 bg-background/80 data-[expanded]:(animate-in fade-in-0) data-[closed]:(animate-out fade-out-0)',
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
<DialogPrimitive.Content
|
||||
class={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[expanded]:(animate-in fade-in-0 zoom-in-95 slide-in-from-left-1/2 slide-in-from-top-48% duration-200) data-[closed]:(animate-out fade-out-0 zoom-out-95 slide-out-to-left-1/2 slide-out-to-top-48% duration-200) md:w-full sm:rounded-lg',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{local.children}
|
||||
<DialogPrimitive.CloseButton class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:(outline-none ring-1.5 ring-ring ring-offset-2) disabled:pointer-events-none bg-inherit transition-property-[opacity,box-shadow]">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="h-4 w-4"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M18 6L6 18M6 6l12 12"
|
||||
/>
|
||||
<title>Close</title>
|
||||
</svg>
|
||||
</DialogPrimitive.CloseButton>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
|
||||
type dialogTitleProps<T extends ValidComponent = 'h2'> = DialogTitleProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export function DialogTitle<T extends ValidComponent = 'h2'>(props: PolymorphicProps<T, dialogTitleProps<T>>) {
|
||||
const [local, rest] = splitProps(props as dialogTitleProps, ['class']);
|
||||
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
class={cn('text-lg font-semibold text-foreground', local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type dialogDescriptionProps<T extends ValidComponent = 'p'> =
|
||||
DialogDescriptionProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export function DialogDescription<T extends ValidComponent = 'p'>(props: PolymorphicProps<T, dialogDescriptionProps<T>>) {
|
||||
const [local, rest] = splitProps(props as dialogDescriptionProps, ['class']);
|
||||
|
||||
return (
|
||||
<DialogPrimitive.Description
|
||||
class={cn('text-sm text-muted-foreground', local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function DialogHeader(props: ComponentProps<'div'>) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'flex flex-col space-y-2 text-center sm:text-left',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function DialogFooter(props: ComponentProps<'div'>) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'flex flex-col-reverse sm:(flex-row justify-end space-x-2)',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -1,286 +0,0 @@
|
||||
import type {
|
||||
DropdownMenuCheckboxItemProps,
|
||||
DropdownMenuContentProps,
|
||||
DropdownMenuGroupLabelProps,
|
||||
DropdownMenuItemLabelProps,
|
||||
DropdownMenuItemProps,
|
||||
DropdownMenuRadioItemProps,
|
||||
DropdownMenuRootProps,
|
||||
DropdownMenuSeparatorProps,
|
||||
DropdownMenuSubTriggerProps,
|
||||
} from '@kobalte/core/dropdown-menu';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import type { ComponentProps, ParentProps, ValidComponent } from 'solid-js';
|
||||
import { cn } from '@/modules/ui/utils/cn';
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from '@kobalte/core/dropdown-menu';
|
||||
import { mergeProps, splitProps } from 'solid-js';
|
||||
|
||||
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
||||
export const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
||||
export const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
||||
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
||||
|
||||
export function DropdownMenu(props: DropdownMenuRootProps) {
|
||||
const merge = mergeProps<DropdownMenuRootProps[]>({ gutter: 4 }, props);
|
||||
|
||||
return <DropdownMenuPrimitive {...merge} />;
|
||||
}
|
||||
|
||||
type dropdownMenuContentProps<T extends ValidComponent = 'div'> =
|
||||
DropdownMenuContentProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export function DropdownMenuContent<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuContentProps<T>>) {
|
||||
const [local, rest] = splitProps(props as dropdownMenuContentProps, [
|
||||
'class',
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
class={cn(
|
||||
'min-w-8rem z-50 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[expanded]:(animate-in fade-in-0 zoom-in-95) data-[closed]:(animate-out fade-out-0 zoom-out-95) focus-visible:(outline-none ring-1.5 ring-ring) transition-shadow',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
|
||||
type dropdownMenuItemProps<T extends ValidComponent = 'div'> =
|
||||
DropdownMenuItemProps<T> & {
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
};
|
||||
|
||||
export function DropdownMenuItem<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuItemProps<T>>) {
|
||||
const [local, rest] = splitProps(props as dropdownMenuItemProps, [
|
||||
'class',
|
||||
'inset',
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.Item
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:(bg-accent text-accent-foreground) data-[disabled]:(pointer-events-none opacity-50)',
|
||||
local.inset && 'pl-8',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type dropdownMenuGroupLabelProps<T extends ValidComponent = 'span'> =
|
||||
DropdownMenuGroupLabelProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export function DropdownMenuGroupLabel<T extends ValidComponent = 'span'>(props: PolymorphicProps<T, dropdownMenuGroupLabelProps<T>>) {
|
||||
const [local, rest] = splitProps(props as dropdownMenuGroupLabelProps, [
|
||||
'class',
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.GroupLabel
|
||||
as="div"
|
||||
class={cn('px-2 py-1.5 text-sm font-semibold', local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type dropdownMenuItemLabelProps<T extends ValidComponent = 'div'> =
|
||||
DropdownMenuItemLabelProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export function DropdownMenuItemLabel<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuItemLabelProps<T>>) {
|
||||
const [local, rest] = splitProps(props as dropdownMenuItemLabelProps, [
|
||||
'class',
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.ItemLabel
|
||||
as="div"
|
||||
class={cn('px-2 py-1.5 text-sm font-semibold', local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type dropdownMenuSeparatorProps<T extends ValidComponent = 'hr'> =
|
||||
DropdownMenuSeparatorProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export function DropdownMenuSeparator<T extends ValidComponent = 'hr'>(props: PolymorphicProps<T, dropdownMenuSeparatorProps<T>>) {
|
||||
const [local, rest] = splitProps(props as dropdownMenuSeparatorProps, [
|
||||
'class',
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
class={cn('-mx-1 my-1 h-px bg-muted', local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function DropdownMenuShortcut(props: ComponentProps<'span'>) {
|
||||
const [local, rest] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<span
|
||||
class={cn('ml-auto text-xs tracking-widest opacity-60', local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type dropdownMenuSubTriggerProps<T extends ValidComponent = 'div'> = ParentProps<DropdownMenuSubTriggerProps<T> & { class?: string }>;
|
||||
|
||||
export function DropdownMenuSubTrigger<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuSubTriggerProps<T>>) {
|
||||
const [local, rest] = splitProps(props as dropdownMenuSubTriggerProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
class={cn(
|
||||
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[expanded]:bg-accent',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{local.children}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
class="ml-auto h-4 w-4"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m9 6l6 6l-6 6"
|
||||
/>
|
||||
<title>Arrow</title>
|
||||
</svg>
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
type dropdownMenuSubContentProps<T extends ValidComponent = 'div'> =
|
||||
DropdownMenuSubTriggerProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export function DropdownMenuSubContent<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuSubContentProps<T>>) {
|
||||
const [local, rest] = splitProps(props as dropdownMenuSubContentProps, [
|
||||
'class',
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
class={cn(
|
||||
'min-w-8rem z-50 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[expanded]:(animate-in fade-in-0 zoom-in-95) data-[closed]:(animate-out fade-out-0 zoom-out-95)',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
|
||||
type dropdownMenuCheckboxItemProps<T extends ValidComponent = 'div'> = ParentProps<DropdownMenuCheckboxItemProps<T> & { class?: string }>;
|
||||
|
||||
export function DropdownMenuCheckboxItem<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuCheckboxItemProps<T>>) {
|
||||
const [local, rest] = splitProps(props as dropdownMenuCheckboxItemProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:(bg-accent text-accent-foreground) data-[disabled]:(pointer-events-none opacity-50)',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<DropdownMenuPrimitive.ItemIndicator class="absolute left-2 inline-flex h-4 w-4 items-center justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="h-4 w-4"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m5 12l5 5L20 7"
|
||||
/>
|
||||
<title>Checkbox</title>
|
||||
</svg>
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
{props.children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
);
|
||||
}
|
||||
|
||||
type dropdownMenuRadioItemProps<T extends ValidComponent = 'div'> = ParentProps<
|
||||
DropdownMenuRadioItemProps<T> & {
|
||||
class?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export function DropdownMenuRadioItem<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, dropdownMenuRadioItemProps<T>>) {
|
||||
const [local, rest] = splitProps(props as dropdownMenuRadioItemProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:(bg-accent text-accent-foreground) data-[disabled]:(pointer-events-none opacity-50)',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<DropdownMenuPrimitive.ItemIndicator class="absolute left-2 inline-flex h-4 w-4 items-center justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="h-2 w-2"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M0 0h24v24H0z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M7 3.34a10 10 0 1 1-4.995 8.984L2 12l.005-.324A10 10 0 0 1 7 3.34"
|
||||
/>
|
||||
</g>
|
||||
<title>Radio</title>
|
||||
</svg>
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
{props.children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
);
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
import { Toaster as Sonner, toast } from 'solid-sonner';
|
||||
|
||||
export { toast };
|
||||
|
||||
export function Toaster(props: Parameters<typeof Sonner>[0]) {
|
||||
return (
|
||||
<Sonner
|
||||
class="toaster group"
|
||||
toastOptions={{
|
||||
classes: {
|
||||
toast: 'group toast group-[.toaster]:(bg-background text-foreground border-border shadow-lg)',
|
||||
description: 'group-[.toast]:text-muted-foreground',
|
||||
actionButton: 'group-[.toast]:(bg-primary text-primary-foreground)',
|
||||
cancelButton: 'group-[.toast]:(bg-muted text-muted-foreground)',
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -1,54 +0,0 @@
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import type {
|
||||
SwitchControlProps,
|
||||
SwitchThumbProps,
|
||||
} from '@kobalte/core/switch';
|
||||
import type { ParentProps, ValidComponent, VoidProps } from 'solid-js';
|
||||
import { cn } from '@/modules/ui/utils/cn';
|
||||
import { Switch as SwitchPrimitive } from '@kobalte/core/switch';
|
||||
import { splitProps } from 'solid-js';
|
||||
|
||||
export const SwitchLabel = SwitchPrimitive.Label;
|
||||
export const Switch = SwitchPrimitive;
|
||||
export const SwitchErrorMessage = SwitchPrimitive.ErrorMessage;
|
||||
export const SwitchDescription = SwitchPrimitive.Description;
|
||||
|
||||
type switchControlProps<T extends ValidComponent = 'input'> = ParentProps<SwitchControlProps<T> & { class?: string }>;
|
||||
|
||||
export function SwitchControl<T extends ValidComponent = 'input'>(props: PolymorphicProps<T, switchControlProps<T>>) {
|
||||
const [local, rest] = splitProps(props as switchControlProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SwitchPrimitive.Input class="[&:focus-visible+div]:(outline-none ring-1.5 ring-ring ring-offset-2 ring-offset-background)" />
|
||||
<SwitchPrimitive.Control
|
||||
class={cn(
|
||||
'inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent bg-input shadow-sm transition-shadow data-[disabled]:(cursor-not-allowed opacity-50) data-[checked]:bg-primary transition-property-[box-shadow,color,background-color]',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{local.children}
|
||||
</SwitchPrimitive.Control>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type switchThumbProps<T extends ValidComponent = 'div'> = VoidProps<SwitchThumbProps<T> & { class?: string }>;
|
||||
|
||||
export function SwitchThumb<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, switchThumbProps<T>>) {
|
||||
const [local, rest] = splitProps(props as switchThumbProps, ['class']);
|
||||
|
||||
return (
|
||||
<SwitchPrimitive.Thumb
|
||||
class={cn(
|
||||
'pointer-events-none block h-4 w-4 translate-x-0 rounded-full bg-background shadow-lg transition-transform data-[checked]:translate-x-4',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import type { TextFieldTextAreaProps } from '@kobalte/core/text-field';
|
||||
import type { ValidComponent, VoidProps } from 'solid-js';
|
||||
import { cn } from '@/modules/ui/utils/cn';
|
||||
import { TextArea as TextFieldPrimitive } from '@kobalte/core/text-field';
|
||||
import { splitProps } from 'solid-js';
|
||||
|
||||
type textAreaProps<T extends ValidComponent = 'textarea'> = VoidProps<
|
||||
TextFieldTextAreaProps<T> & {
|
||||
class?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export function TextArea<T extends ValidComponent = 'textarea'>(props: PolymorphicProps<T, textAreaProps<T>>) {
|
||||
const [local, rest] = splitProps(props as textAreaProps, ['class']);
|
||||
|
||||
return (
|
||||
<TextFieldPrimitive
|
||||
class={cn(
|
||||
'flex min-h-[60px] w-full rounded-md border border-input bg-inherit px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:(outline-none ring-1.5 ring-ring) disabled:(cursor-not-allowed opacity-50) transition-shadow',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -1,116 +0,0 @@
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import type {
|
||||
TextFieldDescriptionProps,
|
||||
TextFieldErrorMessageProps,
|
||||
TextFieldInputProps,
|
||||
TextFieldLabelProps,
|
||||
TextFieldRootProps,
|
||||
} from '@kobalte/core/text-field';
|
||||
import type { ValidComponent, VoidProps } from 'solid-js';
|
||||
import { cn } from '@/modules/ui/utils/cn';
|
||||
import { TextField as TextFieldPrimitive } from '@kobalte/core/text-field';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import { splitProps } from 'solid-js';
|
||||
|
||||
type textFieldProps<T extends ValidComponent = 'div'> =
|
||||
TextFieldRootProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export function TextFieldRoot<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, textFieldProps<T>>) {
|
||||
const [local, rest] = splitProps(props as textFieldProps, ['class']);
|
||||
|
||||
return <TextFieldPrimitive class={cn('space-y-1', local.class)} {...rest} />;
|
||||
}
|
||||
|
||||
export const textfieldLabel = cva(
|
||||
'text-sm data-[disabled]:(cursor-not-allowed opacity-70) font-medium',
|
||||
{
|
||||
variants: {
|
||||
label: {
|
||||
true: 'data-[invalid]:text-destructive',
|
||||
},
|
||||
error: {
|
||||
true: 'text-destructive text-xs',
|
||||
},
|
||||
description: {
|
||||
true: 'font-normal text-muted-foreground',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
label: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
type textFieldLabelProps<T extends ValidComponent = 'label'> =
|
||||
TextFieldLabelProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export function TextFieldLabel<T extends ValidComponent = 'label'>(props: PolymorphicProps<T, textFieldLabelProps<T>>) {
|
||||
const [local, rest] = splitProps(props as textFieldLabelProps, ['class']);
|
||||
|
||||
return (
|
||||
<TextFieldPrimitive.Label
|
||||
class={cn(textfieldLabel(), local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type textFieldErrorMessageProps<T extends ValidComponent = 'div'> =
|
||||
TextFieldErrorMessageProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export function TextFieldErrorMessage<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, textFieldErrorMessageProps<T>>) {
|
||||
const [local, rest] = splitProps(props as textFieldErrorMessageProps, [
|
||||
'class',
|
||||
]);
|
||||
|
||||
return (
|
||||
<TextFieldPrimitive.ErrorMessage
|
||||
class={cn(textfieldLabel({ error: true }), local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type textFieldDescriptionProps<T extends ValidComponent = 'div'> =
|
||||
TextFieldDescriptionProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
export function TextFieldDescription<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, textFieldDescriptionProps<T>>) {
|
||||
const [local, rest] = splitProps(props as textFieldDescriptionProps, [
|
||||
'class',
|
||||
]);
|
||||
|
||||
return (
|
||||
<TextFieldPrimitive.Description
|
||||
class={cn(textfieldLabel({ description: true }), local.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type textFieldInputProps<T extends ValidComponent = 'input'> = VoidProps<
|
||||
TextFieldInputProps<T> & {
|
||||
class?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export function TextField<T extends ValidComponent = 'input'>(props: PolymorphicProps<T, textFieldInputProps<T>>) {
|
||||
const [local, rest] = splitProps(props as textFieldInputProps, ['class']);
|
||||
|
||||
return (
|
||||
<TextFieldPrimitive.Input
|
||||
class={cn(
|
||||
'flex h-9 w-full rounded-md border border-input bg-inherit px-3 py-1 text-sm shadow-sm file:(border-0 bg-transparent text-sm font-medium) placeholder:text-muted-foreground focus-visible:(outline-none ring-1.5 ring-ring) disabled:(cursor-not-allowed opacity-50) transition-shadow',
|
||||
local.class,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -1,279 +0,0 @@
|
||||
import type { Component, ParentComponent } from 'solid-js';
|
||||
import { useCommandPalette } from '@/modules/command-palette/command-palette.provider';
|
||||
import { useI18n } from '@/modules/i18n/i18n.provider';
|
||||
import { Button } from '@/modules/ui/components/button';
|
||||
import { A } from '@solidjs/router';
|
||||
import { Badge } from '../components/badge';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from '../components/dropdown-menu';
|
||||
import { useThemeStore } from '../themes/theme.store';
|
||||
import { cn } from '../utils/cn';
|
||||
import { socialLinks } from './app.layouts.constants';
|
||||
|
||||
const ThemeSwitcher: Component = () => {
|
||||
const themeStore = useThemeStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuItem onClick={() => themeStore.setColorMode({ mode: 'light' })} class="flex items-center gap-2 cursor-pointer">
|
||||
<div class="i-tabler-sun text-lg"></div>
|
||||
{t('navbar.theme.light-mode')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => themeStore.setColorMode({ mode: 'dark' })} class="flex items-center gap-2 cursor-pointer">
|
||||
<div class="i-tabler-moon text-lg"></div>
|
||||
{t('navbar.theme.dark-mode')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => themeStore.setColorMode({ mode: 'system' })} class="flex items-center gap-2 cursor-pointer">
|
||||
<div class="i-tabler-device-laptop text-lg"></div>
|
||||
{t('navbar.theme.system-mode')}
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const LanguageSwitcher: Component = () => {
|
||||
const { t, getLocale, changeLocale, locales } = useI18n();
|
||||
|
||||
return (
|
||||
<>
|
||||
{locales.map(locale => (
|
||||
<DropdownMenuItem onClick={() => changeLocale(locale.key)} class={cn('flex items-center gap-2 cursor-pointer', { 'font-semibold': getLocale() === locale.key })}>
|
||||
{locale.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem as="a" class="flex items-center gap-2 cursor-pointer" target="_blank" rel="noopener noreferrer" href="https://github.com/CorentinTh/it-tools">
|
||||
{t('navbar.contribute-to-i18n')}
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Navbar: Component = () => {
|
||||
const themeStore = useThemeStore();
|
||||
const { t } = useI18n();
|
||||
const { openCommandPalette } = useCommandPalette();
|
||||
const getIsMacOs = () => navigator?.userAgent?.match(/Macintosh;/);
|
||||
|
||||
return (
|
||||
<div class="border-b border-border bg-surface">
|
||||
<div class="flex items-center justify-between px-6 py-3 mx-auto max-w-1200px">
|
||||
<div class="flex items-center gap-4">
|
||||
<Button variant="link" class="text-xl font-semibold border-b border-transparent hover:no-underline h-auto py-0 px-1 ml--1 rounded-none !transition-border-color-250" as={A} href="/" aria-label="Home">
|
||||
<span class="font-bold text-foreground">IT</span>
|
||||
<span class="text-80% font-extrabold border border-2px leading-none border-current rounded-md px-1 py-0.5 ml-1 text-primary">TOOLS</span>
|
||||
</Button>
|
||||
|
||||
<Button size="sm" variant="outline" class="bg-card transition flex items-center gap-2 text-muted-foreground" onClick={openCommandPalette}>
|
||||
<div class="i-tabler-search text-base"></div>
|
||||
{t('commandPalette.trigger.search')}
|
||||
<Badge variant="secondary" class="text-muted-foreground text-10px!">
|
||||
{getIsMacOs() ? '⌘ + K' : 'Ctrl + K'}
|
||||
</Badge>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
<Button variant="ghost" class="text-lg px-0 size-9 hidden md:inline-flex" as={A} href="https://github.com/CorentinTh/enclosed" target="_blank" rel="noopener noreferrer" aria-label="GitHub repository">
|
||||
<div class="i-tabler-brand-github"></div>
|
||||
</Button>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as={Button} class="text-lg px-0 size-9 hidden md:inline-flex" variant="ghost" aria-label="Change theme">
|
||||
<div classList={{ 'i-tabler-moon': themeStore.getColorMode() === 'dark', 'i-tabler-sun': themeStore.getColorMode() === 'light' }}></div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="w-42">
|
||||
<ThemeSwitcher />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as={Button} class="text-lg px-0 size-9 hidden md:inline-flex" variant="ghost" aria-label="Language">
|
||||
<div class="i-custom-language size-4"></div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<LanguageSwitcher />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<DropdownMenu>
|
||||
|
||||
<DropdownMenuTrigger as={Button} class="text-lg px-0 size-9" variant="ghost" aria-label="Menu icon">
|
||||
<div class="i-tabler-dots-vertical hidden md:block"></div>
|
||||
<div class="i-tabler-menu-2 block md:hidden"></div>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent class="w-46">
|
||||
|
||||
{/* Mobile only items */}
|
||||
<DropdownMenuItem as="a" class="flex items-center gap-2 cursor-pointer md:hidden" target="_blank" href="https://github.com/CorentinTh/enclosed" rel="noopener noreferrer">
|
||||
<div class="i-tabler-brand-github text-lg"></div>
|
||||
{t('navbar.github')}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger as="a" class="flex items-center gap-2 md:hidden" aria-label="Change theme">
|
||||
<div class="text-lg" classList={{ 'i-tabler-moon': themeStore.getColorMode() === 'dark', 'i-tabler-sun': themeStore.getColorMode() === 'light' }}></div>
|
||||
{t('navbar.theme.theme')}
|
||||
</DropdownMenuSubTrigger>
|
||||
|
||||
<DropdownMenuSubContent>
|
||||
<ThemeSwitcher />
|
||||
</DropdownMenuSubContent>
|
||||
|
||||
</DropdownMenuSub>
|
||||
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger as="a" class="flex items-center text-medium gap-2 md:hidden" aria-label="Change language">
|
||||
<div class="i-custom-language size-4"></div>
|
||||
{t('navbar.language')}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
<LanguageSwitcher />
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
|
||||
{/* Default items */}
|
||||
|
||||
<DropdownMenuItem as="a" class="flex items-center gap-2 cursor-pointer" target="_blank" href="https://github.com/CorentinTh/it-tools/issues/new/choose" rel="noopener noreferrer">
|
||||
<div class="i-tabler-bug text-lg"></div>
|
||||
{t('navbar.report-bug')}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem as="a" class="flex items-center gap-2 cursor-pointer" target="_blank" href="https://buymeacoffee.com/cthmsst" rel="noopener noreferrer">
|
||||
<div class="i-tabler-pig-money text-lg"></div>
|
||||
{t('navbar.support')}
|
||||
</DropdownMenuItem>
|
||||
|
||||
</DropdownMenuContent>
|
||||
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Footer: Component = () => {
|
||||
const { t, createLocalizedUrl } = useI18n();
|
||||
|
||||
const getFooterSections = () => [
|
||||
{
|
||||
title: t('footer.resources.title'),
|
||||
items: [
|
||||
{ label: t('footer.resources.all-tools'), to: createLocalizedUrl({ path: '/tools' }) },
|
||||
{ label: t('footer.resources.github'), href: 'https://github.com/CorentinTh/it-tools' },
|
||||
{ label: t('footer.resources.support'), href: 'https://buymeacoffee.com/cthmsst' },
|
||||
{ label: 'Humans.txt', href: '/humans.txt' },
|
||||
{ label: t('footer.resources.license'), href: 'https://github.com/CorentinTh/it-tools/blob/main/LICENSE' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('footer.support.title'),
|
||||
items: [
|
||||
{ label: t('footer.support.report-bug'), href: 'https://github.com/CorentinTh/it-tools/issues/new/choose' },
|
||||
{ label: t('footer.support.request-feature'), href: 'https://github.com/CorentinTh/it-tools/issues/new/choose' },
|
||||
{ label: t('footer.support.contribute'), href: 'https://github.com/CorentinTh/it-tools/blob/main/CONTRIBUTING.md' },
|
||||
{ label: t('footer.support.contact'), href: 'https://github.com/CorentinTh/it-tools/issues/new/choose' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('footer.friends.title'),
|
||||
items: [
|
||||
{ label: 'Jugly.io', href: 'https://jugly.io' },
|
||||
{ label: 'Enclosed.cc', href: 'https://enclosed.cc' },
|
||||
],
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
return (
|
||||
<footer class="bg-card border-t border-border">
|
||||
<div class="py-12 px-6 mx-auto max-w-1200px">
|
||||
|
||||
<div class="flex items-start justify-between flex-col md:flex-row gap-12">
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<A href="/" class="text-2xl font-semibold border-b border-transparent hover:no-underline h-auto py-0 px-1 ml--1 rounded-none !transition-border-color-250 group text-muted-foreground flex items-center gap-1">
|
||||
<span class="font-bold group-hover:text-foreground transition">IT</span>
|
||||
<span class="text-80% font-extrabold border border-2px leading-none border-current rounded-md px-1 py-0.5 ml-1 group-hover:text-primary transition">TOOLS</span>
|
||||
</A>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 mt-4">
|
||||
{socialLinks.map(({ icon, href, label }) => (
|
||||
<a href={href} target="_blank" rel="noopener noreferrer" class="text-2xl text-muted-foreground hover:text-primary transition" aria-label={label}>
|
||||
<div class={icon}></div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div class="text-muted-foreground mt-2">
|
||||
Crafted with
|
||||
{' '}
|
||||
<span class="i-tabler-heart inline-block text-base mb--0.5"></span>
|
||||
{' '}
|
||||
by
|
||||
{' '}
|
||||
<a href="https://corentin.tech" target="_blank" rel="noopener" class="hover:text-primary transition">
|
||||
Corentin Thomasset
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-12">
|
||||
{getFooterSections().map(({ title, items }) => (
|
||||
<div>
|
||||
<h4 class="font-semibold text-foreground">{title}</h4>
|
||||
<ul class="mt-4">
|
||||
{items.map(({ label, to, href }) => (
|
||||
<li class="mt-1">
|
||||
{to
|
||||
? (
|
||||
<A href={to} class="text-muted-foreground hover:text-primary transition">
|
||||
{label}
|
||||
</A>
|
||||
)
|
||||
: (
|
||||
<a href={href} target="_blank" rel="noopener" class="text-muted-foreground hover:text-primary transition">
|
||||
{label}
|
||||
</a>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-muted-foreground border-t border-border pt-4 mt-12">
|
||||
<span>
|
||||
©
|
||||
{new Date().getFullYear()}
|
||||
{' '}
|
||||
Corentin Thomasset
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-foreground opacity-80%">
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export const AppLayout: ParentComponent = (props) => {
|
||||
return (
|
||||
<div class="flex flex-col h-screen min-h-0">
|
||||
|
||||
<Navbar />
|
||||
|
||||
<div class="flex-1 pb-20 ">{props.children}</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
@@ -1,17 +0,0 @@
|
||||
export const socialLinks = [
|
||||
{
|
||||
icon: 'i-tabler-brand-github',
|
||||
href: 'https://github.com/CorentinTh/it-tools',
|
||||
label: 'GitHub',
|
||||
},
|
||||
{
|
||||
icon: 'i-tabler-brand-x',
|
||||
href: 'https://x.com/ittoolsdottech',
|
||||
label: 'X',
|
||||
},
|
||||
{
|
||||
icon: 'i-tabler-coffee',
|
||||
href: 'https://buymeacoffee.com/cthmsst',
|
||||
label: 'Support the project',
|
||||
},
|
||||
];
|
@@ -1,11 +0,0 @@
|
||||
import type { ConfigColorMode } from '@kobalte/core/color-mode';
|
||||
import { useColorMode } from '@kobalte/core/color-mode';
|
||||
|
||||
export function useThemeStore() {
|
||||
const { setColorMode, colorMode: getColorMode } = useColorMode();
|
||||
|
||||
return {
|
||||
setColorMode: ({ mode }: { mode: ConfigColorMode }) => setColorMode(mode),
|
||||
getColorMode,
|
||||
};
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
import type { ClassValue } from 'clsx';
|
||||
import clsx from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export const cn = (...classLists: ClassValue[]) => twMerge(clsx(classLists));
|
@@ -1,12 +0,0 @@
|
||||
---
|
||||
to: src/modules/tools/definitions/<%= h.changeCase.param(name) %>/<%= h.changeCase.param(name) %>.tool.ts
|
||||
---
|
||||
import { defineTool } from '../../tools.models'
|
||||
|
||||
export const <%= h.changeCase.camel(name) %>Tool = defineTool({
|
||||
slug: '<%= h.changeCase.param(name) %>',
|
||||
entryFile: () => import('./<%= h.changeCase.param(name) %>.page'),
|
||||
icon: 'i-tabler-question-mark',
|
||||
createdAt: new Date('<%= new Date().toISOString().split('T')[0] %>'),
|
||||
dirName: '<%= h.changeCase.param(name) %>',
|
||||
})
|
@@ -1,4 +0,0 @@
|
||||
---
|
||||
to: src/modules/tools/definitions/<%= h.changeCase.param(name) %>/locales/en.json
|
||||
---
|
||||
{}
|
@@ -1,6 +0,0 @@
|
||||
---
|
||||
inject: true
|
||||
to: src/modules/tools/tools.registry.ts
|
||||
at_line: 0
|
||||
---
|
||||
import { <%= h.changeCase.camel(name) %>Tool } from './definitions/<%= h.changeCase.param(name) %>/<%= h.changeCase.param(name) %>.tool';
|
@@ -1,6 +0,0 @@
|
||||
---
|
||||
inject: true
|
||||
to: src/modules/tools/tools.registry.ts
|
||||
before: "^]"
|
||||
---
|
||||
<%= h.changeCase.camel(name) %>Tool,
|
@@ -1,14 +0,0 @@
|
||||
---
|
||||
to: src/modules/tools/definitions/<%= h.changeCase.param(name) %>/<%= h.changeCase.param(name) %>.page.tsx
|
||||
---
|
||||
import type { Component } from 'solid-js';
|
||||
|
||||
const <%= h.changeCase.pascal(name) %>: Component = () => {
|
||||
return (
|
||||
<div class="mx-auto max-w-1200px p-6">
|
||||
<h1><%= h.changeCase.title(name) %></h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default <%= h.changeCase.pascal(name) %>;
|
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "solid-js",
|
||||
"baseUrl": "./",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"resolveJsonModule": true,
|
||||
"types": ["vite/client"],
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
@@ -1,120 +0,0 @@
|
||||
import {
|
||||
defineConfig,
|
||||
presetIcons,
|
||||
presetUno,
|
||||
presetWebFonts,
|
||||
transformerDirectives,
|
||||
transformerVariantGroup,
|
||||
} from 'unocss';
|
||||
import presetAnimations from 'unocss-preset-animations';
|
||||
import { toolDefinitions } from './src/modules/tools/tools.registry';
|
||||
import { socialLinks } from './src/modules/ui/layouts/app.layouts.constants';
|
||||
|
||||
export default defineConfig({
|
||||
presets: [
|
||||
presetUno({
|
||||
dark: {
|
||||
dark: '[data-kb-theme="dark"]',
|
||||
light: '[data-kb-theme="light"]',
|
||||
},
|
||||
prefix: '',
|
||||
}),
|
||||
presetAnimations(),
|
||||
presetWebFonts({
|
||||
fonts: {
|
||||
sans: 'Inter:400,500,600,700,800,900',
|
||||
},
|
||||
}),
|
||||
presetIcons({
|
||||
collections: {
|
||||
custom: {
|
||||
language: '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 512 512"><path fill="currentColor" d="m478.33 433.6l-90-218a22 22 0 0 0-40.67 0l-90 218a22 22 0 1 0 40.67 16.79L316.66 406h102.67l18.33 44.39A22 22 0 0 0 458 464a22 22 0 0 0 20.32-30.4ZM334.83 362L368 281.65L401.17 362Zm-66.99-19.08a22 22 0 0 0-4.89-30.7c-.2-.15-15-11.13-36.49-34.73c39.65-53.68 62.11-114.75 71.27-143.49H330a22 22 0 0 0 0-44H214V70a22 22 0 0 0-44 0v20H54a22 22 0 0 0 0 44h197.25c-9.52 26.95-27.05 69.5-53.79 108.36c-31.41-41.68-43.08-68.65-43.17-68.87a22 22 0 0 0-40.58 17c.58 1.38 14.55 34.23 52.86 83.93c.92 1.19 1.83 2.35 2.74 3.51c-39.24 44.35-77.74 71.86-93.85 80.74a22 22 0 1 0 21.07 38.63c2.16-1.18 48.6-26.89 101.63-85.59c22.52 24.08 38 35.44 38.93 36.1a22 22 0 0 0 30.75-4.9Z" /></svg>',
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
transformers: [transformerVariantGroup(), transformerDirectives()],
|
||||
theme: {
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))',
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
},
|
||||
warning: {
|
||||
DEFAULT: 'hsl(var(--warning))',
|
||||
foreground: 'hsl(var(--warning-foreground))',
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))',
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))',
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))',
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))',
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
},
|
||||
animation: {
|
||||
keyframes: {
|
||||
'accordion-down':
|
||||
'{ from { height: 0 } to { height: var(--kb-accordion-content-height) } }',
|
||||
'accordion-up':
|
||||
'{ from { height: var(--kb-accordion-content-height) } to { height: 0 } }',
|
||||
'collapsible-down':
|
||||
'{ from { height: 0 } to { height: var(--kb-collapsible-content-height) } }',
|
||||
'collapsible-up':
|
||||
'{ from { height: var(--kb-collapsible-content-height) } to { height: 0 } }',
|
||||
'caret-blink': '{ 0%,70%,100% { opacity: 1 } 20%,50% { opacity: 0 } }',
|
||||
},
|
||||
timingFns: {
|
||||
'accordion-down': 'ease-out',
|
||||
'accordion-up': 'ease-out',
|
||||
'collapsible-down': 'ease-out',
|
||||
'collapsible-up': 'ease-out',
|
||||
'caret-blink': 'ease-out',
|
||||
},
|
||||
durations: {
|
||||
'accordion-down': '0.2s',
|
||||
'accordion-up': '0.2s',
|
||||
'collapsible-down': '0.2s',
|
||||
'collapsible-up': '0.2s',
|
||||
'caret-blink': '1.25s',
|
||||
},
|
||||
counts: {
|
||||
'caret-blink': 'infinite',
|
||||
},
|
||||
},
|
||||
},
|
||||
safelist: [
|
||||
...toolDefinitions.map(tool => tool.icon),
|
||||
...socialLinks.map(({ icon }) => icon),
|
||||
],
|
||||
shortcuts: {
|
||||
'i-logo': 'i-tabler-terminal',
|
||||
},
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user