diff --git a/.env b/.env new file mode 100644 index 0000000..c5b7790 --- /dev/null +++ b/.env @@ -0,0 +1,12 @@ +VITE_BASE_URL=/ + +VITE_APP_TITLE=SoybeanAdmin + +VITE_APP_DESC=SoybeanAdmin is a fresh and elegant admin template + +# the prefix of the icon name +VITE_ICON_PREFIX=icon + +# the prefix of the local svg icon component, must include VITE_ICON_PREFIX +# format {VITE_ICON_PREFIX}-{local icon name} +VITE_ICON_LOCAL_PREFIX=icon-local diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..e980e58 --- /dev/null +++ b/.env.development @@ -0,0 +1,2 @@ +VITE_HTTP_PROXY=Y + diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..4a71bd3 --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +VITE_ROUTER_HISTORY_MODE=hash \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..c75f79a --- /dev/null +++ b/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": "sa/vue", + "settings": { + "import/core-modules": ["uno.css", "~icons/*", "virtual:svg-icons-register"] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ba2840 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json +!.vscode/launch.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +package-lock.json +yarn.lock +pnpm-lock.yaml \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43e6b35 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +registry=https://registry.npmmirror.com/ +shamefully-hoist=true diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..7980cb0 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,17 @@ +{ + "recommendations": [ + "antfu.unocss", + "dbaeumer.vscode-eslint", + "editorconfig.editorconfig", + "esbenp.prettier-vscode", + "formulahendry.auto-complete-tag", + "formulahendry.auto-close-tag", + "formulahendry.auto-rename-tag", + "kisstkondoros.vscode-gutter-preview", + "mariusalchimavicius.json-to-ts", + "mhutchie.git-graph", + "sdras.vue-vscode-snippets", + "vue.volar", + "vue.vscode-typescript-vue-plugin" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b1cb49a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Vue debugger", + "url": "http://localhost:9527", + "webRoot": "${workspaceFolder}" + }, + { + "type": "node", + "request": "launch", + "name": "TS debugger", + "skipFiles": ["/**"], + "runtimeArgs": ["--loader", "tsx"], + "program": "${relativeFile}" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bc25955 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,60 @@ +{ + "cSpell.words": [ + "Antd", + "antv", + "clsx", + "colord", + "consola", + "EDITMSG", + "espree", + "execa", + "heroicons", + "hexcode", + "iconify", + "INDEXEDDB", + "jiti", + "kolorist", + "Laba", + "localforage", + "LOCALSTORAGE", + "nocheck", + "nprogress", + "ofetch", + "preflights", + "sider", + "tada", + "Uncapitalize", + "unocss", + "unplugin", + "VITE", + "vitepress", + "vueuse", + "WEBSQL", + "wechat" + ], + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "editor.fontLigatures": true, + "editor.formatOnSave": false, + "editor.quickSuggestions": { + "strings": true + }, + "editor.tabSize": 2, + "files.associations": { + "*.env.*": "dotenv", + "*.svg": "html" + }, + "files.eol": "\n", + "i18n-ally.displayLanguage": "zh-CN", + "i18n-ally.enabledParsers": ["ts"], + "i18n-ally.enabledFrameworks": ["vue"], + "i18n-ally.editor.preferEditor": true, + "i18n-ally.keystyle": "nested", + "i18n-ally.localesPaths": ["src/locales/lang"], + "unocss.root": ["./"], + "[html][css][less][scss][sass][markdown][yaml][yml][json][jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..131b57a --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# @sa/antd + +SoybeanAdmin AntDesign diff --git a/build/index.ts b/build/index.ts new file mode 100644 index 0000000..460ab6b --- /dev/null +++ b/build/index.ts @@ -0,0 +1 @@ +export * from './plugins'; diff --git a/build/plugins/index.ts b/build/plugins/index.ts new file mode 100644 index 0000000..d437da7 --- /dev/null +++ b/build/plugins/index.ts @@ -0,0 +1,26 @@ +import type { PluginOption } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import VueDevtools from 'vite-plugin-vue-devtools'; +import progress from 'vite-plugin-progress'; +import { setupElegantRouter } from './router'; +import { setupUnocss } from './unocss'; +import { setupUnplugin } from './unplugin'; + +export function setupVitePlugins(viteEnv: Env.ImportMeta) { + const plugins: PluginOption = [ + vue({ + script: { + defineModel: true + } + }), + vueJsx(), + VueDevtools(), + setupElegantRouter(), + setupUnocss(viteEnv), + ...setupUnplugin(viteEnv), + progress() + ]; + + return plugins; +} diff --git a/build/plugins/router.ts b/build/plugins/router.ts new file mode 100644 index 0000000..766570c --- /dev/null +++ b/build/plugins/router.ts @@ -0,0 +1,24 @@ +import ElegantVueRouter from '@elegant-router/vue/vite'; +import type { RouteKey } from '@elegant-router/types'; + +export function setupElegantRouter() { + return ElegantVueRouter({ + layouts: { + base: 'src/layouts/base-layout/index.vue', + blank: 'src/layouts/blank-layout/index.vue' + }, + routePathTransformer(routeName, routePath) { + const key = routeName as RouteKey; + + if (key === 'login') { + const modules: UnionKey.LoginModule[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat']; + + const moduleReg = modules.join('|'); + + return `/login/:module(${moduleReg})?`; + } + + return routePath; + } + }); +} diff --git a/build/plugins/unocss.ts b/build/plugins/unocss.ts new file mode 100644 index 0000000..a81a210 --- /dev/null +++ b/build/plugins/unocss.ts @@ -0,0 +1,33 @@ +import path from 'node:path'; +import unocss from '@unocss/vite'; +import presetIcons from '@unocss/preset-icons'; +import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders'; + +export function setupUnocss(viteEnv: Env.ImportMeta) { + const { VITE_ICON_PREFIX, VITE_ICON_LOCAL_PREFIX } = viteEnv; + + const localIconPath = path.join(process.cwd(), 'src/assets/svg-icon'); + + /** + * the name of the local icon collection + */ + const collectionName = VITE_ICON_LOCAL_PREFIX.replace(`${VITE_ICON_PREFIX}-`, ''); + + return unocss({ + presets: [ + presetIcons({ + prefix: `${VITE_ICON_PREFIX}-`, + scale: 1, + extraProperties: { + display: 'inline-block' + }, + collections: { + [collectionName]: FileSystemIconLoader(localIconPath, svg => + svg.replace(/^ + svg.replace(/^ + + + + + + %VITE_APP_TITLE% + + +
+
+
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..66c2087 --- /dev/null +++ b/package.json @@ -0,0 +1,78 @@ +{ + "name": "@sa/element", + "version": "1.0.0", + "description": "A fresh and elegant admin template, based on Vue3,Vite3,TypeScript,ElementPlus and UnoCSS.", + "author": { + "name": "Soybean", + "email": "soybeanjs@outlook.com", + "url": "https://github.com/soybeanjs" + }, + "scripts": { + "dev": "vite", + "build": "run-s typecheck build-only", + "preview": "vite preview", + "build-only": "vite build", + "typecheck": "vue-tsc --noEmit --skipLibCheck", + "lint": "eslint . --fix", + "format": "sa prettier-write", + "commit": "sa git-commit", + "cleanup": "sa cleanup", + "update-pkg": "sa update-pkg", + "prepare": "simple-git-hooks" + }, + "dependencies": { + "@iconify/vue": "4.1.1", + "@sa/hooks": "workspace:*", + "@sa/materials": "workspace:*", + "@sa/utils": "workspace:*", + "@vueuse/core": "10.5.0", + "ant-design-vue": "4.0.6", + "element-plus": "2.4.1", + "lodash-es": "4.17.21", + "nprogress": "0.2.0", + "pinia": "2.1.7", + "vue": "3.3.4", + "vue-i18n": "9.5.0", + "vue-router": "4.2.5" + }, + "devDependencies": { + "@elegant-router/vue": "0.2.3", + "@iconify-json/heroicons": "1.1.13", + "@iconify-json/ic": "1.1.14", + "@iconify-json/icon-park-outline": "1.1.12", + "@iconify-json/line-md": "1.1.33", + "@iconify-json/logos": "1.1.37", + "@iconify-json/material-symbols": "1.1.60", + "@iconify-json/ph": "1.1.6", + "@iconify-json/tabler": "1.1.95", + "@sa/scripts": "workspace:*", + "@sa/uno-preset": "workspace:*", + "@types/lodash-es": "4.17.10", + "@types/node": "20.8.7", + "@types/nprogress": "0.2.2", + "@unocss/preset-icons": "0.56.5", + "@unocss/preset-uno": "0.56.5", + "@unocss/transformer-directives": "0.56.5", + "@unocss/transformer-variant-group": "0.56.5", + "@unocss/vite": "0.56.5", + "@vitejs/plugin-vue": "4.4.0", + "@vitejs/plugin-vue-jsx": "3.0.2", + "cross-env": "7.0.3", + "eslint-config-sa": "workspace:*", + "npm-run-all": "4.1.5", + "simple-git-hooks": "2.9.0", + "typescript": "5.2.2", + "unocss-preset-scrollbar": "0.3.0", + "unplugin-icons": "0.17.1", + "unplugin-vue-components": "0.25.2", + "vite": "4.5.0", + "vite-plugin-progress": "0.0.7", + "vite-plugin-svg-icons": "2.0.1", + "vite-plugin-vue-devtools": "1.0.0-rc.5", + "vue-tsc": "1.8.19" + }, + "simple-git-hooks": { + "commit-msg": "pnpm sa git-commit-verify", + "pre-commit": "pnpm typecheck && pnpm sa lint-staged" + } +} diff --git a/packages/color-palette/package.json b/packages/color-palette/package.json new file mode 100644 index 0000000..eb467b5 --- /dev/null +++ b/packages/color-palette/package.json @@ -0,0 +1,17 @@ +{ + "name": "@sa/color-palette", + "version": "1.0.0", + "exports": { + ".": "./src/index.ts" + }, + "typesVersions": { + "*": { + "*": [ + "./src/*" + ] + } + }, + "dependencies": { + "colord": "2.9.3" + } +} diff --git a/packages/color-palette/src/color.ts b/packages/color-palette/src/color.ts new file mode 100644 index 0000000..797148d --- /dev/null +++ b/packages/color-palette/src/color.ts @@ -0,0 +1,29 @@ +import { colord, extend } from 'colord'; +import type { HslColor } from 'colord'; +import labPlugin from 'colord/plugins/lab'; + +extend([labPlugin]); + +export function isValidColor(color: string) { + return colord(color).isValid(); +} + +export function getHex(color: string) { + return colord(color).toHex(); +} + +export function getRgb(color: string) { + return colord(color).toRgb(); +} + +export function getHsl(color: string) { + return colord(color).toHsl(); +} + +export function getDeltaE(color1: string, color2: string) { + return colord(color1).delta(color2); +} + +export function transformHslToHex(color: HslColor) { + return colord(color).toHex(); +} diff --git a/packages/color-palette/src/index.ts b/packages/color-palette/src/index.ts new file mode 100644 index 0000000..42568ca --- /dev/null +++ b/packages/color-palette/src/index.ts @@ -0,0 +1,42 @@ +import { getColorPaletteFamily } from './palette'; +import { getColorName } from './name'; +import type { ColorPalette, ColorPaletteNumber, ColorPaletteItem, ColorPaletteFamily } from './type'; +import defaultPalettes from './json/palette.json'; + +/** + * get color palette by provided color and color name + * @param color the provided color + * @param colorName color name + */ +export function getColorPalette(color: string, colorName: string) { + const colorPaletteFamily = getColorPaletteFamily(color, colorName); + + const colorMap = new Map(); + + colorPaletteFamily.palettes.forEach(palette => { + colorMap.set(palette.number, palette); + }); + + const mainColor = colorMap.get(500) as ColorPaletteItem; + const matchColor = colorPaletteFamily.palettes.find(palette => palette.hexcode === color) as ColorPaletteItem; + + const colorPalette: ColorPalette = { + ...colorPaletteFamily, + colorMap, + main: mainColor, + match: matchColor + }; + + return colorPalette; +} + +export default getColorPalette; + +/** + * the builtin color palettes + */ +const colorPalettes = defaultPalettes as ColorPaletteFamily[]; + +export { getColorName, colorPalettes }; + +export type { ColorPalette, ColorPaletteNumber, ColorPaletteItem, ColorPaletteFamily }; diff --git a/packages/color-palette/src/json/color-name.json b/packages/color-palette/src/json/color-name.json new file mode 100644 index 0000000..82b2786 --- /dev/null +++ b/packages/color-palette/src/json/color-name.json @@ -0,0 +1,1568 @@ +[ + ["000000", "Black"], + ["000080", "Navy Blue"], + ["0000C8", "Dark Blue"], + ["0000FF", "Blue"], + ["000741", "Stratos"], + ["001B1C", "Swamp"], + ["002387", "Resolution Blue"], + ["002900", "Deep Fir"], + ["002E20", "Burnham"], + ["002FA7", "International Klein Blue"], + ["003153", "Prussian Blue"], + ["003366", "Midnight Blue"], + ["003399", "Smalt"], + ["003532", "Deep Teal"], + ["003E40", "Cyprus"], + ["004620", "Kaitoke Green"], + ["0047AB", "Cobalt"], + ["004816", "Crusoe"], + ["004950", "Sherpa Blue"], + ["0056A7", "Endeavour"], + ["00581A", "Camarone"], + ["0066CC", "Science Blue"], + ["0066FF", "Blue Ribbon"], + ["00755E", "Tropical Rain Forest"], + ["0076A3", "Allports"], + ["007BA7", "Deep Cerulean"], + ["007EC7", "Lochmara"], + ["007FFF", "Azure Radiance"], + ["008080", "Teal"], + ["0095B6", "Bondi Blue"], + ["009DC4", "Pacific Blue"], + ["00A693", "Persian Green"], + ["00A86B", "Jade"], + ["00CC99", "Caribbean Green"], + ["00CCCC", "Robin's Egg Blue"], + ["00FF00", "Green"], + ["00FF7F", "Spring Green"], + ["00FFFF", "Cyan / Aqua"], + ["010D1A", "Blue Charcoal"], + ["011635", "Midnight"], + ["011D13", "Holly"], + ["012731", "Daintree"], + ["01361C", "Cardin Green"], + ["01371A", "County Green"], + ["013E62", "Astronaut Blue"], + ["013F6A", "Regal Blue"], + ["014B43", "Aqua Deep"], + ["015E85", "Orient"], + ["016162", "Blue Stone"], + ["016D39", "Fun Green"], + ["01796F", "Pine Green"], + ["017987", "Blue Lagoon"], + ["01826B", "Deep Sea"], + ["01A368", "Green Haze"], + ["022D15", "English Holly"], + ["02402C", "Sherwood Green"], + ["02478E", "Congress Blue"], + ["024E46", "Evening Sea"], + ["026395", "Bahama Blue"], + ["02866F", "Observatory"], + ["02A4D3", "Cerulean"], + ["03163C", "Tangaroa"], + ["032B52", "Green Vogue"], + ["036A6E", "Mosque"], + ["041004", "Midnight Moss"], + ["041322", "Black Pearl"], + ["042E4C", "Blue Whale"], + ["044022", "Zuccini"], + ["044259", "Teal Blue"], + ["051040", "Deep Cove"], + ["051657", "Gulf Blue"], + ["055989", "Venice Blue"], + ["056F57", "Watercourse"], + ["062A78", "Catalina Blue"], + ["063537", "Tiber"], + ["069B81", "Gossamer"], + ["06A189", "Niagara"], + ["073A50", "Tarawera"], + ["080110", "Jaguar"], + ["081910", "Black Bean"], + ["082567", "Deep Sapphire"], + ["088370", "Elf Green"], + ["08E8DE", "Bright Turquoise"], + ["092256", "Downriver"], + ["09230F", "Palm Green"], + ["09255D", "Madison"], + ["093624", "Bottle Green"], + ["095859", "Deep Sea Green"], + ["097F4B", "Salem"], + ["0A001C", "Black Russian"], + ["0A480D", "Dark Fern"], + ["0A6906", "Japanese Laurel"], + ["0A6F75", "Atoll"], + ["0B0B0B", "Cod Gray"], + ["0B0F08", "Marshland"], + ["0B1107", "Gordons Green"], + ["0B1304", "Black Forest"], + ["0B6207", "San Felix"], + ["0BDA51", "Malachite"], + ["0C0B1D", "Ebony"], + ["0C0D0F", "Woodsmoke"], + ["0C1911", "Racing Green"], + ["0C7A79", "Surfie Green"], + ["0C8990", "Blue Chill"], + ["0D0332", "Black Rock"], + ["0D1117", "Bunker"], + ["0D1C19", "Aztec"], + ["0D2E1C", "Bush"], + ["0E0E18", "Cinder"], + ["0E2A30", "Firefly"], + ["0F2D9E", "Torea Bay"], + ["10121D", "Vulcan"], + ["101405", "Green Waterloo"], + ["105852", "Eden"], + ["110C6C", "Arapawa"], + ["120A8F", "Ultramarine"], + ["123447", "Elephant"], + ["126B40", "Jewel"], + ["130000", "Diesel"], + ["130A06", "Asphalt"], + ["13264D", "Blue Zodiac"], + ["134F19", "Parsley"], + ["140600", "Nero"], + ["1450AA", "Tory Blue"], + ["151F4C", "Bunting"], + ["1560BD", "Denim"], + ["15736B", "Genoa"], + ["161928", "Mirage"], + ["161D10", "Hunter Green"], + ["162A40", "Big Stone"], + ["163222", "Celtic"], + ["16322C", "Timber Green"], + ["163531", "Gable Green"], + ["171F04", "Pine Tree"], + ["175579", "Chathams Blue"], + ["182D09", "Deep Forest Green"], + ["18587A", "Blumine"], + ["19330E", "Palm Leaf"], + ["193751", "Nile Blue"], + ["1959A8", "Fun Blue"], + ["1A1A68", "Lucky Point"], + ["1AB385", "Mountain Meadow"], + ["1B0245", "Tolopea"], + ["1B1035", "Haiti"], + ["1B127B", "Deep Koamaru"], + ["1B1404", "Acadia"], + ["1B2F11", "Seaweed"], + ["1B3162", "Biscay"], + ["1B659D", "Matisse"], + ["1C1208", "Crowshead"], + ["1C1E13", "Rangoon Green"], + ["1C39BB", "Persian Blue"], + ["1C402E", "Everglade"], + ["1C7C7D", "Elm"], + ["1D6142", "Green Pea"], + ["1E0F04", "Creole"], + ["1E1609", "Karaka"], + ["1E1708", "El Paso"], + ["1E385B", "Cello"], + ["1E433C", "Te Papa Green"], + ["1E90FF", "Dodger Blue"], + ["1E9AB0", "Eastern Blue"], + ["1F120F", "Night Rider"], + ["1FC2C2", "Java"], + ["20208D", "Jacksons Purple"], + ["202E54", "Cloud Burst"], + ["204852", "Blue Dianne"], + ["211A0E", "Eternity"], + ["220878", "Deep Blue"], + ["228B22", "Forest Green"], + ["233418", "Mallard"], + ["240A40", "Violet"], + ["240C02", "Kilamanjaro"], + ["242A1D", "Log Cabin"], + ["242E16", "Black Olive"], + ["24500F", "Green House"], + ["251607", "Graphite"], + ["251706", "Cannon Black"], + ["251F4F", "Port Gore"], + ["25272C", "Shark"], + ["25311C", "Green Kelp"], + ["2596D1", "Curious Blue"], + ["260368", "Paua"], + ["26056A", "Paris M"], + ["261105", "Wood Bark"], + ["261414", "Gondola"], + ["262335", "Steel Gray"], + ["26283B", "Ebony Clay"], + ["273A81", "Bay of Many"], + ["27504B", "Plantation"], + ["278A5B", "Eucalyptus"], + ["281E15", "Oil"], + ["283A77", "Astronaut"], + ["286ACD", "Mariner"], + ["290C5E", "Violent Violet"], + ["292130", "Bastille"], + ["292319", "Zeus"], + ["292937", "Charade"], + ["297B9A", "Jelly Bean"], + ["29AB87", "Jungle Green"], + ["2A0359", "Cherry Pie"], + ["2A140E", "Coffee Bean"], + ["2A2630", "Baltic Sea"], + ["2A380B", "Turtle Green"], + ["2A52BE", "Cerulean Blue"], + ["2B0202", "Sepia Black"], + ["2B194F", "Valhalla"], + ["2B3228", "Heavy Metal"], + ["2C0E8C", "Blue Gem"], + ["2C1632", "Revolver"], + ["2C2133", "Bleached Cedar"], + ["2C8C84", "Lochinvar"], + ["2D2510", "Mikado"], + ["2D383A", "Outer Space"], + ["2D569B", "St Tropaz"], + ["2E0329", "Jacaranda"], + ["2E1905", "Jacko Bean"], + ["2E3222", "Rangitoto"], + ["2E3F62", "Rhino"], + ["2E8B57", "Sea Green"], + ["2EBFD4", "Scooter"], + ["2F270E", "Onion"], + ["2F3CB3", "Governor Bay"], + ["2F519E", "Sapphire"], + ["2F5A57", "Spectra"], + ["2F6168", "Casal"], + ["300529", "Melanzane"], + ["301F1E", "Cocoa Brown"], + ["302A0F", "Woodrush"], + ["304B6A", "San Juan"], + ["30D5C8", "Turquoise"], + ["311C17", "Eclipse"], + ["314459", "Pickled Bluewood"], + ["315BA1", "Azure"], + ["31728D", "Calypso"], + ["317D82", "Paradiso"], + ["32127A", "Persian Indigo"], + ["32293A", "Blackcurrant"], + ["323232", "Mine Shaft"], + ["325D52", "Stromboli"], + ["327C14", "Bilbao"], + ["327DA0", "Astral"], + ["33036B", "Christalle"], + ["33292F", "Thunder"], + ["33CC99", "Shamrock"], + ["341515", "Tamarind"], + ["350036", "Mardi Gras"], + ["350E42", "Valentino"], + ["350E57", "Jagger"], + ["353542", "Tuna"], + ["354E8C", "Chambray"], + ["363050", "Martinique"], + ["363534", "Tuatara"], + ["363C0D", "Waiouru"], + ["36747D", "Ming"], + ["368716", "La Palma"], + ["370202", "Chocolate"], + ["371D09", "Clinker"], + ["37290E", "Brown Tumbleweed"], + ["373021", "Birch"], + ["377475", "Oracle"], + ["380474", "Blue Diamond"], + ["381A51", "Grape"], + ["383533", "Dune"], + ["384555", "Oxford Blue"], + ["384910", "Clover"], + ["394851", "Limed Spruce"], + ["396413", "Dell"], + ["3A0020", "Toledo"], + ["3A2010", "Sambuca"], + ["3A2A6A", "Jacarta"], + ["3A686C", "William"], + ["3A6A47", "Killarney"], + ["3AB09E", "Keppel"], + ["3B000B", "Temptress"], + ["3B0910", "Aubergine"], + ["3B1F1F", "Jon"], + ["3B2820", "Treehouse"], + ["3B7A57", "Amazon"], + ["3B91B4", "Boston Blue"], + ["3C0878", "Windsor"], + ["3C1206", "Rebel"], + ["3C1F76", "Meteorite"], + ["3C2005", "Dark Ebony"], + ["3C3910", "Camouflage"], + ["3C4151", "Bright Gray"], + ["3C4443", "Cape Cod"], + ["3C493A", "Lunar Green"], + ["3D0C02", "Bean "], + ["3D2B1F", "Bistre"], + ["3D7D52", "Goblin"], + ["3E0480", "Kingfisher Daisy"], + ["3E1C14", "Cedar"], + ["3E2B23", "English Walnut"], + ["3E2C1C", "Black Marlin"], + ["3E3A44", "Ship Gray"], + ["3EABBF", "Pelorous"], + ["3F2109", "Bronze"], + ["3F2500", "Cola"], + ["3F3002", "Madras"], + ["3F307F", "Minsk"], + ["3F4C3A", "Cabbage Pont"], + ["3F583B", "Tom Thumb"], + ["3F5D53", "Mineral Green"], + ["3FC1AA", "Puerto Rico"], + ["3FFF00", "Harlequin"], + ["401801", "Brown Pod"], + ["40291D", "Cork"], + ["403B38", "Masala"], + ["403D19", "Thatch Green"], + ["405169", "Fiord"], + ["40826D", "Viridian"], + ["40A860", "Chateau Green"], + ["410056", "Ripe Plum"], + ["411F10", "Paco"], + ["412010", "Deep Oak"], + ["413C37", "Merlin"], + ["414257", "Gun Powder"], + ["414C7D", "East Bay"], + ["4169E1", "Royal Blue"], + ["41AA78", "Ocean Green"], + ["420303", "Burnt Maroon"], + ["423921", "Lisbon Brown"], + ["427977", "Faded Jade"], + ["431560", "Scarlet Gum"], + ["433120", "Iroko"], + ["433E37", "Armadillo"], + ["434C59", "River Bed"], + ["436A0D", "Green Leaf"], + ["44012D", "Barossa"], + ["441D00", "Morocco Brown"], + ["444954", "Mako"], + ["454936", "Kelp"], + ["456CAC", "San Marino"], + ["45B1E8", "Picton Blue"], + ["460B41", "Loulou"], + ["462425", "Crater Brown"], + ["465945", "Gray Asparagus"], + ["4682B4", "Steel Blue"], + ["480404", "Rustic Red"], + ["480607", "Bulgarian Rose"], + ["480656", "Clairvoyant"], + ["481C1C", "Cocoa Bean"], + ["483131", "Woody Brown"], + ["483C32", "Taupe"], + ["49170C", "Van Cleef"], + ["492615", "Brown Derby"], + ["49371B", "Metallic Bronze"], + ["495400", "Verdun Green"], + ["496679", "Blue Bayoux"], + ["497183", "Bismark"], + ["4A2A04", "Bracken"], + ["4A3004", "Deep Bronze"], + ["4A3C30", "Mondo"], + ["4A4244", "Tundora"], + ["4A444B", "Gravel"], + ["4A4E5A", "Trout"], + ["4B0082", "Pigment Indigo"], + ["4B5D52", "Nandor"], + ["4C3024", "Saddle"], + ["4C4F56", "Abbey"], + ["4D0135", "Blackberry"], + ["4D0A18", "Cab Sav"], + ["4D1E01", "Indian Tan"], + ["4D282D", "Cowboy"], + ["4D282E", "Livid Brown"], + ["4D3833", "Rock"], + ["4D3D14", "Punga"], + ["4D400F", "Bronzetone"], + ["4D5328", "Woodland"], + ["4E0606", "Mahogany"], + ["4E2A5A", "Bossanova"], + ["4E3B41", "Matterhorn"], + ["4E420C", "Bronze Olive"], + ["4E4562", "Mulled Wine"], + ["4E6649", "Axolotl"], + ["4E7F9E", "Wedgewood"], + ["4EABD1", "Shakespeare"], + ["4F1C70", "Honey Flower"], + ["4F2398", "Daisy Bush"], + ["4F69C6", "Indigo"], + ["4F7942", "Fern Green"], + ["4F9D5D", "Fruit Salad"], + ["4FA83D", "Apple"], + ["504351", "Mortar"], + ["507096", "Kashmir Blue"], + ["507672", "Cutty Sark"], + ["50C878", "Emerald"], + ["514649", "Emperor"], + ["516E3D", "Chalet Green"], + ["517C66", "Como"], + ["51808F", "Smalt Blue"], + ["52001F", "Castro"], + ["520C17", "Maroon Oak"], + ["523C94", "Gigas"], + ["533455", "Voodoo"], + ["534491", "Victoria"], + ["53824B", "Hippie Green"], + ["541012", "Heath"], + ["544333", "Judge Gray"], + ["54534D", "Fuscous Gray"], + ["549019", "Vida Loca"], + ["55280C", "Cioccolato"], + ["555B10", "Saratoga"], + ["556D56", "Finlandia"], + ["5590D9", "Havelock Blue"], + ["56B4BE", "Fountain Blue"], + ["578363", "Spring Leaves"], + ["583401", "Saddle Brown"], + ["585562", "Scarpa Flow"], + ["587156", "Cactus"], + ["589AAF", "Hippie Blue"], + ["591D35", "Wine Berry"], + ["592804", "Brown Bramble"], + ["593737", "Congo Brown"], + ["594433", "Millbrook"], + ["5A6E9C", "Waikawa Gray"], + ["5A87A0", "Horizon"], + ["5B3013", "Jambalaya"], + ["5C0120", "Bordeaux"], + ["5C0536", "Mulberry Wood"], + ["5C2E01", "Carnaby Tan"], + ["5C5D75", "Comet"], + ["5D1E0F", "Redwood"], + ["5D4C51", "Don Juan"], + ["5D5C58", "Chicago"], + ["5D5E37", "Verdigris"], + ["5D7747", "Dingley"], + ["5DA19F", "Breaker Bay"], + ["5E483E", "Kabul"], + ["5E5D3B", "Hemlock"], + ["5F3D26", "Irish Coffee"], + ["5F5F6E", "Mid Gray"], + ["5F6672", "Shuttle Gray"], + ["5FA777", "Aqua Forest"], + ["5FB3AC", "Tradewind"], + ["604913", "Horses Neck"], + ["605B73", "Smoky"], + ["606E68", "Corduroy"], + ["6093D1", "Danube"], + ["612718", "Espresso"], + ["614051", "Eggplant"], + ["615D30", "Costa Del Sol"], + ["61845F", "Glade Green"], + ["622F30", "Buccaneer"], + ["623F2D", "Quincy"], + ["624E9A", "Butterfly Bush"], + ["625119", "West Coast"], + ["626649", "Finch"], + ["639A8F", "Patina"], + ["63B76C", "Fern"], + ["6456B7", "Blue Violet"], + ["646077", "Dolphin"], + ["646463", "Storm Dust"], + ["646A54", "Siam"], + ["646E75", "Nevada"], + ["6495ED", "Cornflower Blue"], + ["64CCDB", "Viking"], + ["65000B", "Rosewood"], + ["651A14", "Cherrywood"], + ["652DC1", "Purple Heart"], + ["657220", "Fern Frond"], + ["65745D", "Willow Grove"], + ["65869F", "Hoki"], + ["660045", "Pompadour"], + ["660099", "Purple"], + ["66023C", "Tyrian Purple"], + ["661010", "Dark Tan"], + ["66B58F", "Silver Tree"], + ["66FF00", "Bright Green"], + ["66FF66", "Screamin Green"], + ["67032D", "Black Rose"], + ["675FA6", "Scampi"], + ["676662", "Ironside Gray"], + ["678975", "Viridian Green"], + ["67A712", "Christi"], + ["683600", "Nutmeg Wood Finish"], + ["685558", "Zambezi"], + ["685E6E", "Salt Box"], + ["692545", "Tawny Port"], + ["692D54", "Finn"], + ["695F62", "Scorpion"], + ["697E9A", "Lynch"], + ["6A442E", "Spice"], + ["6A5D1B", "Himalaya"], + ["6A6051", "Soya Bean"], + ["6B2A14", "Hairy Heath"], + ["6B3FA0", "Royal Purple"], + ["6B4E31", "Shingle Fawn"], + ["6B5755", "Dorado"], + ["6B8BA2", "Bermuda Gray"], + ["6B8E23", "Olive Drab"], + ["6C3082", "Eminence"], + ["6CDAE7", "Turquoise Blue"], + ["6D0101", "Lonestar"], + ["6D5E54", "Pine Cone"], + ["6D6C6C", "Dove Gray"], + ["6D9292", "Juniper"], + ["6D92A1", "Gothic"], + ["6E0902", "Red Oxide"], + ["6E1D14", "Moccaccino"], + ["6E4826", "Pickled Bean"], + ["6E4B26", "Dallas"], + ["6E6D57", "Kokoda"], + ["6E7783", "Pale Sky"], + ["6F440C", "Cafe Royale"], + ["6F6A61", "Flint"], + ["6F8E63", "Highland"], + ["6F9D02", "Limeade"], + ["6FD0C5", "Downy"], + ["701C1C", "Persian Plum"], + ["704214", "Sepia"], + ["704A07", "Antique Bronze"], + ["704F50", "Ferra"], + ["706555", "Coffee"], + ["708090", "Slate Gray"], + ["711A00", "Cedar Wood Finish"], + ["71291D", "Metallic Copper"], + ["714693", "Affair"], + ["714AB2", "Studio"], + ["715D47", "Tobacco Brown"], + ["716338", "Yellow Metal"], + ["716B56", "Peat"], + ["716E10", "Olivetone"], + ["717486", "Storm Gray"], + ["718080", "Sirocco"], + ["71D9E2", "Aquamarine Blue"], + ["72010F", "Venetian Red"], + ["724A2F", "Old Copper"], + ["726D4E", "Go Ben"], + ["727B89", "Raven"], + ["731E8F", "Seance"], + ["734A12", "Raw Umber"], + ["736C9F", "Kimberly"], + ["736D58", "Crocodile"], + ["737829", "Crete"], + ["738678", "Xanadu"], + ["74640D", "Spicy Mustard"], + ["747D63", "Limed Ash"], + ["747D83", "Rolling Stone"], + ["748881", "Blue Smoke"], + ["749378", "Laurel"], + ["74C365", "Mantis"], + ["755A57", "Russett"], + ["7563A8", "Deluge"], + ["76395D", "Cosmic"], + ["7666C6", "Blue Marguerite"], + ["76BD17", "Lima"], + ["76D7EA", "Sky Blue"], + ["770F05", "Dark Burgundy"], + ["771F1F", "Crown of Thorns"], + ["773F1A", "Walnut"], + ["776F61", "Pablo"], + ["778120", "Pacifika"], + ["779E86", "Oxley"], + ["77DD77", "Pastel Green"], + ["780109", "Japanese Maple"], + ["782D19", "Mocha"], + ["782F16", "Peanut"], + ["78866B", "Camouflage Green"], + ["788A25", "Wasabi"], + ["788BBA", "Ship Cove"], + ["78A39C", "Sea Nymph"], + ["795D4C", "Roman Coffee"], + ["796878", "Old Lavender"], + ["796989", "Rum"], + ["796A78", "Fedora"], + ["796D62", "Sandstone"], + ["79DEEC", "Spray"], + ["7A013A", "Siren"], + ["7A58C1", "Fuchsia Blue"], + ["7A7A7A", "Boulder"], + ["7A89B8", "Wild Blue Yonder"], + ["7AC488", "De York"], + ["7B3801", "Red Beech"], + ["7B3F00", "Cinnamon"], + ["7B6608", "Yukon Gold"], + ["7B7874", "Tapa"], + ["7B7C94", "Waterloo "], + ["7B8265", "Flax Smoke"], + ["7B9F80", "Amulet"], + ["7BA05B", "Asparagus"], + ["7C1C05", "Kenyan Copper"], + ["7C7631", "Pesto"], + ["7C778A", "Topaz"], + ["7C7B7A", "Concord"], + ["7C7B82", "Jumbo"], + ["7C881A", "Trendy Green"], + ["7CA1A6", "Gumbo"], + ["7CB0A1", "Acapulco"], + ["7CB7BB", "Neptune"], + ["7D2C14", "Pueblo"], + ["7DA98D", "Bay Leaf"], + ["7DC8F7", "Malibu"], + ["7DD8C6", "Bermuda"], + ["7E3A15", "Copper Canyon"], + ["7F1734", "Claret"], + ["7F3A02", "Peru Tan"], + ["7F626D", "Falcon"], + ["7F7589", "Mobster"], + ["7F76D3", "Moody Blue"], + ["7FFF00", "Chartreuse"], + ["7FFFD4", "Aquamarine"], + ["800000", "Maroon"], + ["800B47", "Rose Bud Cherry"], + ["801818", "Falu Red"], + ["80341F", "Red Robin"], + ["803790", "Vivid Violet"], + ["80461B", "Russet"], + ["807E79", "Friar Gray"], + ["808000", "Olive"], + ["808080", "Gray"], + ["80B3AE", "Gulf Stream"], + ["80B3C4", "Glacier"], + ["80CCEA", "Seagull"], + ["81422C", "Nutmeg"], + ["816E71", "Spicy Pink"], + ["817377", "Empress"], + ["819885", "Spanish Green"], + ["826F65", "Sand Dune"], + ["828685", "Gunsmoke"], + ["828F72", "Battleship Gray"], + ["831923", "Merlot"], + ["837050", "Shadow"], + ["83AA5D", "Chelsea Cucumber"], + ["83D0C6", "Monte Carlo"], + ["843179", "Plum"], + ["84A0A0", "Granny Smith"], + ["8581D9", "Chetwode Blue"], + ["858470", "Bandicoot"], + ["859FAF", "Bali Hai"], + ["85C4CC", "Half Baked"], + ["860111", "Red Devil"], + ["863C3C", "Lotus"], + ["86483C", "Ironstone"], + ["864D1E", "Bull Shot"], + ["86560A", "Rusty Nail"], + ["868974", "Bitter"], + ["86949F", "Regent Gray"], + ["871550", "Disco"], + ["87756E", "Americano"], + ["877C7B", "Hurricane"], + ["878D91", "Oslo Gray"], + ["87AB39", "Sushi"], + ["885342", "Spicy Mix"], + ["886221", "Kumera"], + ["888387", "Suva Gray"], + ["888D65", "Avocado"], + ["893456", "Camelot"], + ["893843", "Solid Pink"], + ["894367", "Cannon Pink"], + ["897D6D", "Makara"], + ["8A3324", "Burnt Umber"], + ["8A73D6", "True V"], + ["8A8360", "Clay Creek"], + ["8A8389", "Monsoon"], + ["8A8F8A", "Stack"], + ["8AB9F1", "Jordy Blue"], + ["8B00FF", "Electric Violet"], + ["8B0723", "Monarch"], + ["8B6B0B", "Corn Harvest"], + ["8B8470", "Olive Haze"], + ["8B847E", "Schooner"], + ["8B8680", "Natural Gray"], + ["8B9C90", "Mantle"], + ["8B9FEE", "Portage"], + ["8BA690", "Envy"], + ["8BA9A5", "Cascade"], + ["8BE6D8", "Riptide"], + ["8C055E", "Cardinal Pink"], + ["8C472F", "Mule Fawn"], + ["8C5738", "Potters Clay"], + ["8C6495", "Trendy Pink"], + ["8D0226", "Paprika"], + ["8D3D38", "Sanguine Brown"], + ["8D3F3F", "Tosca"], + ["8D7662", "Cement"], + ["8D8974", "Granite Green"], + ["8D90A1", "Manatee"], + ["8DA8CC", "Polo Blue"], + ["8E0000", "Red Berry"], + ["8E4D1E", "Rope"], + ["8E6F70", "Opium"], + ["8E775E", "Domino"], + ["8E8190", "Mamba"], + ["8EABC1", "Nepal"], + ["8F021C", "Pohutukawa"], + ["8F3E33", "El Salva"], + ["8F4B0E", "Korma"], + ["8F8176", "Squirrel"], + ["8FD6B4", "Vista Blue"], + ["900020", "Burgundy"], + ["901E1E", "Old Brick"], + ["907874", "Hemp"], + ["907B71", "Almond Frost"], + ["908D39", "Sycamore"], + ["92000A", "Sangria"], + ["924321", "Cumin"], + ["926F5B", "Beaver"], + ["928573", "Stonewall"], + ["928590", "Venus"], + ["9370DB", "Medium Purple"], + ["93CCEA", "Cornflower"], + ["93DFB8", "Algae Green"], + ["944747", "Copper Rust"], + ["948771", "Arrowtown"], + ["950015", "Scarlett"], + ["956387", "Strikemaster"], + ["959396", "Mountain Mist"], + ["960018", "Carmine"], + ["964B00", "Brown"], + ["967059", "Leather"], + ["9678B6", "Purple Mountain's Majesty"], + ["967BB6", "Lavender Purple"], + ["96A8A1", "Pewter"], + ["96BBAB", "Summer Green"], + ["97605D", "Au Chico"], + ["9771B5", "Wisteria"], + ["97CD2D", "Atlantis"], + ["983D61", "Vin Rouge"], + ["9874D3", "Lilac Bush"], + ["98777B", "Bazaar"], + ["98811B", "Hacienda"], + ["988D77", "Pale Oyster"], + ["98FF98", "Mint Green"], + ["990066", "Fresh Eggplant"], + ["991199", "Violet Eggplant"], + ["991613", "Tamarillo"], + ["991B07", "Totem Pole"], + ["996666", "Copper Rose"], + ["9966CC", "Amethyst"], + ["997A8D", "Mountbatten Pink"], + ["9999CC", "Blue Bell"], + ["9A3820", "Prairie Sand"], + ["9A6E61", "Toast"], + ["9A9577", "Gurkha"], + ["9AB973", "Olivine"], + ["9AC2B8", "Shadow Green"], + ["9B4703", "Oregon"], + ["9B9E8F", "Lemon Grass"], + ["9C3336", "Stiletto"], + ["9D5616", "Hawaiian Tan"], + ["9DACB7", "Gull Gray"], + ["9DC209", "Pistachio"], + ["9DE093", "Granny Smith Apple"], + ["9DE5FF", "Anakiwa"], + ["9E5302", "Chelsea Gem"], + ["9E5B40", "Sepia Skin"], + ["9EA587", "Sage"], + ["9EA91F", "Citron"], + ["9EB1CD", "Rock Blue"], + ["9EDEE0", "Morning Glory"], + ["9F381D", "Cognac"], + ["9F821C", "Reef Gold"], + ["9F9F9C", "Star Dust"], + ["9FA0B1", "Santas Gray"], + ["9FD7D3", "Sinbad"], + ["9FDD8C", "Feijoa"], + ["A02712", "Tabasco"], + ["A1750D", "Buttered Rum"], + ["A1ADB5", "Hit Gray"], + ["A1C50A", "Citrus"], + ["A1DAD7", "Aqua Island"], + ["A1E9DE", "Water Leaf"], + ["A2006D", "Flirt"], + ["A23B6C", "Rouge"], + ["A26645", "Cape Palliser"], + ["A2AAB3", "Gray Chateau"], + ["A2AEAB", "Edward"], + ["A3807B", "Pharlap"], + ["A397B4", "Amethyst Smoke"], + ["A3E3ED", "Blizzard Blue"], + ["A4A49D", "Delta"], + ["A4A6D3", "Wistful"], + ["A4AF6E", "Green Smoke"], + ["A50B5E", "Jazzberry Jam"], + ["A59B91", "Zorba"], + ["A5CB0C", "Bahia"], + ["A62F20", "Roof Terracotta"], + ["A65529", "Paarl"], + ["A68B5B", "Barley Corn"], + ["A69279", "Donkey Brown"], + ["A6A29A", "Dawn"], + ["A72525", "Mexican Red"], + ["A7882C", "Luxor Gold"], + ["A85307", "Rich Gold"], + ["A86515", "Reno Sand"], + ["A86B6B", "Coral Tree"], + ["A8989B", "Dusty Gray"], + ["A899E6", "Dull Lavender"], + ["A8A589", "Tallow"], + ["A8AE9C", "Bud"], + ["A8AF8E", "Locust"], + ["A8BD9F", "Norway"], + ["A8E3BD", "Chinook"], + ["A9A491", "Gray Olive"], + ["A9ACB6", "Aluminium"], + ["A9B2C3", "Cadet Blue"], + ["A9B497", "Schist"], + ["A9BDBF", "Tower Gray"], + ["A9BEF2", "Perano"], + ["A9C6C2", "Opal"], + ["AA375A", "Night Shadz"], + ["AA4203", "Fire"], + ["AA8B5B", "Muesli"], + ["AA8D6F", "Sandal"], + ["AAA5A9", "Shady Lady"], + ["AAA9CD", "Logan"], + ["AAABB7", "Spun Pearl"], + ["AAD6E6", "Regent St Blue"], + ["AAF0D1", "Magic Mint"], + ["AB0563", "Lipstick"], + ["AB3472", "Royal Heath"], + ["AB917A", "Sandrift"], + ["ABA0D9", "Cold Purple"], + ["ABA196", "Bronco"], + ["AC8A56", "Limed Oak"], + ["AC91CE", "East Side"], + ["AC9E22", "Lemon Ginger"], + ["ACA494", "Napa"], + ["ACA586", "Hillary"], + ["ACA59F", "Cloudy"], + ["ACACAC", "Silver Chalice"], + ["ACB78E", "Swamp Green"], + ["ACCBB1", "Spring Rain"], + ["ACDD4D", "Conifer"], + ["ACE1AF", "Celadon"], + ["AD781B", "Mandalay"], + ["ADBED1", "Casper"], + ["ADDFAD", "Moss Green"], + ["ADE6C4", "Padua"], + ["ADFF2F", "Green Yellow"], + ["AE4560", "Hippie Pink"], + ["AE6020", "Desert"], + ["AE809E", "Bouquet"], + ["AF4035", "Medium Carmine"], + ["AF4D43", "Apple Blossom"], + ["AF593E", "Brown Rust"], + ["AF8751", "Driftwood"], + ["AF8F2C", "Alpine"], + ["AF9F1C", "Lucky"], + ["AFA09E", "Martini"], + ["AFB1B8", "Bombay"], + ["AFBDD9", "Pigeon Post"], + ["B04C6A", "Cadillac"], + ["B05D54", "Matrix"], + ["B05E81", "Tapestry"], + ["B06608", "Mai Tai"], + ["B09A95", "Del Rio"], + ["B0E0E6", "Powder Blue"], + ["B0E313", "Inch Worm"], + ["B10000", "Bright Red"], + ["B14A0B", "Vesuvius"], + ["B1610B", "Pumpkin Skin"], + ["B16D52", "Santa Fe"], + ["B19461", "Teak"], + ["B1E2C1", "Fringy Flower"], + ["B1F4E7", "Ice Cold"], + ["B20931", "Shiraz"], + ["B2A1EA", "Biloba Flower"], + ["B32D29", "Tall Poppy"], + ["B35213", "Fiery Orange"], + ["B38007", "Hot Toddy"], + ["B3AF95", "Taupe Gray"], + ["B3C110", "La Rioja"], + ["B43332", "Well Read"], + ["B44668", "Blush"], + ["B4CFD3", "Jungle Mist"], + ["B57281", "Turkish Rose"], + ["B57EDC", "Lavender"], + ["B5A27F", "Mongoose"], + ["B5B35C", "Olive Green"], + ["B5D2CE", "Jet Stream"], + ["B5ECDF", "Cruise"], + ["B6316C", "Hibiscus"], + ["B69D98", "Thatch"], + ["B6B095", "Heathered Gray"], + ["B6BAA4", "Eagle"], + ["B6D1EA", "Spindle"], + ["B6D3BF", "Gum Leaf"], + ["B7410E", "Rust"], + ["B78E5C", "Muddy Waters"], + ["B7A214", "Sahara"], + ["B7A458", "Husk"], + ["B7B1B1", "Nobel"], + ["B7C3D0", "Heather"], + ["B7F0BE", "Madang"], + ["B81104", "Milano Red"], + ["B87333", "Copper"], + ["B8B56A", "Gimblet"], + ["B8C1B1", "Green Spring"], + ["B8C25D", "Celery"], + ["B8E0F9", "Sail"], + ["B94E48", "Chestnut"], + ["B95140", "Crail"], + ["B98D28", "Marigold"], + ["B9C46A", "Wild Willow"], + ["B9C8AC", "Rainee"], + ["BA0101", "Guardsman Red"], + ["BA450C", "Rock Spray"], + ["BA6F1E", "Bourbon"], + ["BA7F03", "Pirate Gold"], + ["BAB1A2", "Nomad"], + ["BAC7C9", "Submarine"], + ["BAEEF9", "Charlotte"], + ["BB3385", "Medium Red Violet"], + ["BB8983", "Brandy Rose"], + ["BBD009", "Rio Grande"], + ["BBD7C1", "Surf"], + ["BCC9C2", "Powder Ash"], + ["BD5E2E", "Tuscany"], + ["BD978E", "Quicksand"], + ["BDB1A8", "Silk"], + ["BDB2A1", "Malta"], + ["BDB3C7", "Chatelle"], + ["BDBBD7", "Lavender Gray"], + ["BDBDC6", "French Gray"], + ["BDC8B3", "Clay Ash"], + ["BDC9CE", "Loblolly"], + ["BDEDFD", "French Pass"], + ["BEA6C3", "London Hue"], + ["BEB5B7", "Pink Swan"], + ["BEDE0D", "Fuego"], + ["BF5500", "Rose of Sharon"], + ["BFB8B0", "Tide"], + ["BFBED8", "Blue Haze"], + ["BFC1C2", "Silver Sand"], + ["BFC921", "Key Lime Pie"], + ["BFDBE2", "Ziggurat"], + ["BFFF00", "Lime"], + ["C02B18", "Thunderbird"], + ["C04737", "Mojo"], + ["C08081", "Old Rose"], + ["C0C0C0", "Silver"], + ["C0D3B9", "Pale Leaf"], + ["C0D8B6", "Pixie Green"], + ["C1440E", "Tia Maria"], + ["C154C1", "Fuchsia Pink"], + ["C1A004", "Buddha Gold"], + ["C1B7A4", "Bison Hide"], + ["C1BAB0", "Tea"], + ["C1BECD", "Gray Suit"], + ["C1D7B0", "Sprout"], + ["C1F07C", "Sulu"], + ["C26B03", "Indochine"], + ["C2955D", "Twine"], + ["C2BDB6", "Cotton Seed"], + ["C2CAC4", "Pumice"], + ["C2E8E5", "Jagged Ice"], + ["C32148", "Maroon Flush"], + ["C3B091", "Indian Khaki"], + ["C3BFC1", "Pale Slate"], + ["C3C3BD", "Gray Nickel"], + ["C3CDE6", "Periwinkle Gray"], + ["C3D1D1", "Tiara"], + ["C3DDF9", "Tropical Blue"], + ["C41E3A", "Cardinal"], + ["C45655", "Fuzzy Wuzzy Brown"], + ["C45719", "Orange Roughy"], + ["C4C4BC", "Mist Gray"], + ["C4D0B0", "Coriander"], + ["C4F4EB", "Mint Tulip"], + ["C54B8C", "Mulberry"], + ["C59922", "Nugget"], + ["C5994B", "Tussock"], + ["C5DBCA", "Sea Mist"], + ["C5E17A", "Yellow Green"], + ["C62D42", "Brick Red"], + ["C6726B", "Contessa"], + ["C69191", "Oriental Pink"], + ["C6A84B", "Roti"], + ["C6C3B5", "Ash"], + ["C6C8BD", "Kangaroo"], + ["C6E610", "Las Palmas"], + ["C7031E", "Monza"], + ["C71585", "Red Violet"], + ["C7BCA2", "Coral Reef"], + ["C7C1FF", "Melrose"], + ["C7C4BF", "Cloud"], + ["C7C9D5", "Ghost"], + ["C7CD90", "Pine Glade"], + ["C7DDE5", "Botticelli"], + ["C88A65", "Antique Brass"], + ["C8A2C8", "Lilac"], + ["C8A528", "Hokey Pokey"], + ["C8AABF", "Lily"], + ["C8B568", "Laser"], + ["C8E3D7", "Edgewater"], + ["C96323", "Piper"], + ["C99415", "Pizza"], + ["C9A0DC", "Light Wisteria"], + ["C9B29B", "Rodeo Dust"], + ["C9B35B", "Sundance"], + ["C9B93B", "Earls Green"], + ["C9C0BB", "Silver Rust"], + ["C9D9D2", "Conch"], + ["C9FFA2", "Reef"], + ["C9FFE5", "Aero Blue"], + ["CA3435", "Flush Mahogany"], + ["CABB48", "Turmeric"], + ["CADCD4", "Paris White"], + ["CAE00D", "Bitter Lemon"], + ["CAE6DA", "Skeptic"], + ["CB8FA9", "Viola"], + ["CBCAB6", "Foggy Gray"], + ["CBD3B0", "Green Mist"], + ["CBDBD6", "Nebula"], + ["CC3333", "Persian Red"], + ["CC5500", "Burnt Orange"], + ["CC7722", "Ochre"], + ["CC8899", "Puce"], + ["CCCAA8", "Thistle Green"], + ["CCCCFF", "Periwinkle"], + ["CCFF00", "Electric Lime"], + ["CD5700", "Tenn"], + ["CD5C5C", "Chestnut Rose"], + ["CD8429", "Brandy Punch"], + ["CDF4FF", "Onahau"], + ["CEB98F", "Sorrell Brown"], + ["CEBABA", "Cold Turkey"], + ["CEC291", "Yuma"], + ["CEC7A7", "Chino"], + ["CFA39D", "Eunry"], + ["CFB53B", "Old Gold"], + ["CFDCCF", "Tasman"], + ["CFE5D2", "Surf Crest"], + ["CFF9F3", "Humming Bird"], + ["CFFAF4", "Scandal"], + ["D05F04", "Red Stage"], + ["D06DA1", "Hopbush"], + ["D07D12", "Meteor"], + ["D0BEF8", "Perfume"], + ["D0C0E5", "Prelude"], + ["D0F0C0", "Tea Green"], + ["D18F1B", "Geebung"], + ["D1BEA8", "Vanilla"], + ["D1C6B4", "Soft Amber"], + ["D1D2CA", "Celeste"], + ["D1D2DD", "Mischka"], + ["D1E231", "Pear"], + ["D2691E", "Hot Cinnamon"], + ["D27D46", "Raw Sienna"], + ["D29EAA", "Careys Pink"], + ["D2B48C", "Tan"], + ["D2DA97", "Deco"], + ["D2F6DE", "Blue Romance"], + ["D2F8B0", "Gossip"], + ["D3CBBA", "Sisal"], + ["D3CDC5", "Swirl"], + ["D47494", "Charm"], + ["D4B6AF", "Clam Shell"], + ["D4BF8D", "Straw"], + ["D4C4A8", "Akaroa"], + ["D4CD16", "Bird Flower"], + ["D4D7D9", "Iron"], + ["D4DFE2", "Geyser"], + ["D4E2FC", "Hawkes Blue"], + ["D54600", "Grenadier"], + ["D591A4", "Can Can"], + ["D59A6F", "Whiskey"], + ["D5D195", "Winter Hazel"], + ["D5F6E3", "Granny Apple"], + ["D69188", "My Pink"], + ["D6C562", "Tacha"], + ["D6CEF6", "Moon Raker"], + ["D6D6D1", "Quill Gray"], + ["D6FFDB", "Snowy Mint"], + ["D7837F", "New York Pink"], + ["D7C498", "Pavlova"], + ["D7D0FF", "Fog"], + ["D84437", "Valencia"], + ["D87C63", "Japonica"], + ["D8BFD8", "Thistle"], + ["D8C2D5", "Maverick"], + ["D8FCFA", "Foam"], + ["D94972", "Cabaret"], + ["D99376", "Burning Sand"], + ["D9B99B", "Cameo"], + ["D9D6CF", "Timberwolf"], + ["D9DCC1", "Tana"], + ["D9E4F5", "Link Water"], + ["D9F7FF", "Mabel"], + ["DA3287", "Cerise"], + ["DA5B38", "Flame Pea"], + ["DA6304", "Bamboo"], + ["DA6A41", "Red Damask"], + ["DA70D6", "Orchid"], + ["DA8A67", "Copperfield"], + ["DAA520", "Golden Grass"], + ["DAECD6", "Zanah"], + ["DAF4F0", "Iceberg"], + ["DAFAFF", "Oyster Bay"], + ["DB5079", "Cranberry"], + ["DB9690", "Petite Orchid"], + ["DB995E", "Di Serria"], + ["DBDBDB", "Alto"], + ["DBFFF8", "Frosted Mint"], + ["DC143C", "Crimson"], + ["DC4333", "Punch"], + ["DCB20C", "Galliano"], + ["DCB4BC", "Blossom"], + ["DCD747", "Wattle"], + ["DCD9D2", "Westar"], + ["DCDDCC", "Moon Mist"], + ["DCEDB4", "Caper"], + ["DCF0EA", "Swans Down"], + ["DDD6D5", "Swiss Coffee"], + ["DDF9F1", "White Ice"], + ["DE3163", "Cerise Red"], + ["DE6360", "Roman"], + ["DEA681", "Tumbleweed"], + ["DEBA13", "Gold Tips"], + ["DEC196", "Brandy"], + ["DECBC6", "Wafer"], + ["DED4A4", "Sapling"], + ["DED717", "Barberry"], + ["DEE5C0", "Beryl Green"], + ["DEF5FF", "Pattens Blue"], + ["DF73FF", "Heliotrope"], + ["DFBE6F", "Apache"], + ["DFCD6F", "Chenin"], + ["DFCFDB", "Lola"], + ["DFECDA", "Willow Brook"], + ["DFFF00", "Chartreuse Yellow"], + ["E0B0FF", "Mauve"], + ["E0B646", "Anzac"], + ["E0B974", "Harvest Gold"], + ["E0C095", "Calico"], + ["E0FFFF", "Baby Blue"], + ["E16865", "Sunglo"], + ["E1BC64", "Equator"], + ["E1C0C8", "Pink Flare"], + ["E1E6D6", "Periglacial Blue"], + ["E1EAD4", "Kidnapper"], + ["E1F6E8", "Tara"], + ["E25465", "Mandy"], + ["E2725B", "Terracotta"], + ["E28913", "Golden Bell"], + ["E292C0", "Shocking"], + ["E29418", "Dixie"], + ["E29CD2", "Light Orchid"], + ["E2D8ED", "Snuff"], + ["E2EBED", "Mystic"], + ["E2F3EC", "Apple Green"], + ["E30B5C", "Razzmatazz"], + ["E32636", "Alizarin Crimson"], + ["E34234", "Cinnabar"], + ["E3BEBE", "Cavern Pink"], + ["E3F5E1", "Peppermint"], + ["E3F988", "Mindaro"], + ["E47698", "Deep Blush"], + ["E49B0F", "Gamboge"], + ["E4C2D5", "Melanie"], + ["E4CFDE", "Twilight"], + ["E4D1C0", "Bone"], + ["E4D422", "Sunflower"], + ["E4D5B7", "Grain Brown"], + ["E4D69B", "Zombie"], + ["E4F6E7", "Frostee"], + ["E4FFD1", "Snow Flurry"], + ["E52B50", "Amaranth"], + ["E5841B", "Zest"], + ["E5CCC9", "Dust Storm"], + ["E5D7BD", "Stark White"], + ["E5D8AF", "Hampton"], + ["E5E0E1", "Bon Jour"], + ["E5E5E5", "Mercury"], + ["E5F9F6", "Polar"], + ["E64E03", "Trinidad"], + ["E6BE8A", "Gold Sand"], + ["E6BEA5", "Cashmere"], + ["E6D7B9", "Double Spanish White"], + ["E6E4D4", "Satin Linen"], + ["E6F2EA", "Harp"], + ["E6F8F3", "Off Green"], + ["E6FFE9", "Hint of Green"], + ["E6FFFF", "Tranquil"], + ["E77200", "Mango Tango"], + ["E7730A", "Christine"], + ["E79F8C", "Tonys Pink"], + ["E79FC4", "Kobi"], + ["E7BCB4", "Rose Fog"], + ["E7BF05", "Corn"], + ["E7CD8C", "Putty"], + ["E7ECE6", "Gray Nurse"], + ["E7F8FF", "Lily White"], + ["E7FEFF", "Bubbles"], + ["E89928", "Fire Bush"], + ["E8B9B3", "Shilo"], + ["E8E0D5", "Pearl Bush"], + ["E8EBE0", "Green White"], + ["E8F1D4", "Chrome White"], + ["E8F2EB", "Gin"], + ["E8F5F2", "Aqua Squeeze"], + ["E96E00", "Clementine"], + ["E97451", "Burnt Sienna"], + ["E97C07", "Tahiti Gold"], + ["E9CECD", "Oyster Pink"], + ["E9D75A", "Confetti"], + ["E9E3E3", "Ebb"], + ["E9F8ED", "Ottoman"], + ["E9FFFD", "Clear Day"], + ["EA88A8", "Carissma"], + ["EAAE69", "Porsche"], + ["EAB33B", "Tulip Tree"], + ["EAC674", "Rob Roy"], + ["EADAB8", "Raffia"], + ["EAE8D4", "White Rock"], + ["EAF6EE", "Panache"], + ["EAF6FF", "Solitude"], + ["EAF9F5", "Aqua Spring"], + ["EAFFFE", "Dew"], + ["EB9373", "Apricot"], + ["EBC2AF", "Zinnwaldite"], + ["ECA927", "Fuel Yellow"], + ["ECC54E", "Ronchi"], + ["ECC7EE", "French Lilac"], + ["ECCDB9", "Just Right"], + ["ECE090", "Wild Rice"], + ["ECEBBD", "Fall Green"], + ["ECEBCE", "Aths Special"], + ["ECF245", "Starship"], + ["ED0A3F", "Red Ribbon"], + ["ED7A1C", "Tango"], + ["ED9121", "Carrot Orange"], + ["ED989E", "Sea Pink"], + ["EDB381", "Tacao"], + ["EDC9AF", "Desert Sand"], + ["EDCDAB", "Pancho"], + ["EDDCB1", "Chamois"], + ["EDEA99", "Primrose"], + ["EDF5DD", "Frost"], + ["EDF5F5", "Aqua Haze"], + ["EDF6FF", "Zumthor"], + ["EDF9F1", "Narvik"], + ["EDFC84", "Honeysuckle"], + ["EE82EE", "Lavender Magenta"], + ["EEC1BE", "Beauty Bush"], + ["EED794", "Chalky"], + ["EED9C4", "Almond"], + ["EEDC82", "Flax"], + ["EEDEDA", "Bizarre"], + ["EEE3AD", "Double Colonial White"], + ["EEEEE8", "Cararra"], + ["EEEF78", "Manz"], + ["EEF0C8", "Tahuna Sands"], + ["EEF0F3", "Athens Gray"], + ["EEF3C3", "Tusk"], + ["EEF4DE", "Loafer"], + ["EEF6F7", "Catskill White"], + ["EEFDFF", "Twilight Blue"], + ["EEFF9A", "Jonquil"], + ["EEFFE2", "Rice Flower"], + ["EF863F", "Jaffa"], + ["EFEFEF", "Gallery"], + ["EFF2F3", "Porcelain"], + ["F091A9", "Mauvelous"], + ["F0D52D", "Golden Dream"], + ["F0DB7D", "Golden Sand"], + ["F0DC82", "Buff"], + ["F0E2EC", "Prim"], + ["F0E68C", "Khaki"], + ["F0EEFD", "Selago"], + ["F0EEFF", "Titan White"], + ["F0F8FF", "Alice Blue"], + ["F0FCEA", "Feta"], + ["F18200", "Gold Drop"], + ["F19BAB", "Wewak"], + ["F1E788", "Sahara Sand"], + ["F1E9D2", "Parchment"], + ["F1E9FF", "Blue Chalk"], + ["F1EEC1", "Mint Julep"], + ["F1F1F1", "Seashell"], + ["F1F7F2", "Saltpan"], + ["F1FFAD", "Tidal"], + ["F1FFC8", "Chiffon"], + ["F2552A", "Flamingo"], + ["F28500", "Tangerine"], + ["F2C3B2", "Mandys Pink"], + ["F2F2F2", "Concrete"], + ["F2FAFA", "Black Squeeze"], + ["F34723", "Pomegranate"], + ["F3AD16", "Buttercup"], + ["F3D69D", "New Orleans"], + ["F3D9DF", "Vanilla Ice"], + ["F3E7BB", "Sidecar"], + ["F3E9E5", "Dawn Pink"], + ["F3EDCF", "Wheatfield"], + ["F3FB62", "Canary"], + ["F3FBD4", "Orinoco"], + ["F3FFD8", "Carla"], + ["F400A1", "Hollywood Cerise"], + ["F4A460", "Sandy brown"], + ["F4C430", "Saffron"], + ["F4D81C", "Ripe Lemon"], + ["F4EBD3", "Janna"], + ["F4F2EE", "Pampas"], + ["F4F4F4", "Wild Sand"], + ["F4F8FF", "Zircon"], + ["F57584", "Froly"], + ["F5C85C", "Cream Can"], + ["F5C999", "Manhattan"], + ["F5D5A0", "Maize"], + ["F5DEB3", "Wheat"], + ["F5E7A2", "Sandwisp"], + ["F5E7E2", "Pot Pourri"], + ["F5E9D3", "Albescent White"], + ["F5EDEF", "Soft Peach"], + ["F5F3E5", "Ecru White"], + ["F5F5DC", "Beige"], + ["F5FB3D", "Golden Fizz"], + ["F5FFBE", "Australian Mint"], + ["F64A8A", "French Rose"], + ["F653A6", "Brilliant Rose"], + ["F6A4C9", "Illusion"], + ["F6F0E6", "Merino"], + ["F6F7F7", "Black Haze"], + ["F6FFDC", "Spring Sun"], + ["F7468A", "Violet Red"], + ["F77703", "Chilean Fire"], + ["F77FBE", "Persian Pink"], + ["F7B668", "Rajah"], + ["F7C8DA", "Azalea"], + ["F7DBE6", "We Peep"], + ["F7F2E1", "Quarter Spanish White"], + ["F7F5FA", "Whisper"], + ["F7FAF7", "Snow Drift"], + ["F8B853", "Casablanca"], + ["F8C3DF", "Chantilly"], + ["F8D9E9", "Cherub"], + ["F8DB9D", "Marzipan"], + ["F8DD5C", "Energy Yellow"], + ["F8E4BF", "Givry"], + ["F8F0E8", "White Linen"], + ["F8F4FF", "Magnolia"], + ["F8F6F1", "Spring Wood"], + ["F8F7DC", "Coconut Cream"], + ["F8F7FC", "White Lilac"], + ["F8F8F7", "Desert Storm"], + ["F8F99C", "Texas"], + ["F8FACD", "Corn Field"], + ["F8FDD3", "Mimosa"], + ["F95A61", "Carnation"], + ["F9BF58", "Saffron Mango"], + ["F9E0ED", "Carousel Pink"], + ["F9E4BC", "Dairy Cream"], + ["F9E663", "Portica"], + ["F9EAF3", "Amour"], + ["F9F8E4", "Rum Swizzle"], + ["F9FF8B", "Dolly"], + ["F9FFF6", "Sugar Cane"], + ["FA7814", "Ecstasy"], + ["FA9D5A", "Tan Hide"], + ["FAD3A2", "Corvette"], + ["FADFAD", "Peach Yellow"], + ["FAE600", "Turbo"], + ["FAEAB9", "Astra"], + ["FAECCC", "Champagne"], + ["FAF0E6", "Linen"], + ["FAF3F0", "Fantasy"], + ["FAF7D6", "Citrine White"], + ["FAFAFA", "Alabaster"], + ["FAFDE4", "Hint of Yellow"], + ["FAFFA4", "Milan"], + ["FB607F", "Brink Pink"], + ["FB8989", "Geraldine"], + ["FBA0E3", "Lavender Rose"], + ["FBA129", "Sea Buckthorn"], + ["FBAC13", "Sun"], + ["FBAED2", "Lavender Pink"], + ["FBB2A3", "Rose Bud"], + ["FBBEDA", "Cupid"], + ["FBCCE7", "Classic Rose"], + ["FBCEB1", "Apricot Peach"], + ["FBE7B2", "Banana Mania"], + ["FBE870", "Marigold Yellow"], + ["FBE96C", "Festival"], + ["FBEA8C", "Sweet Corn"], + ["FBEC5D", "Candy Corn"], + ["FBF9F9", "Hint of Red"], + ["FBFFBA", "Shalimar"], + ["FC0FC0", "Shocking Pink"], + ["FC80A5", "Tickle Me Pink"], + ["FC9C1D", "Tree Poppy"], + ["FCC01E", "Lightning Yellow"], + ["FCD667", "Goldenrod"], + ["FCD917", "Candlelight"], + ["FCDA98", "Cherokee"], + ["FCF4D0", "Double Pearl Lusta"], + ["FCF4DC", "Pearl Lusta"], + ["FCF8F7", "Vista White"], + ["FCFBF3", "Bianca"], + ["FCFEDA", "Moon Glow"], + ["FCFFE7", "China Ivory"], + ["FCFFF9", "Ceramic"], + ["FD0E35", "Torch Red"], + ["FD5B78", "Wild Watermelon"], + ["FD7B33", "Crusta"], + ["FD7C07", "Sorbus"], + ["FD9FA2", "Sweet Pink"], + ["FDD5B1", "Light Apricot"], + ["FDD7E4", "Pig Pink"], + ["FDE1DC", "Cinderella"], + ["FDE295", "Golden Glow"], + ["FDE910", "Lemon"], + ["FDF5E6", "Old Lace"], + ["FDF6D3", "Half Colonial White"], + ["FDF7AD", "Drover"], + ["FDFEB8", "Pale Prim"], + ["FDFFD5", "Cumulus"], + ["FE28A2", "Persian Rose"], + ["FE4C40", "Sunset Orange"], + ["FE6F5E", "Bittersweet"], + ["FE9D04", "California"], + ["FEA904", "Yellow Sea"], + ["FEBAAD", "Melon"], + ["FED33C", "Bright Sun"], + ["FED85D", "Dandelion"], + ["FEDB8D", "Salomie"], + ["FEE5AC", "Cape Honey"], + ["FEEBF3", "Remy"], + ["FEEFCE", "Oasis"], + ["FEF0EC", "Bridesmaid"], + ["FEF2C7", "Beeswax"], + ["FEF3D8", "Bleach White"], + ["FEF4CC", "Pipi"], + ["FEF4DB", "Half Spanish White"], + ["FEF4F8", "Wisp Pink"], + ["FEF5F1", "Provincial Pink"], + ["FEF7DE", "Half Dutch White"], + ["FEF8E2", "Solitaire"], + ["FEF8FF", "White Pointer"], + ["FEF9E3", "Off Yellow"], + ["FEFCED", "Orange White"], + ["FF0000", "Red"], + ["FF007F", "Rose"], + ["FF00CC", "Purple Pizzazz"], + ["FF00FF", "Magenta / Fuchsia"], + ["FF2400", "Scarlet"], + ["FF3399", "Wild Strawberry"], + ["FF33CC", "Razzle Dazzle Rose"], + ["FF355E", "Radical Red"], + ["FF3F34", "Red Orange"], + ["FF4040", "Coral Red"], + ["FF4D00", "Vermilion"], + ["FF4F00", "International Orange"], + ["FF6037", "Outrageous Orange"], + ["FF6600", "Blaze Orange"], + ["FF66FF", "Pink Flamingo"], + ["FF681F", "Orange"], + ["FF69B4", "Hot Pink"], + ["FF6B53", "Persimmon"], + ["FF6FFF", "Blush Pink"], + ["FF7034", "Burning Orange"], + ["FF7518", "Pumpkin"], + ["FF7D07", "Flamenco"], + ["FF7F00", "Flush Orange"], + ["FF7F50", "Coral"], + ["FF8C69", "Salmon"], + ["FF9000", "Pizazz"], + ["FF910F", "West Side"], + ["FF91A4", "Pink Salmon"], + ["FF9933", "Neon Carrot"], + ["FF9966", "Atomic Tangerine"], + ["FF9980", "Vivid Tangerine"], + ["FF9E2C", "Sunshade"], + ["FFA000", "Orange Peel"], + ["FFA194", "Mona Lisa"], + ["FFA500", "Web Orange"], + ["FFA6C9", "Carnation Pink"], + ["FFAB81", "Hit Pink"], + ["FFAE42", "Yellow Orange"], + ["FFB0AC", "Cornflower Lilac"], + ["FFB1B3", "Sundown"], + ["FFB31F", "My Sin"], + ["FFB555", "Texas Rose"], + ["FFB7D5", "Cotton Candy"], + ["FFB97B", "Macaroni and Cheese"], + ["FFBA00", "Selective Yellow"], + ["FFBD5F", "Koromiko"], + ["FFBF00", "Amber"], + ["FFC0A8", "Wax Flower"], + ["FFC0CB", "Pink"], + ["FFC3C0", "Your Pink"], + ["FFC901", "Supernova"], + ["FFCBA4", "Flesh"], + ["FFCC33", "Sunglow"], + ["FFCC5C", "Golden Tainoi"], + ["FFCC99", "Peach Orange"], + ["FFCD8C", "Chardonnay"], + ["FFD1DC", "Pastel Pink"], + ["FFD2B7", "Romantic"], + ["FFD38C", "Grandis"], + ["FFD700", "Gold"], + ["FFD800", "School bus Yellow"], + ["FFD8D9", "Cosmos"], + ["FFDB58", "Mustard"], + ["FFDCD6", "Peach Schnapps"], + ["FFDDAF", "Caramel"], + ["FFDDCD", "Tuft Bush"], + ["FFDDCF", "Watusi"], + ["FFDDF4", "Pink Lace"], + ["FFDEAD", "Navajo White"], + ["FFDEB3", "Frangipani"], + ["FFE1DF", "Pippin"], + ["FFE1F2", "Pale Rose"], + ["FFE2C5", "Negroni"], + ["FFE5A0", "Cream Brulee"], + ["FFE5B4", "Peach"], + ["FFE6C7", "Tequila"], + ["FFE772", "Kournikova"], + ["FFEAC8", "Sandy Beach"], + ["FFEAD4", "Karry"], + ["FFEC13", "Broom"], + ["FFEDBC", "Colonial White"], + ["FFEED8", "Derby"], + ["FFEFA1", "Vis Vis"], + ["FFEFC1", "Egg White"], + ["FFEFD5", "Papaya Whip"], + ["FFEFEC", "Fair Pink"], + ["FFF0DB", "Peach Cream"], + ["FFF0F5", "Lavender blush"], + ["FFF14F", "Gorse"], + ["FFF1B5", "Buttermilk"], + ["FFF1D8", "Pink Lady"], + ["FFF1EE", "Forget Me Not"], + ["FFF1F9", "Tutu"], + ["FFF39D", "Picasso"], + ["FFF3F1", "Chardon"], + ["FFF46E", "Paris Daisy"], + ["FFF4CE", "Barley White"], + ["FFF4DD", "Egg Sour"], + ["FFF4E0", "Sazerac"], + ["FFF4E8", "Serenade"], + ["FFF4F3", "Chablis"], + ["FFF5EE", "Seashell Peach"], + ["FFF5F3", "Sauvignon"], + ["FFF6D4", "Milk Punch"], + ["FFF6DF", "Varden"], + ["FFF6F5", "Rose White"], + ["FFF8D1", "Baja White"], + ["FFF9E2", "Gin Fizz"], + ["FFF9E6", "Early Dawn"], + ["FFFACD", "Lemon Chiffon"], + ["FFFAF4", "Bridal Heath"], + ["FFFBDC", "Scotch Mist"], + ["FFFBF9", "Soapstone"], + ["FFFC99", "Witch Haze"], + ["FFFCEA", "Buttery White"], + ["FFFCEE", "Island Spice"], + ["FFFDD0", "Cream"], + ["FFFDE6", "Chilean Heath"], + ["FFFDE8", "Travertine"], + ["FFFDF3", "Orchid White"], + ["FFFDF4", "Quarter Pearl Lusta"], + ["FFFEE1", "Half and Half"], + ["FFFEEC", "Apricot White"], + ["FFFEF0", "Rice Cake"], + ["FFFEF6", "Black White"], + ["FFFEFD", "Romance"], + ["FFFF00", "Yellow"], + ["FFFF66", "Laser Lemon"], + ["FFFF99", "Pale Canary"], + ["FFFFB4", "Portafino"], + ["FFFFF0", "Ivory"], + ["FFFFFF", "White"] +] diff --git a/packages/color-palette/src/json/palette.json b/packages/color-palette/src/json/palette.json new file mode 100644 index 0000000..440a04b --- /dev/null +++ b/packages/color-palette/src/json/palette.json @@ -0,0 +1,274 @@ +[ + { + "key": "red", + "palettes": [ + { "hexcode": "#fef2f2", "number": 50, "name": "Bridesmaid" }, + { "hexcode": "#fee2e2", "number": 100, "name": "Pippin" }, + { "hexcode": "#fecaca", "number": 200, "name": "Your Pink" }, + { "hexcode": "#fca5a5", "number": 300, "name": "Cornflower Lilac" }, + { "hexcode": "#f87171", "number": 400, "name": "Bittersweet" }, + { "hexcode": "#ef4444", "number": 500, "name": "Cinnabar" }, + { "hexcode": "#dc2626", "number": 600, "name": "Persian Red" }, + { "hexcode": "#b91c1c", "number": 700, "name": "Thunderbird" }, + { "hexcode": "#991b1b", "number": 800, "name": "Old Brick" }, + { "hexcode": "#7f1d1d", "number": 900, "name": "Falu Red" }, + { "hexcode": "#450a0a", "number": 950, "name": "Mahogany" } + ] + }, + { + "key": "orange", + "palettes": [ + { "hexcode": "#fff7ed", "number": 50, "name": "Serenade" }, + { "hexcode": "#ffedd5", "number": 100, "name": "Derby" }, + { "hexcode": "#fed7aa", "number": 200, "name": "Caramel" }, + { "hexcode": "#fdba74", "number": 300, "name": "Macaroni and Cheese" }, + { "hexcode": "#fb923c", "number": 400, "name": "Neon Carrot" }, + { "hexcode": "#f97316", "number": 500, "name": "Ecstasy" }, + { "hexcode": "#ea580c", "number": 600, "name": "Trinidad" }, + { "hexcode": "#c2410c", "number": 700, "name": "Tia Maria" }, + { "hexcode": "#9a3412", "number": 800, "name": "Tabasco" }, + { "hexcode": "#7c2d12", "number": 900, "name": "Pueblo" }, + { "hexcode": "#431407", "number": 950, "name": "Rebel" } + ] + }, + { + "key": "amber", + "palettes": [ + { "hexcode": "#fffbeb", "number": 50, "name": "Island Spice" }, + { "hexcode": "#fef3c7", "number": 100, "name": "Beeswax" }, + { "hexcode": "#fde68a", "number": 200, "name": "Sweet Corn" }, + { "hexcode": "#fcd34d", "number": 300, "name": "Mustard" }, + { "hexcode": "#fbbf24", "number": 400, "name": "Lightning Yellow" }, + { "hexcode": "#f59e0b", "number": 500, "name": "California" }, + { "hexcode": "#d97706", "number": 600, "name": "Christine" }, + { "hexcode": "#b45309", "number": 700, "name": "Vesuvius" }, + { "hexcode": "#92400e", "number": 800, "name": "Korma" }, + { "hexcode": "#78350f", "number": 900, "name": "Copper Canyon" }, + { "hexcode": "#451a03", "number": 950, "name": "Brown Pod" } + ] + }, + { + "key": "yellow", + "palettes": [ + { "hexcode": "#fefce8", "number": 50, "name": "Orange White" }, + { "hexcode": "#fef9c3", "number": 100, "name": "Lemon Chiffon" }, + { "hexcode": "#fef08a", "number": 200, "name": "Sweet Corn" }, + { "hexcode": "#fde047", "number": 300, "name": "Bright Sun" }, + { "hexcode": "#facc15", "number": 400, "name": "Candlelight" }, + { "hexcode": "#eab308", "number": 500, "name": "Corn" }, + { "hexcode": "#ca8a04", "number": 600, "name": "Pirate Gold" }, + { "hexcode": "#a16207", "number": 700, "name": "Mai Tai" }, + { "hexcode": "#854d0e", "number": 800, "name": "Korma" }, + { "hexcode": "#713f12", "number": 900, "name": "Sepia" }, + { "hexcode": "#422006", "number": 950, "name": "Dark Ebony" } + ] + }, + { + "key": "lime", + "palettes": [ + { "hexcode": "#f7fee7", "number": 50, "name": "Spring Sun" }, + { "hexcode": "#ecfccb", "number": 100, "name": "Chiffon" }, + { "hexcode": "#d9f99d", "number": 200, "name": "Gossip" }, + { "hexcode": "#bef264", "number": 300, "name": "Sulu" }, + { "hexcode": "#a3e635", "number": 400, "name": "Conifer" }, + { "hexcode": "#84cc16", "number": 500, "name": "Lima" }, + { "hexcode": "#65a30d", "number": 600, "name": "Christi" }, + { "hexcode": "#4d7c0f", "number": 700, "name": "Green Leaf" }, + { "hexcode": "#3f6212", "number": 800, "name": "Dell" }, + { "hexcode": "#365314", "number": 900, "name": "Clover" }, + { "hexcode": "#1a2e05", "number": 950, "name": "Deep Forest Green" } + ] + }, + { + "key": "green", + "palettes": [ + { "hexcode": "#f0fdf4", "number": 50, "name": "Ottoman" }, + { "hexcode": "#dcfce7", "number": 100, "name": "Blue Romance" }, + { "hexcode": "#bbf7d0", "number": 200, "name": "Magic Mint" }, + { "hexcode": "#86efac", "number": 300, "name": "Algae Green" }, + { "hexcode": "#4ade80", "number": 400, "name": "Emerald" }, + { "hexcode": "#22c55e", "number": 500, "name": "Malachite" }, + { "hexcode": "#16a34a", "number": 600, "name": "Salem" }, + { "hexcode": "#15803d", "number": 700, "name": "Jewel" }, + { "hexcode": "#166534", "number": 800, "name": "Jewel" }, + { "hexcode": "#14532d", "number": 900, "name": "Green Pea" }, + { "hexcode": "#052e16", "number": 950, "name": "English Holly" } + ] + }, + { + "key": "emerald", + "palettes": [ + { "hexcode": "#ecfdf5", "number": 50, "name": "White Ice" }, + { "hexcode": "#d1fae5", "number": 100, "name": "Granny Apple" }, + { "hexcode": "#a7f3d0", "number": 200, "name": "Magic Mint" }, + { "hexcode": "#6ee7b7", "number": 300, "name": "Bermuda" }, + { "hexcode": "#34d399", "number": 400, "name": "Shamrock" }, + { "hexcode": "#10b981", "number": 500, "name": "Mountain Meadow" }, + { "hexcode": "#059669", "number": 600, "name": "Green Haze" }, + { "hexcode": "#047857", "number": 700, "name": "Watercourse" }, + { "hexcode": "#065f46", "number": 800, "name": "Watercourse" }, + { "hexcode": "#064e3b", "number": 900, "name": "Evening Sea" }, + { "hexcode": "#022c22", "number": 950, "name": "Burnham" } + ] + }, + { + "key": "teal", + "palettes": [ + { "hexcode": "#f0fdfa", "number": 50, "name": "White Ice" }, + { "hexcode": "#ccfbf1", "number": 100, "name": "Scandal" }, + { "hexcode": "#99f6e4", "number": 200, "name": "Ice Cold" }, + { "hexcode": "#5eead4", "number": 300, "name": "Turquoise Blue" }, + { "hexcode": "#2dd4bf", "number": 400, "name": "Turquoise" }, + { "hexcode": "#14b8a6", "number": 500, "name": "Java" }, + { "hexcode": "#0d9488", "number": 600, "name": "Blue Chill" }, + { "hexcode": "#0f766e", "number": 700, "name": "Genoa" }, + { "hexcode": "#115e59", "number": 800, "name": "Eden" }, + { "hexcode": "#134e4a", "number": 900, "name": "Eden" }, + { "hexcode": "#042f2e", "number": 950, "name": "Tiber" } + ] + }, + { + "key": "cyan", + "palettes": [ + { "hexcode": "#ecfeff", "number": 50, "name": "Bubbles" }, + { "hexcode": "#cffafe", "number": 100, "name": "Oyster Bay" }, + { "hexcode": "#a5f3fc", "number": 200, "name": "Anakiwa" }, + { "hexcode": "#67e8f9", "number": 300, "name": "Spray" }, + { "hexcode": "#22d3ee", "number": 400, "name": "Bright Turquoise" }, + { "hexcode": "#06b6d4", "number": 500, "name": "Cerulean" }, + { "hexcode": "#0891b2", "number": 600, "name": "Bondi Blue" }, + { "hexcode": "#0e7490", "number": 700, "name": "Blue Chill" }, + { "hexcode": "#155e75", "number": 800, "name": "Blumine" }, + { "hexcode": "#164e63", "number": 900, "name": "Chathams Blue" }, + { "hexcode": "#083344", "number": 950, "name": "Tarawera" } + ] + }, + { + "key": "sky", + "palettes": [ + { "hexcode": "#f0f9ff", "number": 50, "name": "Alice Blue" }, + { "hexcode": "#e0f2fe", "number": 100, "name": "Pattens Blue" }, + { "hexcode": "#bae6fd", "number": 200, "name": "French Pass" }, + { "hexcode": "#7dd3fc", "number": 300, "name": "Malibu" }, + { "hexcode": "#38bdf8", "number": 400, "name": "Picton Blue" }, + { "hexcode": "#0ea5e9", "number": 500, "name": "Cerulean" }, + { "hexcode": "#0284c7", "number": 600, "name": "Lochmara" }, + { "hexcode": "#0369a1", "number": 700, "name": "Bahama Blue" }, + { "hexcode": "#075985", "number": 800, "name": "Venice Blue" }, + { "hexcode": "#0c4a6e", "number": 900, "name": "Chathams Blue" }, + { "hexcode": "#082f49", "number": 950, "name": "Blue Whale" } + ] + }, + { + "key": "blue", + "palettes": [ + { "hexcode": "#eff6ff", "number": 50, "name": "Zumthor" }, + { "hexcode": "#dbeafe", "number": 100, "name": "Hawkes Blue" }, + { "hexcode": "#bfdbfe", "number": 200, "name": "Tropical Blue" }, + { "hexcode": "#93c5fd", "number": 300, "name": "Malibu" }, + { "hexcode": "#60a5fa", "number": 400, "name": "Cornflower Blue" }, + { "hexcode": "#3b82f6", "number": 500, "name": "Dodger Blue" }, + { "hexcode": "#2563eb", "number": 600, "name": "Royal Blue" }, + { "hexcode": "#1d4ed8", "number": 700, "name": "Cerulean Blue" }, + { "hexcode": "#1e40af", "number": 800, "name": "Persian Blue" }, + { "hexcode": "#1e3a8a", "number": 900, "name": "Bay of Many" }, + { "hexcode": "#172554", "number": 950, "name": "Bunting" } + ] + }, + { + "key": "indigo", + "palettes": [ + { "hexcode": "#eef2ff", "number": 50, "name": "Zircon" }, + { "hexcode": "#e0e7ff", "number": 100, "name": "Hawkes Blue" }, + { "hexcode": "#c7d2fe", "number": 200, "name": "Periwinkle" }, + { "hexcode": "#a5b4fc", "number": 300, "name": "Perano" }, + { "hexcode": "#818cf8", "number": 400, "name": "Portage" }, + { "hexcode": "#6366f1", "number": 500, "name": "Royal Blue" }, + { "hexcode": "#4f46e5", "number": 600, "name": "Royal Blue" }, + { "hexcode": "#4338ca", "number": 700, "name": "Governor Bay" }, + { "hexcode": "#3730a3", "number": 800, "name": "Governor Bay" }, + { "hexcode": "#312e81", "number": 900, "name": "Minsk" }, + { "hexcode": "#1e1b4b", "number": 950, "name": "Port Gore" } + ] + }, + { + "key": "violet", + "palettes": [ + { "hexcode": "#f5f3ff", "number": 50, "name": "Titan White" }, + { "hexcode": "#ede9fe", "number": 100, "name": "Titan White" }, + { "hexcode": "#ddd6fe", "number": 200, "name": "Fog" }, + { "hexcode": "#c4b5fd", "number": 300, "name": "Melrose" }, + { "hexcode": "#a78bfa", "number": 400, "name": "Dull Lavender" }, + { "hexcode": "#8b5cf6", "number": 500, "name": "Medium Purple" }, + { "hexcode": "#7c3aed", "number": 600, "name": "Purple Heart" }, + { "hexcode": "#6d28d9", "number": 700, "name": "Purple Heart" }, + { "hexcode": "#5b21b6", "number": 800, "name": "Purple Heart" }, + { "hexcode": "#4c1d95", "number": 900, "name": "Daisy Bush" }, + { "hexcode": "#2e1065", "number": 950, "name": "Violent Violet" } + ] + }, + { + "key": "purple", + "palettes": [ + { "hexcode": "#faf5ff", "number": 50, "name": "Magnolia" }, + { "hexcode": "#f3e8ff", "number": 100, "name": "Blue Chalk" }, + { "hexcode": "#e9d5ff", "number": 200, "name": "Blue Chalk" }, + { "hexcode": "#d8b4fe", "number": 300, "name": "Mauve" }, + { "hexcode": "#c084fc", "number": 400, "name": "Heliotrope" }, + { "hexcode": "#a855f7", "number": 500, "name": "Medium Purple" }, + { "hexcode": "#9333ea", "number": 600, "name": "Electric Violet" }, + { "hexcode": "#7e22ce", "number": 700, "name": "Purple Heart" }, + { "hexcode": "#6b21a8", "number": 800, "name": "Seance" }, + { "hexcode": "#581c87", "number": 900, "name": "Daisy Bush" }, + { "hexcode": "#3b0764", "number": 950, "name": "Christalle" } + ] + }, + { + "key": "fuchsia", + "palettes": [ + { "hexcode": "#fdf4ff", "number": 50, "name": "White Pointer" }, + { "hexcode": "#fae8ff", "number": 100, "name": "White Pointer" }, + { "hexcode": "#f5d0fe", "number": 200, "name": "Mauve" }, + { "hexcode": "#f0abfc", "number": 300, "name": "Mauve" }, + { "hexcode": "#e879f9", "number": 400, "name": "Heliotrope" }, + { "hexcode": "#d946ef", "number": 500, "name": "Heliotrope" }, + { "hexcode": "#c026d3", "number": 600, "name": "Fuchsia Pink" }, + { "hexcode": "#a21caf", "number": 700, "name": "Violet Eggplant" }, + { "hexcode": "#86198f", "number": 800, "name": "Seance" }, + { "hexcode": "#701a75", "number": 900, "name": "Seance" }, + { "hexcode": "#4a044e", "number": 950, "name": "Clairvoyant" } + ] + }, + { + "key": "pink", + "palettes": [ + { "hexcode": "#fdf2f8", "number": 50, "name": "Wisp Pink" }, + { "hexcode": "#fce7f3", "number": 100, "name": "Carousel Pink" }, + { "hexcode": "#fbcfe8", "number": 200, "name": "Classic Rose" }, + { "hexcode": "#f9a8d4", "number": 300, "name": "Lavender Pink" }, + { "hexcode": "#f472b6", "number": 400, "name": "Persian Pink" }, + { "hexcode": "#ec4899", "number": 500, "name": "Brilliant Rose" }, + { "hexcode": "#db2777", "number": 600, "name": "Cerise" }, + { "hexcode": "#be185d", "number": 700, "name": "Maroon Flush" }, + { "hexcode": "#9d174d", "number": 800, "name": "Disco" }, + { "hexcode": "#831843", "number": 900, "name": "Disco" }, + { "hexcode": "#500724", "number": 950, "name": "Cab Sav" } + ] + }, + { + "key": "rose", + "palettes": [ + { "hexcode": "#fff1f2", "number": 50, "name": "Lavender blush" }, + { "hexcode": "#ffe4e6", "number": 100, "name": "Cosmos" }, + { "hexcode": "#fecdd3", "number": 200, "name": "Pastel Pink" }, + { "hexcode": "#fda4af", "number": 300, "name": "Sweet Pink" }, + { "hexcode": "#fb7185", "number": 400, "name": "Froly" }, + { "hexcode": "#f43f5e", "number": 500, "name": "Radical Red" }, + { "hexcode": "#e11d48", "number": 600, "name": "Amaranth" }, + { "hexcode": "#be123c", "number": 700, "name": "Cardinal" }, + { "hexcode": "#9f1239", "number": 800, "name": "Shiraz" }, + { "hexcode": "#881337", "number": 900, "name": "Claret" }, + { "hexcode": "#4c0519", "number": 950, "name": "Cab Sav" } + ] + } +] diff --git a/packages/color-palette/src/name.ts b/packages/color-palette/src/name.ts new file mode 100644 index 0000000..8aebbde --- /dev/null +++ b/packages/color-palette/src/name.ts @@ -0,0 +1,46 @@ +import { getHex, getRgb, getHsl } from './color'; +import colorNames from './json/color-name.json'; + +export function getColorName(color: string) { + const hex = getHex(color); + const rgb = getRgb(color); + const hsl = getHsl(color); + + let ndf = 0; + let ndf1 = 0; + let ndf2 = 0; + let cl = -1; + let df = -1; + + let name = ''; + + colorNames.some((item, index) => { + const [hexValue, colorName] = item; + + const hexcode = `#${hexValue}`; + + const match = hex === hexcode; + + if (match) { + name = colorName; + } else { + const { r, g, b } = getRgb(hexcode); + const { h, s, l } = getHsl(hexcode); + + ndf1 = (rgb.r - r) ** 2 + (rgb.g - g) ** 2 + (rgb.b - b) ** 2; + ndf2 = (hsl.h - h) ** 2 + (hsl.s - s) ** 2 + (hsl.l - l) ** 2; + + ndf = ndf1 + ndf2 * 2; + if (df < 0 || df > ndf) { + df = ndf; + cl = index; + } + } + + return match; + }); + + name = cl < 0 ? 'Invalid Color' : colorNames[cl][1]; + + return name; +} diff --git a/packages/color-palette/src/palette.ts b/packages/color-palette/src/palette.ts new file mode 100644 index 0000000..e4b7e63 --- /dev/null +++ b/packages/color-palette/src/palette.ts @@ -0,0 +1,95 @@ +import { isValidColor, getHsl, getDeltaE, transformHslToHex } from './color'; +import { getColorName } from './name'; +import type { ColorPaletteFamily, ColorPaletteFamilyWithNearestPalette } from './type'; +import defaultPalettes from './json/palette.json'; + +export function getNearestColorPaletteFamily(color: string, families: ColorPaletteFamily[]) { + const familyWithConfig = families.map(family => { + const palettes = family.palettes.map(palette => { + return { + ...palette, + delta: getDeltaE(color, palette.hexcode) + }; + }); + + const nearestPalette = palettes.reduce((prev, curr) => (prev.delta < curr.delta ? prev : curr)); + + return { + ...family, + palettes, + nearestPalette + }; + }); + + const nearestPaletteFamily = familyWithConfig.reduce((prev, curr) => + prev.nearestPalette.delta < curr.nearestPalette.delta ? prev : curr + ); + + const { l } = getHsl(color); + + const paletteFamily: ColorPaletteFamilyWithNearestPalette = { + ...nearestPaletteFamily, + nearestLightnessPalette: nearestPaletteFamily.palettes.reduce((prev, curr) => { + const { l: prevLightness } = getHsl(prev.hexcode); + const { l: currLightness } = getHsl(curr.hexcode); + + const deltaPrev = Math.abs(prevLightness - l); + const deltaCurr = Math.abs(currLightness - l); + + return deltaPrev < deltaCurr ? prev : curr; + }) + }; + + return paletteFamily; +} + +export function getColorPaletteFamily(color: string, colorName: string) { + if (!isValidColor(color)) { + throw new Error('Invalid color, please check color value!'); + } + + const { h: h1, s: s1 } = getHsl(color); + + const { nearestLightnessPalette, palettes } = getNearestColorPaletteFamily( + color, + defaultPalettes as ColorPaletteFamily[] + ); + + const { number, hexcode } = nearestLightnessPalette; + + const { h: h2, s: s2 } = getHsl(hexcode); + + const deltaH = h1 - h2 || h2; + + const sRatio = s1 / s2; + + const colorPaletteFamily: ColorPaletteFamily = { + key: colorName, + palettes: palettes.map(palette => { + let hexValue = color; + + const isSame = number === palette.number; + + if (!isSame) { + const { h: h3, s: s3, l } = getHsl(palette.hexcode); + + const newH = deltaH < 0 ? h3 + deltaH : deltaH; + const newS = s3 * sRatio; + + hexValue = transformHslToHex({ + h: newH, + s: newS, + l + }); + } + + return { + hexcode: hexValue, + number: palette.number, + name: getColorName(hexValue) + }; + }) + }; + + return colorPaletteFamily; +} diff --git a/packages/color-palette/src/type.ts b/packages/color-palette/src/type.ts new file mode 100644 index 0000000..bdad089 --- /dev/null +++ b/packages/color-palette/src/type.ts @@ -0,0 +1,63 @@ +/** + * the color palette number + */ +export type ColorPaletteNumber = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950; + +/** + * the color palette item + */ +export type ColorPaletteItem = { + /** + * the color hexcode + */ + hexcode: string; + /** + * the color number + * @link {@link ColorPaletteNumber} + */ + number: ColorPaletteNumber; + /** + * the color name + */ + name: string; +}; + +export type ColorPaletteFamily = { + /** + * the color palette family key + */ + key: string; + /** + * the color palette family's palettes + */ + palettes: ColorPaletteItem[]; +}; + +export type ColorPaletteWithDelta = ColorPaletteItem & { + delta: number; +}; + +export type ColorPaletteItemWithName = ColorPaletteItem & { + name: string; +}; + +export type ColorPaletteFamilyWithNearestPalette = ColorPaletteFamily & { + nearestPalette: ColorPaletteWithDelta; + nearestLightnessPalette: ColorPaletteWithDelta; +}; + +export type ColorPalette = ColorPaletteFamily & { + /** + * the color map of the palette + */ + colorMap: Map; + /** + * the main color of the palette + * @description which number is 500 + */ + main: ColorPaletteItemWithName; + /** + * the match color of the palette + */ + match: ColorPaletteItemWithName; +}; diff --git a/packages/docs/.vitepress/config.ts b/packages/docs/.vitepress/config.ts new file mode 100644 index 0000000..1a71de8 --- /dev/null +++ b/packages/docs/.vitepress/config.ts @@ -0,0 +1,38 @@ +import path from 'node:path'; +import { defineConfig } from 'vitepress'; + +export default defineConfig({ + title: 'Soybean Admin', + description: '一个优雅、清新、漂亮的中后台模版', + head: [ + ['meta', { name: 'author', content: 'Soybean' }], + [ + 'meta', + { + name: 'keywords', + content: 'soybean, soybean-admin, vite, vue, vue3, soybean-admin docs' + } + ], + ['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }], + [ + 'meta', + { + name: 'viewport', + content: 'width=device-width,initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no' + } + ], + ['link', { rel: 'icon', href: '/favicon.ico' }] + ], + srcDir: path.join(process.cwd(), 'packages/docs/src'), + themeConfig: { + logo: '/logo.svg', + socialLinks: [{ icon: 'github', link: 'https://github.com/honghuangdc/soybean-admin' }], + algolia: { + appId: '98WN1RY04S', + apiKey: '13e9f5767b774422a5880723d9c23265', + indexName: 'soybean' + }, + nav: [], + sidebar: {} + } +}); diff --git a/packages/docs/.vitepress/icon.ts b/packages/docs/.vitepress/icon.ts new file mode 100644 index 0000000..28717de --- /dev/null +++ b/packages/docs/.vitepress/icon.ts @@ -0,0 +1,32 @@ +export const qqSvg = ` + + + + + + + + + + + + +`; diff --git a/packages/docs/.vitepress/theme/index.ts b/packages/docs/.vitepress/theme/index.ts new file mode 100644 index 0000000..7850521 --- /dev/null +++ b/packages/docs/.vitepress/theme/index.ts @@ -0,0 +1,4 @@ +import Theme from 'vitepress/theme'; +import './style.css'; + +export default Theme; diff --git a/packages/docs/.vitepress/theme/style.css b/packages/docs/.vitepress/theme/style.css new file mode 100644 index 0000000..f98e61f --- /dev/null +++ b/packages/docs/.vitepress/theme/style.css @@ -0,0 +1,90 @@ +/** + * Customize default theme styling by overriding CSS variables: + * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css + */ + +/** + * Colors + * -------------------------------------------------------------------------- */ + +:root { + --vp-c-brand: #646cff; + --vp-c-brand-light: #747bff; + --vp-c-brand-lighter: #9499ff; + --vp-c-brand-lightest: #bcc0ff; + --vp-c-brand-dark: #535bf2; + --vp-c-brand-darker: #454ce1; + --vp-c-brand-dimm: rgba(100, 108, 255, 0.08); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: var(--vp-c-brand-light); + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand); + --vp-button-brand-hover-border: var(--vp-c-brand-light); + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-light); + --vp-button-brand-active-border: var(--vp-c-brand-light); + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-button-brand-bg); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + var(--vp-c-brand-lightest) 30%, + var(--vp-c-brand-darker) + ); + + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + var(--vp-c-brand-lightest) 30%, + var(--vp-c-brand) 50% + ); + --vp-home-hero-image-filter: blur(40px); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(72px); + } +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-tip-border: var(--vp-c-brand); + --vp-custom-block-tip-text: var(--vp-c-brand-darker); + --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); +} + +.dark { + --vp-custom-block-tip-border: var(--vp-c-brand); + --vp-custom-block-tip-text: var(--vp-c-brand-lightest); + --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); +} + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand) !important; +} diff --git a/packages/docs/package.json b/packages/docs/package.json new file mode 100644 index 0000000..faaed9f --- /dev/null +++ b/packages/docs/package.json @@ -0,0 +1,12 @@ +{ + "name": "@sa/docs", + "version": "1.0.0", + "scripts": { + "dev": "vitepress dev", + "build": "vitepress build", + "serve": "vitepress serve" + }, + "devDependencies": { + "vitepress": "1.0.0-rc.22" + } +} diff --git a/packages/docs/src/index.md b/packages/docs/src/index.md new file mode 100644 index 0000000..af7f532 --- /dev/null +++ b/packages/docs/src/index.md @@ -0,0 +1,44 @@ +--- +layout: home + +title: Soybean Admin +titleTemplate: 一个清新优雅的中后台模版 + +hero: + name: Soybean Admin + text: 清新优雅的中后台模版 + tagline: 基于 Vue3 + Vite3 + TS + NaiveUI + UnoCSS + image: + src: /logo.svg + alt: Soybean Admin + actions: + - theme: brand + text: 开始 + link: /guide/ + - theme: alt + text: 介绍 + link: /guide/introduction + - theme: alt + text: 在 GitHub 上查看 + link: https://github.com/honghuangdc/soybean-admin + +features: + - icon: 🆕 + title: 最新流行技术栈 + details: 基于Vue3、Vite3、TS、NaiveUI和UnoCSS等最新技术栈开发 + - icon: 🦋 + title: 极高水准的代码规范 + details: 代码规范完善,代码结构清晰 + - icon: 🛠️ + title: 丰富的插件 + details: 常见的Web端插件示例实现 + - icon: 🔩 + title: 主题配置 + details: 丰富的主题配置及暗黑主题适配 + - icon: 🔗 + title: 基于文件的路由系统 + details: 自动生成路由声明、路由导入和路由模块 + - icon: 🔑 + title: 权限管理 + details: 完善的前后端权限管理方案 +--- diff --git a/packages/eslint-config/configs/base.js b/packages/eslint-config/configs/base.js new file mode 100644 index 0000000..d3af3b6 --- /dev/null +++ b/packages/eslint-config/configs/base.js @@ -0,0 +1,6 @@ +/** + * @type {import('eslint').ESLint.ConfigData} + */ +module.exports = { + extends: [require.resolve('./ts.js'), require.resolve('./prettier.js')] +}; diff --git a/packages/eslint-config/configs/js.js b/packages/eslint-config/configs/js.js new file mode 100644 index 0000000..0065e63 --- /dev/null +++ b/packages/eslint-config/configs/js.js @@ -0,0 +1,44 @@ +/** + * @type {import('eslint').ESLint.ConfigData} + */ +module.exports = { + root: true, + env: { + browser: true, + node: true, + commonjs: true, + es2024: true + }, + parserOptions: { + ecmaVersion: 2024, + ecmaFeatures: { + jsx: true + }, + sourceType: 'module' + }, + ignorePatterns: [ + 'node_modules', + '*.min.*', + 'CHANGELOG.md', + 'dist', + 'LICENSE*', + 'output', + 'coverage', + 'public', + 'temp', + 'package-lock.json', + 'pnpm-lock.yaml', + 'yarn.lock', + '__snapshots__', + '!.github', + '!.vitepress', + '!.vscode' + ], + plugins: ['n', 'promise'], + extends: [require.resolve('../rules/all.js'), 'plugin:import/recommended'], + rules: { + // import + 'import/no-mutable-exports': 'error', + 'import/no-named-as-default': 'off' + } +}; diff --git a/packages/eslint-config/configs/prettier.js b/packages/eslint-config/configs/prettier.js new file mode 100644 index 0000000..fe58ce9 --- /dev/null +++ b/packages/eslint-config/configs/prettier.js @@ -0,0 +1,11 @@ +const prettierRules = require('../rules/prettier'); + +/** + * @type {import('eslint').ESLint.ConfigData} + */ +module.exports = { + extends: ['plugin:prettier/recommended'], + rules: { + 'prettier/prettier': ['error', prettierRules] + } +}; diff --git a/packages/eslint-config/configs/ts.js b/packages/eslint-config/configs/ts.js new file mode 100644 index 0000000..e700bf4 --- /dev/null +++ b/packages/eslint-config/configs/ts.js @@ -0,0 +1,61 @@ +/** + * @type {import('eslint').ESLint.ConfigData} + */ +module.exports = { + plugins: ['@typescript-eslint'], + extends: [require.resolve('./js.js'), 'plugin:import/typescript', 'plugin:@typescript-eslint/recommended'], + settings: { + 'import/resolver': { + typescript: { + project: ['tsconfig.json', 'packages/*/tsconfig.json', 'examples/*/tsconfig.json', 'docs/*/tsconfig.json'] + } + } + }, + overrides: [ + { + files: ['*.ts', '*.tsx', '*.mts', '*.cts'], + parser: '@typescript-eslint/parser' + }, + { + files: ['*.js', '*.mjs', '*.cjs', '*.cts'], + rules: { + '@typescript-eslint/no-var-requires': 'off' + } + } + ], + rules: { + // TS + '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports', disallowTypeAnnotations: false }], + '@typescript-eslint/no-empty-interface': [ + 'error', + { + allowSingleExtends: true + } + ], + + // Override JS + 'no-redeclare': 'off', + '@typescript-eslint/no-redeclare': 'error', + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + vars: 'all', + args: 'all', + ignoreRestSiblings: false, + varsIgnorePattern: '^_', + argsIgnorePattern: '^_' + } + ], + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': ['error', { functions: false, classes: false, variables: true }], + 'no-shadow': 'off', + '@typescript-eslint/no-shadow': 'error', + + // off + '@typescript-eslint/consistent-type-definitions': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-non-null-assertion': 'off' + } +}; diff --git a/packages/eslint-config/configs/vue.js b/packages/eslint-config/configs/vue.js new file mode 100644 index 0000000..493f5a7 --- /dev/null +++ b/packages/eslint-config/configs/vue.js @@ -0,0 +1,30 @@ +/** + * @type {import('eslint').ESLint.ConfigData} + */ +module.exports = { + extends: ['plugin:vue/vue3-recommended', require.resolve('./base.js')], + overrides: [ + { + files: ['*.vue'], + parser: 'vue-eslint-parser', + parserOptions: { + parser: { + js: 'espree', + jsx: 'espree', + ts: '@typescript-eslint/parser', + tsx: '@typescript-eslint/parser' + }, + extraFileExtensions: ['.vue'], + ecmaFeatures: { + jsx: true + } + }, + rules: { + 'no-undef': 'off' // TS will check un declared variables, if the script code is is in a .vue file, this rule should not disabled + } + } + ], + rules: { + 'vue/multi-word-component-names': 'off' + } +}; diff --git a/packages/eslint-config/index.js b/packages/eslint-config/index.js new file mode 100644 index 0000000..bae7d0c --- /dev/null +++ b/packages/eslint-config/index.js @@ -0,0 +1,6 @@ +const baseConfig = require('./configs/base'); + +/** + * @type {import('eslint').ESLint.ConfigData} + */ +module.exports = baseConfig; diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json new file mode 100644 index 0000000..f9e8e8c --- /dev/null +++ b/packages/eslint-config/package.json @@ -0,0 +1,23 @@ +{ + "name": "eslint-config-sa", + "version": "1.0.0", + "description": "SoybeanAdmin's eslint config resets", + "exports": { + ".": "./index.js", + "./vue": "./configs/vue.js" + }, + "devDependencies": { + "@types/eslint": "8.44.6", + "@typescript-eslint/eslint-plugin": "6.8.0", + "@typescript-eslint/parser": "6.8.0", + "eslint": "8.51.0", + "eslint-config-prettier": "9.0.0", + "eslint-import-resolver-typescript": "3.6.1", + "eslint-plugin-import": "2.28.1", + "eslint-plugin-n": "16.2.0", + "eslint-plugin-prettier": "5.0.1", + "eslint-plugin-promise": "6.1.1", + "eslint-plugin-vue": "9.17.0", + "prettier": "3.0.3" + } +} diff --git a/packages/eslint-config/rules/all.js b/packages/eslint-config/rules/all.js new file mode 100644 index 0000000..967c3f4 --- /dev/null +++ b/packages/eslint-config/rules/all.js @@ -0,0 +1,1681 @@ +/** + * @type {import('eslint').Linter.RulesRecord} + */ +module.exports = { + rules: { + /* + * Possible Problems + * These rules relate to possible logic errors in code + */ + + /** + * @description Enforce return statements in callbacks of array methods + * @link https://eslint.org/docs/latest/rules/array-callback-return + */ + 'array-callback-return': 'error', + + /** + * @description Require `super()` calls in constructors + * @link https://eslint.org/docs/latest/rules/constructor-super + */ + 'constructor-super': 'error', + + /** + * @description Enforce \"for\" loop update clause moving the counter in the right direction. + * @link https://eslint.org/docs/latest/rules/for-direction + */ + 'for-direction': 'error', + + /** + * @description Enforce `return` statements in getters + * @link https://eslint.org/docs/latest/rules/getter-return + */ + 'getter-return': 'error', + + /** + * @description Disallow using an async function as a Promise executor + * @link https://eslint.org/docs/latest/rules/no-async-promise-executor + */ + 'no-async-promise-executor': 'error', + + /** + * @description Disallow `await` inside of loops + * @link https://eslint.org/docs/latest/rules/no-await-in-loop + */ + 'no-await-in-loop': 'error', + + /** + * @description Disallow reassigning class members + * @link https://eslint.org/docs/latest/rules/no-class-assign + */ + 'no-class-assign': 'error', + + /** + * @description Disallow comparing against -0 + * @link https://eslint.org/docs/latest/rules/no-compare-neg-zero + */ + 'no-compare-neg-zero': 'error', + + /** + * @description Disallow assignment operators in conditional expressions + * @link https://eslint.org/docs/latest/rules/no-cond-assign + */ + 'no-cond-assign': 'error', + + /** + * @description Disallow reassigning `const` variables + * @link https://eslint.org/docs/latest/rules/no-const-assign + */ + 'no-const-assign': 'error', + + /** + * @description Disallow expressions where the operation doesn't affect the value + * @link https://eslint.org/docs/latest/rules/no-constant-binary-expression + */ + 'no-constant-binary-expression': 'error', + + /** + * @description Disallow constant expressions in conditions + * @link https://eslint.org/docs/latest/rules/no-constant-condition + */ + 'no-constant-condition': 'error', + + /** + * @description Disallow returning value from constructor + * @link https://eslint.org/docs/latest/rules/no-constructor-return + */ + 'no-constructor-return': 'error', + + /** + * @description Disallow control characters in regular expressions + * @link https://eslint.org/docs/latest/rules/no-control-regex + */ + 'no-control-regex': 'error', + + /** + * @description Disallow the use of `debugger` + * @link https://eslint.org/docs/latest/rules/no-debugger + */ + 'no-debugger': 'error', + + /** + * @description Disallow duplicate arguments in `function` definitions + * @link https://eslint.org/docs/latest/rules/no-dupe-args + */ + 'no-dupe-args': 'error', + + /** + * @description Disallow duplicate class members + * @link https://eslint.org/docs/latest/rules/no-dupe-class-members + */ + 'no-dupe-class-members': 'error', + + /** + * @description Disallow duplicate conditions in if-else-if chains + * @link https://eslint.org/docs/latest/rules/no-dupe-else-if + */ + 'no-dupe-else-if': 'error', + + /** + * @description Disallow duplicate keys in object literals + * @link https://eslint.org/docs/latest/rules/no-dupe-keys + */ + 'no-dupe-keys': 'error', + + /** + * @description Disallow duplicate case labels + * @link https://eslint.org/docs/latest/rules/no-duplicate-case + */ + 'no-duplicate-case': 'error', + + /** + * @description Disallow duplicate module imports + * @link https://eslint.org/docs/latest/rules/no-duplicate-imports + */ + 'no-duplicate-imports': 'off', + + /** + * @description Disallow empty character classes in regular expressions + * @link https://eslint.org/docs/latest/rules/no-empty-character-class + */ + 'no-empty-character-class': 'error', + + /** + * @description Disallow empty destructuring patterns + * @link https://eslint.org/docs/latest/rules/no-empty-pattern + */ + 'no-empty-pattern': 'error', + + /** + * @description Disallow reassigning exceptions in `catch` clauses + * @link https://eslint.org/docs/latest/rules/no-ex-assign + */ + 'no-ex-assign': 'error', + + /** + * @description Disallow fallthrough of `case` statements + * @link https://eslint.org/docs/latest/rules/no-fallthrough + */ + 'no-fallthrough': 'error', + + /** + * @description Disallow reassigning `function` declarations + * @link https://eslint.org/docs/latest/rules/no-func-assign + */ + 'no-func-assign': 'error', + + /** + * @description Disallow assigning to imported bindings + * @link https://eslint.org/docs/latest/rules/no-import-assign + */ + 'no-import-assign': 'error', + + /** + * @description Disallow variable or `function` declarations in nested blocks + * @link https://eslint.org/docs/latest/rules/no-inner-declarations + */ + 'no-inner-declarations': 'error', + + /** + * @description Disallow invalid regular expression strings in `RegExp` constructors + * @link https://eslint.org/docs/latest/rules/no-invalid-regexp + */ + 'no-invalid-regexp': 'error', + + /** + * @description Disallow irregular whitespace + * @link https://eslint.org/docs/latest/rules/no-irregular-whitespace + */ + 'no-irregular-whitespace': 'error', + + /** + * @description Disallow literal numbers that lose precision + * @link https://eslint.org/docs/latest/rules/no-loss-of-precision + */ + 'no-loss-of-precision': 'error', + + /** + * @description Disallow characters which are made with multiple code points in character class syntax + * @link https://eslint.org/docs/latest/rules/no-misleading-character-class + */ + 'no-misleading-character-class': 'error', + + /** + * @description Disallow `new` operators with the `Symbol` object + * @link https://eslint.org/docs/latest/rules/no-new-symbol + */ + 'no-new-symbol': 'error', + + /** + * @description Disallow calling global object properties as functions + * @link https://eslint.org/docs/latest/rules/no-obj-calls + */ + 'no-obj-calls': 'error', + + /** + * @description Disallow returning values from Promise executor functions + * @link https://eslint.org/docs/latest/rules/no-promise-executor-return + */ + 'no-promise-executor-return': 'error', + + /** + * @description Disallow calling some `Object.prototype` methods directly on objects + * @link https://eslint.org/docs/latest/rules/no-prototype-builtins + */ + 'no-prototype-builtins': 'error', + + /** + * @description Disallow assignments where both sides are exactly the same + * @link https://eslint.org/docs/latest/rules/no-self-assign + */ + 'no-self-assign': 'error', + + /** + * @description Disallow comparisons where both sides are exactly the same + * @link https://eslint.org/docs/latest/rules/no-self-compare + */ + 'no-self-compare': 'error', + + /** + * @description Disallow returning values from setters + * @link https://eslint.org/docs/latest/rules/no-setter-return + */ + 'no-setter-return': 'error', + + /** + * @description Disallow sparse arrays + * @link https://eslint.org/docs/latest/rules/no-sparse-arrays + */ + 'no-sparse-arrays': 'error', + + /** + * @description Disallow template literal placeholder syntax in regular strings + * @link https://eslint.org/docs/latest/rules/no-template-curly-in-string + */ + 'no-template-curly-in-string': 'error', + + /** + * @description Disallow `this`/`super` before calling `super()` in constructors + * @link https://eslint.org/docs/latest/rules/no-this-before-super + */ + 'no-this-before-super': 'error', + + /** + * @description Disallow the use of undeclared variables unless mentioned in *global* comments + * @link https://eslint.org/docs/latest/rules/no-undef + */ + 'no-undef': 'error', + + /** + * @description Disallow confusing multiline expressions + * @link https://eslint.org/docs/latest/rules/no-unexpected-multiline + */ + 'no-unexpected-multiline': 'error', + + /** + * @description Disallow unmodified loop conditions + * @link https://eslint.org/docs/latest/rules/no-unmodified-loop-condition + */ + 'no-unmodified-loop-condition': 'error', + + /** + * @description Disallow unreachable code after `return`, `throw`, `continue`, and `break` statements + * @link https://eslint.org/docs/latest/rules/no-unreachable + */ + 'no-unreachable': 'error', + + /** + * @description Disallow loops with a body that allows only one iteration + * @link https://eslint.org/docs/latest/rules/no-unreachable-loop + */ + 'no-unreachable-loop': 'error', + + /** + * @description Disallow control flow statements in `finally` blocks + * @link https://eslint.org/docs/latest/rules/no-unsafe-finally + */ + 'no-unsafe-finally': 'error', + + /** + * @description Disallow negating the left operand of relational operators + * @link https://eslint.org/docs/latest/rules/no-unsafe-negation + */ + 'no-unsafe-negation': 'error', + + /** + * @description Disallow use of optional chaining in contexts where the `undefined` value is not allowed + * @link https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining + */ + 'no-unsafe-optional-chaining': 'error', + + /** + * @description Disallow unused private class members + * @link https://eslint.org/docs/latest/rules/no-unused-private-class-members + */ + 'no-unused-private-class-members': 'error', + + /** + * @description Disallow unused variables + * @link https://eslint.org/docs/latest/rules/no-unused-vars + */ + 'no-unused-vars': 'error', + + /** + * @description Disallow the use of variables before they are defined + * @link https://eslint.org/docs/latest/rules/no-use-before-define + */ + 'no-use-before-define': ['error', { functions: false, classes: false, variables: true }], + + /** + * @description Disallow useless backreferences in regular expressions + * @link https://eslint.org/docs/latest/rules/no-useless-backreference + */ + 'no-useless-backreference': 'error', + + /** + * @description Disallow assignments that can lead to race conditions due to usage of `await` or `yield` + * @link https://eslint.org/docs/latest/rules/require-atomic-updates + */ + 'require-atomic-updates': 'error', + + /** + * @description Require calls to `isNaN()` when checking for `NaN` + * @link https://eslint.org/docs/latest/rules/use-isnan + */ + 'use-isnan': 'error', + + /** + * @description Enforce comparing `typeof` expressions against valid strings + * @link https://eslint.org/docs/latest/rules/valid-typeof + */ + 'valid-typeof': 'error', + + // End + + /* + * Suggestions + * These rules suggest alternate ways of doing things: + */ + + /** + * @description Enforce getter and setter pairs in objects and classes + * @link https://eslint.org/docs/latest/rules/accessor-pairs + */ + 'accessor-pairs': ['error', { setWithoutGet: true, enforceForClassMembers: true }], + + /** + * @description Require braces around arrow function bodies + * @link https://eslint.org/docs/latest/rules/arrow-body-style + */ + 'arrow-body-style': 'error', + + /** + * @description Enforce the use of variables within the scope they are defined + * @link https://eslint.org/docs/latest/rules/block-scoped-var + */ + 'block-scoped-var': 'error', + + /** + * @description Enforce camelcase naming convention + * @link https://eslint.org/docs/latest/rules/camelcase + */ + camelcase: 'off', + + /** + * @description Enforce or disallow capitalization of the first letter of a comment + * @link https://eslint.org/docs/latest/rules/capitalized-comments + */ + 'capitalized-comments': 'off', + + /** + * @description Enforce that class methods utilize `this` + * @link https://eslint.org/docs/latest/rules/class-methods-use-this + */ + 'class-methods-use-this': 'error', + + /** + * @description Enforce a maximum cyclomatic complexity allowed in a program + * @link https://eslint.org/docs/latest/rules/complexity + */ + complexity: 'error', + + /** + * @description Require `return` statements to either always or never specify values + * @link https://eslint.org/docs/latest/rules/consistent-return + */ + 'consistent-return': 'error', + + /** + * @description Enforce consistent naming when capturing the current execution context + * @link https://eslint.org/docs/latest/rules/consistent-this + */ + 'consistent-this': 'error', + + /** + * @description Enforce consistent brace style for all control statements + * @link https://eslint.org/docs/latest/rules/curly + */ + curly: 'error', + + /** + * @description Require `default` cases in `switch` statements + * @link https://eslint.org/docs/latest/rules/default-case + */ + 'default-case': 'error', + + /** + * @description Enforce default clauses in switch statements to be last + * @link https://eslint.org/docs/latest/rules/default-case-last + */ + 'default-case-last': 'error', + + /** + * @description Enforce default parameters to be last + * @link https://eslint.org/docs/latest/rules/default-param-last + */ + 'default-param-last': 'error', + + /** + * @description Enforce dot notation whenever possible + * @link https://eslint.org/docs/latest/rules/dot-notation + */ + 'dot-notation': 'error', + + /** + * @description Require the use of `===` and `!==` + * @link https://eslint.org/docs/latest/rules/eqeqeq + */ + eqeqeq: 'error', + + /** + * @description Require function names to match the name of the variable or property to which they are assigned + * @link https://eslint.org/docs/latest/rules/func-name-matching + */ + 'func-name-matching': 'error', + + /** + * @description Require or disallow named `function` expressions + * @link https://eslint.org/docs/latest/rules/func-names + */ + 'func-names': 'error', + + /** + * @description Enforce the consistent use of either `function` declarations or expressions + * @link https://eslint.org/docs/latest/rules/func-style + */ + 'func-style': 'off', + + /** + * @description Require grouped accessor pairs in object literals and classes + * @link https://eslint.org/docs/latest/rules/grouped-accessor-pairs + */ + 'grouped-accessor-pairs': 'error', + + /** + * @description Require `for-in` loops to include an `if` statement + * @link https://eslint.org/docs/latest/rules/guard-for-in + */ + 'guard-for-in': 'error', + + /** + * @description Disallow specified identifiers + * @link https://eslint.org/docs/latest/rules/id-denylist + */ + 'id-denylist': 'error', + + /** + * @description Enforce minimum and maximum identifier lengths + * @link https://eslint.org/docs/latest/rules/id-length + */ + 'id-length': 'off', + + /** + * @description Require identifiers to match a specified regular expression + * @link https://eslint.org/docs/latest/rules/id-match + */ + 'id-match': 'error', + + /** + * @description Require or disallow initialization in variable declarations + * @link https://eslint.org/docs/latest/rules/init-declarations + */ + 'init-declarations': 'off', + + /** + * @description Enforce a maximum number of classes per file + * @link https://eslint.org/docs/latest/rules/max-classes-per-file + */ + 'max-classes-per-file': 'off', + + /** + * @description Enforce a maximum depth that blocks can be nested + * @link https://eslint.org/docs/latest/rules/max-depth + */ + 'max-depth': 'error', + + /** + * @description Enforce a maximum number of lines per file + * @link https://eslint.org/docs/latest/rules/max-lines + */ + 'max-lines': 'off', + + /** + * @description Enforce a maximum number of lines of code in a function + * @link https://eslint.org/docs/latest/rules/max-lines-per-function + */ + 'max-lines-per-function': 'off', + + /** + * @description Enforce a maximum depth that callbacks can be nested + * @link https://eslint.org/docs/latest/rules/max-nested-callbacks + */ + 'max-nested-callbacks': 'error', + + /** + * @description Enforce a maximum number of parameters in function definitions + * @link https://eslint.org/docs/latest/rules/max-params + */ + 'max-params': 'error', + + /** + * @description Enforce a maximum number of statements allowed in function blocks + * @link https://eslint.org/docs/latest/rules/max-statements + */ + 'max-statements': 'off', + + /** + * @description Enforce a particular style for multiline comments + * @link https://eslint.org/docs/latest/rules/multiline-comment-style + */ + 'multiline-comment-style': 'off', + + /** + * @description Require constructor names to begin with a capital letter + * @link https://eslint.org/docs/latest/rules/new-cap + */ + 'new-cap': ['error', { newIsCap: true, capIsNew: false, properties: true }], + + /** + * @description Disallow the use of `alert`, `confirm`, and `prompt` + * @link https://eslint.org/docs/latest/rules/no-alert + */ + 'no-alert': 'error', + + /** + * @description Disallow `Array` constructors + * @link https://eslint.org/docs/latest/rules/no-array-constructor + */ + 'no-array-constructor': 'error', + + /** + * @description Disallow bitwise operators + * @link https://eslint.org/docs/latest/rules/no-bitwise + */ + 'no-bitwise': 'error', + + /** + * @description Disallow the use of `arguments.caller` or `arguments.callee` + * @link https://eslint.org/docs/latest/rules/no-caller + */ + 'no-caller': 'error', + + /** + * @description Disallow lexical declarations in case clauses + * @link https://eslint.org/docs/latest/rules/no-case-declarations + */ + 'no-case-declarations': 'error', + + /** + * @description Disallow arrow functions where they could be confused with comparisons + * @link https://eslint.org/docs/latest/rules/no-confusing-arrow + */ + 'no-confusing-arrow': 'error', + + /** + * @description Disallow the use of `console` + * @link https://eslint.org/docs/latest/rules/no-console + */ + 'no-console': 'warn', + + /** + * @description Disallow `continue` statements + * @link https://eslint.org/docs/latest/rules/no-continue + */ + 'no-continue': 'error', + + /** + * @description Disallow deleting variables + * @link https://eslint.org/docs/latest/rules/no-delete-var + */ + 'no-delete-var': 'error', + + /** + * @description Disallow division operators explicitly at the beginning of regular expressions + * @link https://eslint.org/docs/latest/rules/no-div-regex + */ + 'no-div-regex': 'error', + + /** + * @description Disallow `else` blocks after `return` statements in `if` statements + * @link https://eslint.org/docs/latest/rules/no-else-return + */ + 'no-else-return': 'error', + + /** + * @description Disallow empty block statements + * @link https://eslint.org/docs/latest/rules/no-empty + */ + 'no-empty': [ + 'error', + { + allowEmptyCatch: true + } + ], + + /** + * @description Disallow empty functions + * @link https://eslint.org/docs/latest/rules/no-empty-function + */ + 'no-empty-function': 'error', + + /** + * @description Disallow `null` comparisons without type-checking operators + * @link https://eslint.org/docs/latest/rules/no-eq-null + */ + 'no-eq-null': 'error', + + /** + * @description Disallow the use of `eval()` + * @link https://eslint.org/docs/latest/rules/no-eval + */ + 'no-eval': 'error', + + /** + * @description Disallow extending native types + * @link https://eslint.org/docs/latest/rules/no-extend-native + */ + 'no-extend-native': 'error', + + /** + * @description Disallow unnecessary calls to `.bind()` + * @link https://eslint.org/docs/latest/rules/no-extra-bind + */ + 'no-extra-bind': 'error', + + /** + * @description Disallow unnecessary boolean casts + * @link https://eslint.org/docs/latest/rules/no-extra-boolean-cast + */ + 'no-extra-boolean-cast': 'error', + + /** + * @description Disallow unnecessary labels + * @link https://eslint.org/docs/latest/rules/no-extra-label + */ + 'no-extra-label': 'error', + + /** + * @description Disallow unnecessary semicolons + * @link https://eslint.org/docs/latest/rules/no-extra-semi + */ + 'no-extra-semi': 'error', + + /** + * @description Disallow leading or trailing decimal points in numeric literals + * @link https://eslint.org/docs/latest/rules/no-floating-decimal + */ + 'no-floating-decimal': 'error', + + /** + * @description Disallow assignments to native objects or read-only global variables + * @link https://eslint.org/docs/latest/rules/no-global-assign + */ + 'no-global-assign': 'error', + + /** + * @description Disallow shorthand type conversions + * @link https://eslint.org/docs/latest/rules/no-implicit-coercion + */ + 'no-implicit-coercion': 'error', + + /** + * @description Disallow declarations in the global scope + * @link https://eslint.org/docs/latest/rules/no-implicit-globals + */ + 'no-implicit-globals': 'error', + + /** + * @description Disallow the use of `eval()`-like methods + * @link https://eslint.org/docs/latest/rules/no-implied-eval + */ + 'no-implied-eval': 'error', + + /** + * @description Disallow inline comments after code + * @link https://eslint.org/docs/latest/rules/no-inline-comments + */ + 'no-inline-comments': 'off', + + /** + * @description Disallow use of `this` in contexts where the value of `this` is `undefined` + * @link https://eslint.org/docs/latest/rules/no-invalid-this + */ + 'no-invalid-this': 'error', + + /** + * @description Disallow the use of the `__iterator__` property + * @link https://eslint.org/docs/latest/rules/no-iterator + */ + 'no-iterator': 'error', + + /** + * @description Disallow labels that share a name with a variable + * @link https://eslint.org/docs/latest/rules/no-label-var + */ + 'no-label-var': 'error', + + /** + * @description Disallow labeled statements + * @link https://eslint.org/docs/latest/rules/no-labels + */ + 'no-labels': 'error', + + /** + * @description Disallow unnecessary nested blocks + * @link https://eslint.org/docs/latest/rules/no-lone-blocks + */ + 'no-lone-blocks': 'error', + + /** + * @description Disallow `if` statements as the only statement in `else` blocks + * @link https://eslint.org/docs/latest/rules/no-lonely-if + */ + 'no-lonely-if': 'error', + + /** + * @description Disallow function declarations that contain unsafe references inside loop statements + * @link https://eslint.org/docs/latest/rules/no-loop-func + */ + 'no-loop-func': 'error', + + /** + * @description Disallow magic numbers + * @link https://eslint.org/docs/latest/rules/no-magic-numbers + */ + 'no-magic-numbers': 'off', + + /** + * @description Disallow mixed binary operators + * @link https://eslint.org/docs/latest/rules/no-mixed-operators + */ + 'no-mixed-operators': [ + 'error', + { + groups: [ + ['+', '-', '*', '/', '%', '**'], + ['&', '|', '^', '~', '<<', '>>', '>>>'], + ['==', '!=', '===', '!==', '>', '>=', '<', '<='], + ['&&', '||'], + ['in', 'instanceof'] + ], + allowSamePrecedence: true + } + ], + + /** + * @description Disallow use of chained assignment expressions + * @link https://eslint.org/docs/latest/rules/no-multi-assign + */ + 'no-multi-assign': 'error', + + /** + * @description Disallow multiline strings + * @link https://eslint.org/docs/latest/rules/no-multi-str + */ + 'no-multi-str': 'error', + + /** + * @description Disallow negated conditions + * @link https://eslint.org/docs/latest/rules/no-negated-condition + */ + 'no-negated-condition': 'off', + + /** + * @description Disallow nested ternary expressions + * @link https://eslint.org/docs/latest/rules/no-nested-ternary + */ + 'no-nested-ternary': 'error', + + /** + * @description Disallow `new` operators outside of assignments or comparisons + * @link https://eslint.org/docs/latest/rules/no-new + */ + 'no-new': 'error', + + /** + * @description Disallow `new` operators with the `Function` object + * @link https://eslint.org/docs/latest/rules/no-new-func + */ + 'no-new-func': 'error', + + /** + * @description Disallow `Object` constructors + * @link https://eslint.org/docs/latest/rules/no-new-object + */ + 'no-new-object': 'error', + + /** + * @description Disallow `new` operators with the `String`, `Number`, and `Boolean` objects + * @link https://eslint.org/docs/latest/rules/no-new-wrappers + */ + 'no-new-wrappers': 'error', + + /** + * @description Disallow `\\8` and `\\9` escape sequences in string literals + * @link https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape + */ + 'no-nonoctal-decimal-escape': 'error', + + /** + * @description Disallow octal literals + * @link https://eslint.org/docs/latest/rules/no-octal + */ + 'no-octal': 'error', + + /** + * @description Disallow octal escape sequences in string literals + * @link https://eslint.org/docs/latest/rules/no-octal-escape + */ + 'no-octal-escape': 'error', + + /** + * @description Disallow reassigning `function` parameters + * @link https://eslint.org/docs/latest/rules/no-param-reassign + */ + 'no-param-reassign': 'error', + + /** + * @description Disallow the unary operators `++` and `--` + * @link https://eslint.org/docs/latest/rules/no-plusplus + */ + 'no-plusplus': 'error', + + /** + * @description Disallow the use of the `__proto__` property + * @link https://eslint.org/docs/latest/rules/no-proto + */ + 'no-proto': 'error', + + /** + * @description Disallow variable redeclaration + * @link https://eslint.org/docs/latest/rules/no-redeclare + */ + 'no-redeclare': 'error', + + /** + * @description Disallow multiple spaces in regular expressions + * @link https://eslint.org/docs/latest/rules/no-regex-spaces + */ + 'no-regex-spaces': 'error', + + /** + * @description Disallow specified names in exports + * @link https://eslint.org/docs/latest/rules/no-restricted-exports + */ + 'no-restricted-exports': 'error', + + /** + * @description Disallow specified global variables + * @link https://eslint.org/docs/latest/rules/no-restricted-globals + */ + 'no-restricted-globals': 'error', + + /** + * @description Disallow specified modules when loaded by `import` + * @link https://eslint.org/docs/latest/rules/no-restricted-imports + */ + 'no-restricted-imports': 'error', + + /** + * @description Disallow certain properties on certain objects + * @link https://eslint.org/docs/latest/rules/no-restricted-properties + */ + 'no-restricted-properties': 'error', + + /** + * @description Disallow specified syntax + * @link https://eslint.org/docs/latest/rules/no-restricted-syntax + */ + 'no-restricted-syntax': ['error', 'DebuggerStatement', 'LabeledStatement', 'WithStatement'], + + /** + * @description Disallow assignment operators in `return` statements + * @link https://eslint.org/docs/latest/rules/no-return-assign + */ + 'no-return-assign': 'error', + + /** + * @description Disallow unnecessary `return await` + * @link https://eslint.org/docs/latest/rules/no-return-await + */ + 'no-return-await': 'error', + + /** + * @description Disallow `javascript:` urls + * @link https://eslint.org/docs/latest/rules/no-script-url + */ + 'no-script-url': 'error', + + /** + * @description Disallow comma operators + * @link https://eslint.org/docs/latest/rules/no-sequences + */ + 'no-sequences': 'error', + + /** + * @description Disallow variable declarations from shadowing variables declared in the outer scope + * @link https://eslint.org/docs/latest/rules/no-shadow + */ + 'no-shadow': 'error', + + /** + * @description Disallow identifiers from shadowing restricted names + * @link https://eslint.org/docs/latest/rules/no-shadow-restricted-names + */ + 'no-shadow-restricted-names': 'error', + + /** + * @description Disallow ternary operators + * @link https://eslint.org/docs/latest/rules/no-ternary + */ + 'no-ternary': 'off', + + /** + * @description Disallow throwing literals as exceptions + * @link https://eslint.org/docs/latest/rules/no-throw-literal + */ + 'no-throw-literal': 'error', + + /** + * @description Disallow initializing variables to `undefined` + * @link https://eslint.org/docs/latest/rules/no-undef-init + */ + 'no-undef-init': 'error', + + /** + * @description Disallow the use of `undefined` as an identifier + * @link https://eslint.org/docs/latest/rules/no-undefined + */ + 'no-undefined': 'off', + + /** + * @description Disallow dangling underscores in identifiers + * @link https://eslint.org/docs/latest/rules/no-underscore-dangle + */ + 'no-underscore-dangle': 'error', + + /** + * @description Disallow ternary operators when simpler alternatives exist + * @link https://eslint.org/docs/latest/rules/no-unneeded-ternary + */ + 'no-unneeded-ternary': 'error', + + /** + * @description Disallow unused expressions + * @link https://eslint.org/docs/latest/rules/no-unused-expressions + */ + 'no-unused-expressions': 'error', + + /** + * @description Disallow unused labels + * @link https://eslint.org/docs/latest/rules/no-unused-labels + */ + 'no-unused-labels': 'error', + + /** + * @description Disallow unnecessary calls to `.call()` and `.apply()` + * @link https://eslint.org/docs/latest/rules/no-useless-call + */ + 'no-useless-call': 'error', + + /** + * @description Disallow unnecessary `catch` clauses + * @link https://eslint.org/docs/latest/rules/no-useless-catch + */ + 'no-useless-catch': 'error', + + /** + * @description Disallow unnecessary computed property keys in objects and classes + * @link https://eslint.org/docs/latest/rules/no-useless-computed-key + */ + 'no-useless-computed-key': 'error', + + /** + * @description Disallow unnecessary concatenation of literals or template literals + * @link https://eslint.org/docs/latest/rules/no-useless-concat + */ + 'no-useless-concat': 'error', + + /** + * @description Disallow unnecessary constructors + * @link https://eslint.org/docs/latest/rules/no-useless-constructor + */ + 'no-useless-constructor': 'error', + + /** + * @description Disallow unnecessary escape characters + * @link https://eslint.org/docs/latest/rules/no-useless-escape + */ + 'no-useless-escape': 'error', + + /** + * @description Disallow renaming import, export, and destructured assignments to the same name + * @link https://eslint.org/docs/latest/rules/no-useless-rename + */ + 'no-useless-rename': 'error', + + /** + * @description Disallow redundant return statements + * @link https://eslint.org/docs/latest/rules/no-useless-return + */ + 'no-useless-return': 'error', + + /** + * @description Require `let` or `const` instead of `var` + * @link https://eslint.org/docs/latest/rules/no-var + */ + 'no-var': 'error', + + /** + * @description Disallow `void` operators + * @link https://eslint.org/docs/latest/rules/no-void + */ + 'no-void': 'error', + + /** + * @description Disallow specified warning terms in comments + * @link https://eslint.org/docs/latest/rules/no-warning-comments + */ + 'no-warning-comments': 'error', + + /** + * @description Disallow `with` statements + * @link https://eslint.org/docs/latest/rules/no-with + */ + 'no-with': 'error', + + /** + * @description Require or disallow method and property shorthand syntax for object literals + * @link https://eslint.org/docs/latest/rules/object-shorthand + */ + 'object-shorthand': [ + 'error', + 'always', + { + ignoreConstructors: false, + avoidQuotes: true + } + ], + + /** + * @description Enforce variables to be declared either together or separately in functions + * @link https://eslint.org/docs/latest/rules/one-var + */ + 'one-var': ['error', 'never'], + + /** + * @description Require or disallow newlines around variable declarations + * @link https://eslint.org/docs/latest/rules/one-var-declaration-per-line + */ + 'one-var-declaration-per-line': 'error', + + /** + * @description Require or disallow assignment operator shorthand where possible + * @link https://eslint.org/docs/latest/rules/operator-assignment + */ + 'operator-assignment': 'error', + + /** + * @description Require using arrow functions for callbacks + * @link https://eslint.org/docs/latest/rules/prefer-arrow-callback + */ + 'prefer-arrow-callback': 'error', + + /** + * @description Require `const` declarations for variables that are never reassigned after declared + * @link https://eslint.org/docs/latest/rules/prefer-const + */ + 'prefer-const': 'error', + + /** + * @description Require destructuring from arrays and/or objects + * @link https://eslint.org/docs/latest/rules/prefer-destructuring + */ + 'prefer-destructuring': 'off', + + /** + * @description Disallow the use of `Math.pow` in favor of the `**` operator + * @link https://eslint.org/docs/latest/rules/prefer-exponentiation-operator + */ + 'prefer-exponentiation-operator': 'error', + + /** + * @description Enforce using named capture group in regular expression + * @link https://eslint.org/docs/latest/rules/prefer-named-capture-group + */ + 'prefer-named-capture-group': 'off', + + /** + * @description Disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals + * @link https://eslint.org/docs/latest/rules/prefer-numeric-literals + */ + 'prefer-numeric-literals': 'error', + + /** + * @description Disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()` + * @link https://eslint.org/docs/latest/rules/prefer-object-has-own + */ + 'prefer-object-has-own': 'error', + + /** + * @description Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead. + * @link https://eslint.org/docs/latest/rules/prefer-object-spread + */ + 'prefer-object-spread': 'error', + + /** + * @description Require using Error objects as Promise rejection reasons + * @link https://eslint.org/docs/latest/rules/prefer-promise-reject-errors + */ + 'prefer-promise-reject-errors': 'error', + + /** + * @description Disallow use of the `RegExp` constructor in favor of regular expression literals + * @link https://eslint.org/docs/latest/rules/prefer-regex-literals + */ + 'prefer-regex-literals': 'error', + + /** + * @description Require rest parameters instead of `arguments` + * @link https://eslint.org/docs/latest/rules/prefer-rest-params + */ + 'prefer-rest-params': 'error', + + /** + * @description Require spread operators instead of `.apply()` + * @link https://eslint.org/docs/latest/rules/prefer-spread + */ + 'prefer-spread': 'error', + + /** + * @description Require template literals instead of string concatenation + * @link https://eslint.org/docs/latest/rules/prefer-template + */ + 'prefer-template': 'error', + + /** + * @description Require quotes around object literal property names + * @link https://eslint.org/docs/latest/rules/quote-props + */ + 'quote-props': 'error', + + /** + * @description Enforce the consistent use of the radix argument when using `parseInt()` + * @link https://eslint.org/docs/latest/rules/radix + */ + radix: 'error', + + /** + * @description Disallow async functions which have no `await` expression + * @link https://eslint.org/docs/latest/rules/require-await + */ + 'require-await': 'off', + + /** + * @description Enforce the use of `u` flag on RegExp + * @link https://eslint.org/docs/latest/rules/require-unicode-regexp + */ + 'require-unicode-regexp': 'off', + + /** + * @description Require generator functions to contain `yield` + * @link https://eslint.org/docs/latest/rules/require-yield + */ + 'require-yield': 'error', + + /** + * @description Enforce sorted import declarations within modules + * @link https://eslint.org/docs/latest/rules/sort-imports + */ + 'sort-imports': 'off', + + /** + * @description Require object keys to be sorted + * @link https://eslint.org/docs/latest/rules/sort-keys + */ + 'sort-keys': 'off', + + /** + * @description Require variables within the same declaration block to be sorted + * @link https://eslint.org/docs/latest/rules/sort-vars + */ + 'sort-vars': 'error', + + /** + * @description Enforce consistent spacing after the `//` or `/*` in a comment + * @link https://eslint.org/docs/latest/rules/spaced-comment + */ + 'spaced-comment': [ + 'error', + 'always', + { + line: { markers: ['*package', '!', '/', ',', '='] }, + block: { + balanced: true, + markers: ['*package', '!', ',', ':', '::', 'flow-include'], + exceptions: ['*'] + } + } + ], + + /** + * @description Require or disallow strict mode directives + * @link https://eslint.org/docs/latest/rules/strict + */ + strict: 'error', + + /** + * @description Require symbol descriptions + * @link https://eslint.org/docs/latest/rules/symbol-description + */ + 'symbol-description': 'error', + + /** + * @description Require `var` declarations be placed at the top of their containing scope + * @link https://eslint.org/docs/latest/rules/vars-on-top + */ + 'vars-on-top': 'error', + + /** + * @description Require or disallow \"Yoda\" conditions + * @link https://eslint.org/docs/latest/rules/yoda + */ + yoda: 'error', + + // End + + /* + * Layout & Formatting + * These rules care about how the code looks rather than how it executes: + */ + + /** + * @description Enforce linebreaks after opening and before closing array brackets + * @link https://eslint.org/docs/latest/rules/array-bracket-newline + */ + 'array-bracket-newline': 'error', + + /** + * @description Enforce consistent spacing inside array brackets + * @link https://eslint.org/docs/latest/rules/array-bracket-spacing + */ + 'array-bracket-spacing': 'error', + + /** + * @description Enforce line breaks after each array element + * @link https://eslint.org/docs/latest/rules/array-element-newline + */ + 'array-element-newline': 'error', + + /** + * @description Require parentheses around arrow function arguments + * @link https://eslint.org/docs/latest/rules/arrow-parens + */ + 'arrow-parens': ['error', 'as-needed'], + + /** + * @description Enforce consistent spacing before and after the arrow in arrow functions + * @link https://eslint.org/docs/latest/rules/arrow-spacing + */ + 'arrow-spacing': 'error', + + /** + * @description Disallow or enforce spaces inside of blocks after opening block and before closing block + * @link https://eslint.org/docs/latest/rules/block-spacing + */ + 'block-spacing': 'error', + + /** + * @description Enforce consistent brace style for blocks + * @link https://eslint.org/docs/latest/rules/brace-style + */ + 'brace-style': 'error', + + /** + * @description Require or disallow trailing commas + * @link https://eslint.org/docs/latest/rules/comma-dangle + */ + 'comma-dangle': 'error', + + /** + * @description Enforce consistent spacing before and after commas + * @link https://eslint.org/docs/latest/rules/comma-spacing + */ + 'comma-spacing': 'error', + + /** + * @description Enforce consistent comma style + * @link https://eslint.org/docs/latest/rules/comma-style + */ + 'comma-style': 'error', + + /** + * @description Enforce consistent spacing inside computed property brackets + * @link https://eslint.org/docs/latest/rules/computed-property-spacing + */ + 'computed-property-spacing': 'error', + + /** + * @description Enforce consistent newlines before and after dots + * @link https://eslint.org/docs/latest/rules/dot-location + */ + 'dot-location': ['error', 'property'], + + /** + * @description Require or disallow newline at the end of files + * @link https://eslint.org/docs/latest/rules/eol-last + */ + 'eol-last': 'error', + + /** + * @description Require or disallow spacing between function identifiers and their invocations + * @link https://eslint.org/docs/latest/rules/func-call-spacing + */ + 'func-call-spacing': 'error', + + /** + * @description Enforce line breaks between arguments of a function call + * @link https://eslint.org/docs/latest/rules/function-call-argument-newline + */ + 'function-call-argument-newline': 'error', + + /** + * @description Enforce consistent line breaks inside function parentheses + * @link https://eslint.org/docs/latest/rules/function-paren-newline + */ + 'function-paren-newline': 'error', + + /** + * @description Enforce consistent spacing around `*` operators in generator functions + * @link https://eslint.org/docs/latest/rules/generator-star-spacing + */ + 'generator-star-spacing': 'error', + + /** + * @description Enforce the location of arrow function bodies + * @link https://eslint.org/docs/latest/rules/implicit-arrow-linebreak + */ + 'implicit-arrow-linebreak': 'error', + + /** + * @description Enforce consistent indentation + * @link https://eslint.org/docs/latest/rules/indent + */ + indent: [ + 'error', + 2, + { + SwitchCase: 1, + VariableDeclarator: 1, + outerIIFEBody: 1, + MemberExpression: 1, + FunctionDeclaration: { parameters: 1, body: 1 }, + FunctionExpression: { parameters: 1, body: 1 }, + CallExpression: { arguments: 1 }, + ArrayExpression: 1, + ObjectExpression: 1, + ImportDeclaration: 1, + flatTernaryExpressions: false, + ignoreComments: false, + ignoredNodes: [ + 'TemplateLiteral *', + 'JSXElement', + 'JSXElement > *', + 'JSXAttribute', + 'JSXIdentifier', + 'JSXNamespacedName', + 'JSXMemberExpression', + 'JSXSpreadAttribute', + 'JSXExpressionContainer', + 'JSXOpeningElement', + 'JSXClosingElement', + 'JSXFragment', + 'JSXOpeningFragment', + 'JSXClosingFragment', + 'JSXText', + 'JSXEmptyExpression', + 'JSXSpreadChild' + ], + offsetTernaryExpressions: true + } + ], + + /** + * @description Enforce the consistent use of either double or single quotes in JSX attributes + * @link https://eslint.org/docs/latest/rules/jsx-quotes + */ + 'jsx-quotes': 'error', + + /** + * @description Enforce consistent spacing between keys and values in object literal properties + * @link https://eslint.org/docs/latest/rules/key-spacing + */ + 'key-spacing': 'error', + + /** + * @description Enforce consistent spacing before and after keywords + * @link https://eslint.org/docs/latest/rules/keyword-spacing + */ + 'keyword-spacing': 'error', + + /** + * @description Enforce position of line comments + * @link https://eslint.org/docs/latest/rules/line-comment-position + */ + 'line-comment-position': 'off', + + /** + * @description Enforce consistent linebreak style + * @link https://eslint.org/docs/latest/rules/linebreak-style + */ + 'linebreak-style': 'error', + + /** + * @description Require empty lines around comments + * @link https://eslint.org/docs/latest/rules/lines-around-comment + */ + 'lines-around-comment': 'error', + + /** + * @description Require or disallow an empty line between class members + * @link https://eslint.org/docs/latest/rules/lines-between-class-members + */ + 'lines-between-class-members': 'error', + + /** + * @description Enforce a maximum line length + * @link https://eslint.org/docs/latest/rules/max-len + */ + 'max-len': 'error', + + /** + * @description Enforce a maximum number of statements allowed per line + * @link https://eslint.org/docs/latest/rules/max-statements-per-line + */ + 'max-statements-per-line': 'off', + + /** + * @description Enforce newlines between operands of ternary expressions + * @link https://eslint.org/docs/latest/rules/multiline-ternary + */ + 'multiline-ternary': ['error', 'always-multiline'], + + /** + * @description Enforce or disallow parentheses when invoking a constructor with no arguments + * @link https://eslint.org/docs/latest/rules/new-parens + */ + 'new-parens': 'error', + + /** + * @description Require a newline after each call in a method chain + * @link https://eslint.org/docs/latest/rules/newline-per-chained-call + */ + 'newline-per-chained-call': 'error', + + /** + * @description Disallow unnecessary parentheses + * @link https://eslint.org/docs/latest/rules/no-extra-parens + */ + 'no-extra-parens': 'error', + + /** + * @description Disallow mixed spaces and tabs for indentation + * @link https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs + */ + 'no-mixed-spaces-and-tabs': 'error', + + /** + * @description Disallow multiple spaces + * @link https://eslint.org/docs/latest/rules/no-multi-spaces + */ + 'no-multi-spaces': 'error', + + /** + * @description Disallow multiple empty lines + * @link https://eslint.org/docs/latest/rules/no-multiple-empty-lines + */ + 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }], + + /** + * @description Disallow all tabs + * @link https://eslint.org/docs/latest/rules/no-tabs + */ + 'no-tabs': 'error', + + /** + * @description Disallow trailing whitespace at the end of lines + * @link https://eslint.org/docs/latest/rules/no-trailing-spaces + */ + 'no-trailing-spaces': 'error', + + /** + * @description Disallow whitespace before properties + * @link https://eslint.org/docs/latest/rules/no-whitespace-before-property + */ + 'no-whitespace-before-property': 'error', + + /** + * @description Enforce the location of single-line statements + * @link https://eslint.org/docs/latest/rules/nonblock-statement-body-position + */ + 'nonblock-statement-body-position': 'error', + + /** + * @description Enforce consistent line breaks after opening and before closing braces + * @link https://eslint.org/docs/latest/rules/object-curly-newline + */ + 'object-curly-newline': 'error', + + /** + * @description Enforce consistent spacing inside braces + * @link https://eslint.org/docs/latest/rules/object-curly-spacing + */ + 'object-curly-spacing': 'error', + + /** + * @description Enforce placing object properties on separate lines + * @link https://eslint.org/docs/latest/rules/object-property-newline + */ + 'object-property-newline': 'error', + + /** + * @description Enforce consistent linebreak style for operators + * @link https://eslint.org/docs/latest/rules/operator-linebreak + */ + 'operator-linebreak': 'error', + + /** + * @description Require or disallow padding within blocks + * @link https://eslint.org/docs/latest/rules/padded-blocks + */ + 'padded-blocks': 'error', + + /** + * @description Require or disallow padding lines between statements + * @link https://eslint.org/docs/latest/rules/padding-line-between-statements + */ + 'padding-line-between-statements': 'error', + + /** + * @description Enforce the consistent use of either backticks, double, or single quotes + * @link https://eslint.org/docs/latest/rules/quotes + */ + quotes: 'error', + + /** + * @description Enforce spacing between rest and spread operators and their expressions + * @link https://eslint.org/docs/latest/rules/rest-spread-spacing + */ + 'rest-spread-spacing': 'error', + + /** + * @description Require or disallow semicolons instead of ASI + * @link https://eslint.org/docs/latest/rules/semi + */ + semi: 'error', + + /** + * @description Enforce consistent spacing before and after semicolons + * @link https://eslint.org/docs/latest/rules/semi-spacing + */ + 'semi-spacing': 'error', + + /** + * @description Enforce location of semicolons + * @link https://eslint.org/docs/latest/rules/semi-style + */ + 'semi-style': 'error', + + /** + * @description Enforce consistent spacing before blocks + * @link https://eslint.org/docs/latest/rules/space-before-blocks + */ + 'space-before-blocks': 'error', + + /** + * @description Enforce consistent spacing before `function` definition opening parenthesis + * @link https://eslint.org/docs/latest/rules/space-before-function-paren + */ + 'space-before-function-paren': 'error', + + /** + * @description Enforce consistent spacing inside parentheses + * @link https://eslint.org/docs/latest/rules/space-in-parens + */ + 'space-in-parens': 'error', + + /** + * @description Require spacing around infix operators + * @link https://eslint.org/docs/latest/rules/space-infix-ops + */ + 'space-infix-ops': 'error', + + /** + * @description Enforce consistent spacing before or after unary operators + * @link https://eslint.org/docs/latest/rules/space-unary-ops + */ + 'space-unary-ops': 'error', + + /** + * @description Enforce spacing around colons of switch statements + * @link https://eslint.org/docs/latest/rules/switch-colon-spacing + */ + 'switch-colon-spacing': 'error', + + /** + * @description Require or disallow spacing around embedded expressions of template strings + * @link https://eslint.org/docs/latest/rules/template-curly-spacing + */ + 'template-curly-spacing': 'error', + + /** + * @description Require or disallow spacing between template tags and their literals + * @link https://eslint.org/docs/latest/rules/template-tag-spacing + */ + 'template-tag-spacing': 'error', + + /** + * @description Require or disallow Unicode byte order mark (BOM) + * @link https://eslint.org/docs/latest/rules/unicode-bom + */ + 'unicode-bom': 'error', + + /** + * @description Require parentheses around immediate `function` invocations + * @link https://eslint.org/docs/latest/rules/wrap-iife + */ + 'wrap-iife': 'error', + + /** + * @description Require parenthesis around regex literals + * @link https://eslint.org/docs/latest/rules/wrap-regex + */ + 'wrap-regex': 'error', + + /** + * @description Require or disallow spacing around the `*` in `yield*` expressions + * @link https://eslint.org/docs/latest/rules/yield-star-spacing + */ + 'yield-star-spacing': 'error' + } +}; diff --git a/packages/eslint-config/rules/prettier.js b/packages/eslint-config/rules/prettier.js new file mode 100644 index 0000000..41f00d6 --- /dev/null +++ b/packages/eslint-config/rules/prettier.js @@ -0,0 +1,26 @@ +/** + * @type {import('prettier').Options} + */ +module.exports = { + printWidth: 120, + tabWidth: 2, + useTabs: false, + semi: true, + singleQuote: true, + quoteProps: 'as-needed', + jsxSingleQuote: false, + trailingComma: 'none', + bracketSpacing: true, + bracketSameLine: false, + arrowParens: 'avoid', + rangeStart: 0, + rangeEnd: Number.POSITIVE_INFINITY, + requirePragma: false, + insertPragma: false, + proseWrap: 'preserve', + htmlWhitespaceSensitivity: 'ignore', + vueIndentScriptAndStyle: false, + endOfLine: 'lf', + embeddedLanguageFormatting: 'auto', + singleAttributePerLine: false +}; diff --git a/packages/hooks/package.json b/packages/hooks/package.json new file mode 100644 index 0000000..c980ce9 --- /dev/null +++ b/packages/hooks/package.json @@ -0,0 +1,14 @@ +{ + "name": "@sa/hooks", + "version": "1.0.0", + "exports": { + ".": "./src/index.ts" + }, + "typesVersions": { + "*": { + "*": [ + "./src/*" + ] + } + } +} diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts new file mode 100644 index 0000000..fd14530 --- /dev/null +++ b/packages/hooks/src/index.ts @@ -0,0 +1,5 @@ +import useBoolean from './use-boolean'; +import useLoading from './use-loading'; +import useContext from './use-context'; + +export { useBoolean, useLoading, useContext }; diff --git a/packages/hooks/src/use-boolean.ts b/packages/hooks/src/use-boolean.ts new file mode 100644 index 0000000..302bb3c --- /dev/null +++ b/packages/hooks/src/use-boolean.ts @@ -0,0 +1,30 @@ +import { ref } from 'vue'; + +/** + * boolean + * @param initValue init value + */ +export default function useBoolean(initValue = false) { + const bool = ref(initValue); + + function setBool(value: boolean) { + bool.value = value; + } + function setTrue() { + setBool(true); + } + function setFalse() { + setBool(false); + } + function toggle() { + setBool(!bool.value); + } + + return { + bool, + setBool, + setTrue, + setFalse, + toggle + }; +} diff --git a/packages/hooks/src/use-context.ts b/packages/hooks/src/use-context.ts new file mode 100644 index 0000000..4263074 --- /dev/null +++ b/packages/hooks/src/use-context.ts @@ -0,0 +1,108 @@ +import { inject, provide } from 'vue'; +import type { InjectionKey } from 'vue'; + +type ContextFn = () => T; + +/** + * use context + * @param contextName context name + * @param fn context function + * @example + * ```ts + * // there are three vue files: A.vue, B.vue, C.vue, and A.vue is the parent component of B.vue and C.vue + * + * // context.ts + * import { ref } from 'vue'; + * import { useContext } from '@sa/hooks'; + * + * export const { setupStore, useStore } = useContext('demo', () => { + * const count = ref(0); + * + * function increment() { + * count.value++; + * } + * + * function decrement() { + * count.value--; + * } + * + * return { + * count, + * increment, + * decrement + * }; + * }) + * ``` + * + * // A.vue + * ```vue + * + * + * ``` + * // B.vue + * ```vue + * + * + * ``` + * + * // C.vue is same as B.vue + */ +export default function useContext(contextName: string, fn: ContextFn) { + const { useProvide, useInject } = createContext(contextName); + + const context = fn(); + + /** + * setup store in the parent component + */ + function setupStore() { + return useProvide(context); + } + + /** + * use store in the child component + */ + function useStore() { + return useInject(); + } + + return { + setupStore, + useStore + }; +} + +/** + * create context + */ +function createContext(contextName: string) { + const injectKey: InjectionKey = Symbol(contextName); + + function useProvide(context: T) { + provide(injectKey, context); + + return context; + } + + function useInject() { + return inject(injectKey) as T; + } + + return { + useProvide, + useInject + }; +} diff --git a/packages/hooks/src/use-loading.ts b/packages/hooks/src/use-loading.ts new file mode 100644 index 0000000..5d07db4 --- /dev/null +++ b/packages/hooks/src/use-loading.ts @@ -0,0 +1,15 @@ +import useBoolean from './use-boolean'; + +/** + * loading + * @param initValue init value + */ +export default function useLoading(initValue = false) { + const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(initValue); + + return { + loading, + startLoading, + endLoading + }; +} diff --git a/packages/hooks/tsconfig.json b/packages/hooks/tsconfig.json new file mode 100644 index 0000000..d9203c3 --- /dev/null +++ b/packages/hooks/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "module": "ESNext", + "target": "ESNext", + "lib": ["DOM", "ESNext"], + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "jsx": "preserve", + "moduleResolution": "node", + "resolveJsonModule": true, + "noUnusedLocals": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/materials/.eslintrc b/packages/materials/.eslintrc new file mode 100644 index 0000000..38de2a0 --- /dev/null +++ b/packages/materials/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": "sa/vue", + "rules": { + "vue/multi-word-component-names": "off" + } +} diff --git a/packages/materials/package.json b/packages/materials/package.json new file mode 100644 index 0000000..f3e6bbf --- /dev/null +++ b/packages/materials/package.json @@ -0,0 +1,20 @@ +{ + "name": "@sa/materials", + "version": "1.0.0", + "exports": { + ".": "./src/index.ts" + }, + "typesVersions": { + "*": { + "*": [ + "./src/*" + ] + } + }, + "dependencies": { + "@sa/utils": "workspace:*" + }, + "devDependencies": { + "typed-css-modules": "0.8.0" + } +} diff --git a/packages/materials/src/index.ts b/packages/materials/src/index.ts new file mode 100644 index 0000000..7e153d6 --- /dev/null +++ b/packages/materials/src/index.ts @@ -0,0 +1,5 @@ +import AdminLayout from './libs/admin-layout'; +import PageTab from './libs/page-tab'; + +export { AdminLayout, PageTab }; +export * from './types'; diff --git a/packages/materials/src/libs/admin-layout/index.module.css b/packages/materials/src/libs/admin-layout/index.module.css new file mode 100644 index 0000000..e5c8ac8 --- /dev/null +++ b/packages/materials/src/libs/admin-layout/index.module.css @@ -0,0 +1,63 @@ +/* @type */ + +.layout-header, +.layout-header-placement { + height: var(--soy-header-height); +} + +.layout-header { + z-index: var(--soy-header-z-index); +} + +.layout-tab { + top: var(--soy-header-height); + height: var(--soy-tab-height); + z-index: var(--soy-tab-z-index); +} + +.layout-tab-placement { + height: var(--soy-tab-height); +} + +.layout-sider { + width: var(--soy-sider-width); + z-index: var(--soy-sider-z-index); +} + +.layout-mobile-sider { + z-index: var(--soy-sider-z-index); +} + +.layout-mobile-sider-mask { + z-index: var(--soy-mobile-sider-z-index); +} + +.layout-sider_collapsed { + width: var(--soy-sider-collapsed-width); + z-index: var(--soy-sider-z-index); +} + +.layout-footer, +.layout-footer-placement { + height: var(--soy-footer-height); +} + +.layout-footer { + z-index: var(--soy-footer-z-index); +} + +.left-gap { + padding-left: var(--soy-sider-width); +} + +.left-gap_collapsed { + padding-left: var(--soy-sider-collapsed-width); +} + +.sider-padding-top { + padding-top: var(--soy-header-height); +} + +.sider-padding-bottom { + padding-bottom: var(--soy-footer-height); +} diff --git a/packages/materials/src/libs/admin-layout/index.module.css.d.ts b/packages/materials/src/libs/admin-layout/index.module.css.d.ts new file mode 100644 index 0000000..869ed53 --- /dev/null +++ b/packages/materials/src/libs/admin-layout/index.module.css.d.ts @@ -0,0 +1,17 @@ +declare const styles: { + readonly 'layout-header': string; + readonly 'layout-header-placement': string; + readonly 'layout-tab': string; + readonly 'layout-tab-placement': string; + readonly 'layout-sider': string; + readonly 'layout-mobile-sider': string; + readonly 'layout-mobile-sider-mask': string; + readonly 'layout-sider_collapsed': string; + readonly 'layout-footer': string; + readonly 'layout-footer-placement': string; + readonly 'left-gap': string; + readonly 'left-gap_collapsed': string; + readonly 'sider-padding-top': string; + readonly 'sider-padding-bottom': string; +}; +export = styles; diff --git a/packages/materials/src/libs/admin-layout/index.ts b/packages/materials/src/libs/admin-layout/index.ts new file mode 100644 index 0000000..753449a --- /dev/null +++ b/packages/materials/src/libs/admin-layout/index.ts @@ -0,0 +1,3 @@ +import AdminLayout from './index.vue'; + +export default AdminLayout; diff --git a/packages/materials/src/libs/admin-layout/index.vue b/packages/materials/src/libs/admin-layout/index.vue new file mode 100644 index 0000000..13b9e9a --- /dev/null +++ b/packages/materials/src/libs/admin-layout/index.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/packages/materials/src/libs/admin-layout/shared.ts b/packages/materials/src/libs/admin-layout/shared.ts new file mode 100644 index 0000000..05f0ec0 --- /dev/null +++ b/packages/materials/src/libs/admin-layout/shared.ts @@ -0,0 +1,70 @@ +import type { AdminLayoutProps, LayoutCssVarsProps, LayoutCssVars } from '../../types'; + +/** + * the id of the scroll element of the layout + */ +export const LAYOUT_SCROLL_EL_ID = '__SCROLL_EL_ID__'; + +/** + * the max z-index of the layout + */ +export const LAYOUT_MAX_Z_INDEX = 100; + +/** + * create layout css vars by css vars props + * @param props css vars props + */ +function createLayoutCssVarsByCssVarsProps(props: LayoutCssVarsProps) { + const cssVars: LayoutCssVars = { + '--soy-header-height': `${props.headerHeight}px`, + '--soy-header-z-index': props.headerZIndex, + '--soy-tab-height': `${props.tabHeight}px`, + '--soy-tab-z-index': props.tabZIndex, + '--soy-sider-width': `${props.siderWidth}px`, + '--soy-sider-collapsed-width': `${props.siderCollapsedWidth}px`, + '--soy-sider-z-index': props.siderZIndex, + '--soy-mobile-sider-z-index': props.mobileSiderZIndex, + '--soy-footer-height': `${props.footerHeight}px`, + '--soy-footer-z-index': props.footerZIndex + }; + + return cssVars; +} + +/** + * create layout css vars + * @param props + */ +export function createLayoutCssVars(props: AdminLayoutProps) { + const { + mode, + isMobile, + maxZIndex = LAYOUT_MAX_Z_INDEX, + headerHeight, + tabHeight, + siderWidth, + siderCollapsedWidth, + footerHeight + } = props; + + const headerZIndex = maxZIndex - 3; + const tabZIndex = maxZIndex - 5; + const siderZIndex = mode === 'vertical' || isMobile ? maxZIndex - 1 : maxZIndex - 4; + const mobileSiderZIndex = isMobile ? maxZIndex - 2 : 0; + const footerZIndex = maxZIndex - 5; + + const cssProps: LayoutCssVarsProps = { + headerHeight, + headerZIndex, + tabHeight, + tabZIndex, + siderWidth, + siderZIndex, + mobileSiderZIndex, + siderCollapsedWidth, + footerHeight, + footerZIndex + }; + + return createLayoutCssVarsByCssVarsProps(cssProps); +} diff --git a/packages/materials/src/libs/page-tab/button-tab.vue b/packages/materials/src/libs/page-tab/button-tab.vue new file mode 100644 index 0000000..38f7451 --- /dev/null +++ b/packages/materials/src/libs/page-tab/button-tab.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/packages/materials/src/libs/page-tab/chrome-tab-bg.vue b/packages/materials/src/libs/page-tab/chrome-tab-bg.vue new file mode 100644 index 0000000..d0f0bd0 --- /dev/null +++ b/packages/materials/src/libs/page-tab/chrome-tab-bg.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/packages/materials/src/libs/page-tab/chrome-tab.vue b/packages/materials/src/libs/page-tab/chrome-tab.vue new file mode 100644 index 0000000..4c979fb --- /dev/null +++ b/packages/materials/src/libs/page-tab/chrome-tab.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/packages/materials/src/libs/page-tab/icon-close.vue b/packages/materials/src/libs/page-tab/icon-close.vue new file mode 100644 index 0000000..f089930 --- /dev/null +++ b/packages/materials/src/libs/page-tab/icon-close.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/packages/materials/src/libs/page-tab/index.module.css b/packages/materials/src/libs/page-tab/index.module.css new file mode 100644 index 0000000..aa32c55 --- /dev/null +++ b/packages/materials/src/libs/page-tab/index.module.css @@ -0,0 +1,97 @@ +/* @type */ + +.button-tab { + border-color: #e5e7eb; +} + +.button-tab_dark { + border-color: #ffffff3d; +} + +.button-tab:hover { + color: var(--soy-primary-color); + border-color: var(--soy-primary-color-opacity3); +} + +.button-tab_active { + color: var(--soy-primary-color); + border-color: var(--soy-primary-color-opacity3); + background-color: var(--soy-primary-color-opacity1); +} + +.button-tab_active_dark { + background-color: var(--soy-primary-color-opacity2); +} + +.button-tab .icon_close:hover { + font-size: 12px; + color: #ffffff; + background-color: var(--soy-primary-color); +} + +.button-tab_dark .icon_close:hover { + color: #000000; +} + +.chrome-tab:hover { + z-index: 9; +} + +.chrome-tab_active { + z-index: 10; + color: var(--soy-primary-color); +} + +.chrome-tab__bg { + color: transparent; +} + +.chrome-tab_active .chrome-tab__bg { + color: var(--soy-primary-color1); +} + +.chrome-tab_active_dark .chrome-tab__bg { + color: var(--soy-primary-color2); +} + +.chrome-tab:hover .chrome-tab__bg { + color: #dee1e6; +} + +.chrome-tab_active:hover .chrome-tab__bg { + color: var(--soy-primary-color1); +} + +.chrome-tab_dark:hover .chrome-tab__bg { + color: #333333; +} + +.chrome-tab_active_dark:hover .chrome-tab__bg { + color: var(--soy-primary-color2); +} + +.chrome-tab .icon_close:hover { + font-size: 12px; + color: #ffffff; + background-color: #9ca3af; +} + +.chrome-tab_active .icon_close:hover { + background-color: var(--soy-primary-color); +} + +.chrome-tab_dark .icon_close:hover { + color: #000000; +} + +.chrome-tab_active .chrome-tab-divider { + opacity: 0; +} + +.chrome-tab:hover .chrome-tab-divider { + opacity: 0; +} + +.chrome-tab_dark .chrome-tab-divider { + background-color: rgba(255, 255, 255, 0.9); +} diff --git a/packages/materials/src/libs/page-tab/index.module.css.d.ts b/packages/materials/src/libs/page-tab/index.module.css.d.ts new file mode 100644 index 0000000..05c9066 --- /dev/null +++ b/packages/materials/src/libs/page-tab/index.module.css.d.ts @@ -0,0 +1,14 @@ +declare const styles: { + readonly 'button-tab': string; + readonly 'button-tab_dark': string; + readonly 'button-tab_active': string; + readonly 'button-tab_active_dark': string; + readonly icon_close: string; + readonly 'chrome-tab': string; + readonly 'chrome-tab_active': string; + readonly 'chrome-tab__bg': string; + readonly 'chrome-tab_active_dark': string; + readonly 'chrome-tab_dark': string; + readonly 'chrome-tab-divider': string; +}; +export = styles; diff --git a/packages/materials/src/libs/page-tab/index.ts b/packages/materials/src/libs/page-tab/index.ts new file mode 100644 index 0000000..b402adf --- /dev/null +++ b/packages/materials/src/libs/page-tab/index.ts @@ -0,0 +1,3 @@ +import PageTab from './index.vue'; + +export default PageTab; diff --git a/packages/materials/src/libs/page-tab/index.vue b/packages/materials/src/libs/page-tab/index.vue new file mode 100644 index 0000000..ba40948 --- /dev/null +++ b/packages/materials/src/libs/page-tab/index.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/packages/materials/src/libs/page-tab/shared.ts b/packages/materials/src/libs/page-tab/shared.ts new file mode 100644 index 0000000..933c765 --- /dev/null +++ b/packages/materials/src/libs/page-tab/shared.ts @@ -0,0 +1,33 @@ +import { addColorAlpha, transformColorWithOpacity } from '@sa/utils'; +import type { PageTabCssVarsProps, PageTabCssVars } from '../../types'; + +/** + * the active color of the tab + */ +export const ACTIVE_COLOR = '#1890ff'; + +function createCssVars(props: PageTabCssVarsProps) { + const cssVars: PageTabCssVars = { + '--soy-primary-color': props.primaryColor, + '--soy-primary-color1': props.primaryColor1, + '--soy-primary-color2': props.primaryColor2, + '--soy-primary-color-opacity1': props.primaryColorOpacity1, + '--soy-primary-color-opacity2': props.primaryColorOpacity2, + '--soy-primary-color-opacity3': props.primaryColorOpacity3 + }; + + return cssVars; +} + +export function createTabCssVars(primaryColor: string) { + const cssProps: PageTabCssVarsProps = { + primaryColor, + primaryColor1: transformColorWithOpacity(primaryColor, 0.1, '#ffffff'), + primaryColor2: transformColorWithOpacity(primaryColor, 0.3, '#000000'), + primaryColorOpacity1: addColorAlpha(primaryColor, 0.1), + primaryColorOpacity2: addColorAlpha(primaryColor, 0.15), + primaryColorOpacity3: addColorAlpha(primaryColor, 0.3) + }; + + return createCssVars(cssProps); +} diff --git a/packages/materials/src/libs/tooltip/index.vue b/packages/materials/src/libs/tooltip/index.vue new file mode 100644 index 0000000..5bde582 --- /dev/null +++ b/packages/materials/src/libs/tooltip/index.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/packages/materials/src/types/index.ts b/packages/materials/src/types/index.ts new file mode 100644 index 0000000..f451922 --- /dev/null +++ b/packages/materials/src/types/index.ts @@ -0,0 +1,282 @@ +/** + * header config + */ +interface AdminLayoutHeaderConfig { + /** + * whether header is visible + * @default true + */ + headerVisible?: boolean; + /** + * header class + * @default '' + */ + headerClass?: string; + /** + * header height + * @default 56px + */ + headerHeight?: number; +} + +/** + * tab config + */ +interface AdminLayoutTabConfig { + /** + * whether tab is visible + * @default true + */ + tabVisible?: boolean; + /** + * tab class + * @default '' + */ + tabClass?: string; + /** + * tab height + * @default 48px + */ + tabHeight?: number; +} + +/** + * sider config + */ +interface AdminLayoutSiderConfig { + /** + * whether sider is visible + * @default true + */ + siderVisible?: boolean; + /** + * sider class + * @default '' + */ + siderClass?: string; + /** + * mobile sider class + * @default '' + */ + mobileSiderClass?: string; + /** + * sider collapse status + * @default false + */ + siderCollapse?: boolean; + /** + * sider width when collapse is false + * @default '220px' + */ + siderWidth?: number; + /** + * sider width when collapse is true + * @default '64px' + */ + siderCollapsedWidth?: number; +} + +/** + * content config + */ +export interface AdminLayoutContentConfig { + /** + * content class + * @default '' + */ + contentClass?: string; + /** + * whether content is full the page + * @description if true, other elements will be hidden by `display: none` + */ + fullContent?: boolean; +} + +/** + * footer config + */ +export interface AdminLayoutFooterConfig { + /** + * whether footer is visible + * @default true + */ + footerVisible?: boolean; + /** + * whether footer is fixed + * @default true + */ + fixedFooter?: boolean; + /** + * footer class + * @default '' + */ + footerClass?: string; + /** + * footer height + * @default 48px + */ + footerHeight?: number; + /** + * whether footer is on the right side + * @description when the layout is vertical, the footer is on the right side + */ + rightFooter?: boolean; +} + +/** + * layout mode + * - horizontal + * - vertical + */ +export type LayoutMode = 'horizontal' | 'vertical'; + +/** + * the scroll mode when content overflow + * - wrapper: the layout component's wrapper element has a scrollbar + * - content: the layout component's content element has a scrollbar + * @default 'wrapper' + */ +export type LayoutScrollMode = 'wrapper' | 'content'; + +/** + * admin layout props + */ +export interface AdminLayoutProps + extends AdminLayoutHeaderConfig, + AdminLayoutTabConfig, + AdminLayoutSiderConfig, + AdminLayoutContentConfig, + AdminLayoutFooterConfig { + /** + * layout mode + * - {@link LayoutMode} + */ + mode?: LayoutMode; + /** is mobile layout */ + isMobile?: boolean; + /** + * scroll mode + * - {@link ScrollMode} + */ + scrollMode?: LayoutScrollMode; + /** + * the id of the scroll element of the layout + * @description it can be used to get the corresponding Dom and scroll it + * @default + * ```ts + * const adminLayoutScrollElId = '__ADMIN_LAYOUT_SCROLL_EL_ID__' + * ``` + * @example use the default id by import + * ```ts + * import { adminLayoutScrollElId } from '@sa/vue-materials'; + * ``` + */ + scrollElId?: string; + /** + * the class of the scroll element + */ + scrollElClass?: string; + /** + * the class of the scroll wrapper element + */ + scrollWrapperClass?: string; + /** + * the common class of the layout + * @description is can be used to configure the transition animation + * @default 'transition-all-300' + */ + commonClass?: string; + /** + * whether fix the header and tab + * @default true + */ + fixedTop?: boolean; + /** + * the max z-index of the layout + * @description the z-index of Header,Tab,Sider and Footer will not exceed this value + */ + maxZIndex?: number; +} + +type Kebab = S extends Uncapitalize ? S : `-${Uncapitalize}`; + +type KebabCase = S extends `${infer Start}${infer End}` + ? `${Uncapitalize}${KebabCase>}` + : S; + +type Prefix = '--soy-'; + +export type LayoutCssVarsProps = Pick< + AdminLayoutProps, + 'headerHeight' | 'tabHeight' | 'siderWidth' | 'siderCollapsedWidth' | 'footerHeight' +> & { + headerZIndex?: number; + tabZIndex?: number; + siderZIndex?: number; + mobileSiderZIndex?: number; + footerZIndex?: number; +}; + +export type LayoutCssVars = { + [K in keyof LayoutCssVarsProps as `${Prefix}${KebabCase}`]: string | number; +}; + +/** + * the mode of the tab + * - button: button style + * - chrome: chrome style + * @default chrome + */ +export type PageTabMode = 'button' | 'chrome'; + +export interface PageTabProps { + /** + * whether is dark mode + */ + darkMode?: boolean; + /** + * the mode of the tab + * - {@link TabMode} + */ + mode?: PageTabMode; + /** + * the common class of the layout + * @description is can be used to configure the transition animation + * @default 'transition-all-300' + */ + commonClass?: string; + /** + * the class of the button tab + */ + buttonClass?: string; + /** + * the class of the chrome tab + */ + chromeClass?: string; + /** + * whether the tab is active + */ + active?: boolean; + /** + * the color of the active tab + */ + activeColor?: string; + /** + * whether the tab is closable + * @description show the close icon when true + */ + closable?: boolean; +} + +export type PageTabCssVarsProps = { + primaryColor: string; + primaryColor1: string; + primaryColor2: string; + primaryColorOpacity1: string; + primaryColorOpacity2: string; + primaryColorOpacity3: string; +}; + +export type PageTabCssVars = { + [K in keyof PageTabCssVarsProps as `${Prefix}${KebabCase}`]: string | number; +}; diff --git a/packages/materials/tsconfig.json b/packages/materials/tsconfig.json new file mode 100644 index 0000000..d9203c3 --- /dev/null +++ b/packages/materials/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "module": "ESNext", + "target": "ESNext", + "lib": ["DOM", "ESNext"], + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "jsx": "preserve", + "moduleResolution": "node", + "resolveJsonModule": true, + "noUnusedLocals": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/request/package.json b/packages/request/package.json new file mode 100644 index 0000000..179d67d --- /dev/null +++ b/packages/request/package.json @@ -0,0 +1,17 @@ +{ + "name": "@sa/request", + "version": "1.0.0", + "exports": { + ".": "./src/index.ts" + }, + "typesVersions": { + "*": { + "*": [ + "./src/*" + ] + } + }, + "dependencies": { + "ofetch": "1.3.3" + } +} diff --git a/packages/request/src/index.ts b/packages/request/src/index.ts new file mode 100644 index 0000000..dce1ed4 --- /dev/null +++ b/packages/request/src/index.ts @@ -0,0 +1,10 @@ +import { ofetch } from 'ofetch'; +import type { FetchOptions } from 'ofetch'; + +export function createRequest(options: FetchOptions) { + const request = ofetch.create(options); + + return request; +} + +export default createRequest; diff --git a/packages/request/tsconfig.json b/packages/request/tsconfig.json new file mode 100644 index 0000000..d9203c3 --- /dev/null +++ b/packages/request/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "module": "ESNext", + "target": "ESNext", + "lib": ["DOM", "ESNext"], + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "jsx": "preserve", + "moduleResolution": "node", + "resolveJsonModule": true, + "noUnusedLocals": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/scripts/bin.cjs b/packages/scripts/bin.cjs new file mode 100755 index 0000000..364c497 --- /dev/null +++ b/packages/scripts/bin.cjs @@ -0,0 +1,6 @@ +#!/usr/bin/env node + +const path = require('path'); +const jiti = require('jiti')(__filename); + +jiti(path.resolve(__dirname, './src/index.ts')); diff --git a/packages/scripts/package.json b/packages/scripts/package.json new file mode 100644 index 0000000..6270274 --- /dev/null +++ b/packages/scripts/package.json @@ -0,0 +1,28 @@ +{ + "name": "@sa/scripts", + "version": "1.0.0", + "exports": { + ".": "./src/index.ts" + }, + "typesVersions": { + "*": { + "*": [ + "./src/*" + ] + } + }, + "bin": { + "sa": "./bin.cjs" + }, + "devDependencies": { + "c12": "1.5.1", + "cac": "6.7.14", + "consola": "3.2.3", + "enquirer": "2.4.1", + "execa": "8.0.1", + "jiti": "1.20.0", + "lint-staged": "15.0.2", + "npm-check-updates": "16.14.6", + "rimraf": "5.0.5" + } +} diff --git a/packages/scripts/src/commands/cleanup.ts b/packages/scripts/src/commands/cleanup.ts new file mode 100644 index 0000000..a9d3990 --- /dev/null +++ b/packages/scripts/src/commands/cleanup.ts @@ -0,0 +1,5 @@ +import { rimraf } from 'rimraf'; + +export async function cleanup(paths: string[]) { + await rimraf(paths, { glob: true }); +} diff --git a/packages/scripts/src/commands/git-commit.ts b/packages/scripts/src/commands/git-commit.ts new file mode 100644 index 0000000..b064009 --- /dev/null +++ b/packages/scripts/src/commands/git-commit.ts @@ -0,0 +1,75 @@ +import path from 'node:path'; +import { readFileSync } from 'node:fs'; +import enquirer from 'enquirer'; +import { bgRed, red, green } from 'kolorist'; +import { execCommand } from '../shared'; +import type { CliOption } from '../types'; + +interface PromptObject { + types: string; + scopes: string; + description: string; +} + +export async function gitCommit( + gitCommitTypes: CliOption['gitCommitTypes'], + gitCommitScopes: CliOption['gitCommitScopes'] +) { + const typesChoices = gitCommitTypes.map(([name, title]) => { + const nameWithSuffix = `${name}:`; + + const message = `${nameWithSuffix.padEnd(12)}${title}`; + + return { + name, + message + }; + }); + + const scopesChoices = gitCommitScopes.map(([name, title]) => ({ + name, + message: `${name.padEnd(30)} (${title})` + })); + + const result = await enquirer.prompt([ + { + name: 'types', + type: 'select', + message: 'Please select a type', + choices: typesChoices + }, + { + name: 'scopes', + type: 'select', + message: 'Please select a scope', + choices: scopesChoices + }, + { + name: 'description', + type: 'text', + message: 'Please enter a description' + } + ]); + + const commitMsg = `${result.types}(${result.scopes}): ${result.description}`; + + await execCommand('git', ['commit', '-m', commitMsg], { stdio: 'inherit' }); +} + +export async function gitCommitVerify() { + const gitPath = await execCommand('git', ['rev-parse', '--show-toplevel']); + + const gitMsgPath = path.join(gitPath, '.git', 'COMMIT_EDITMSG'); + + const commitMsg = readFileSync(gitMsgPath, 'utf8').trim(); + + const REG_EXP = /(?[a-z]+)(\((?.+)\))?(?!)?: (?.+)/i; + + if (!REG_EXP.test(commitMsg)) { + throw new Error( + `${bgRed(' ERROR ')} ${red('git commit message must match the Conventional Commits standard!')}\n\n${green( + 'Recommended to use the command `pnpm commit` to generate Conventional Commits compliant commit information.\nGet more info about Conventional Commits, follow this link: https://conventionalcommits.org' + )}` + ); + } +} diff --git a/packages/scripts/src/commands/index.ts b/packages/scripts/src/commands/index.ts new file mode 100644 index 0000000..99a1bba --- /dev/null +++ b/packages/scripts/src/commands/index.ts @@ -0,0 +1,5 @@ +export * from './git-commit'; +export * from './cleanup'; +export * from './update-pkg'; +export * from './prettier'; +export * from './lint-staged'; diff --git a/packages/scripts/src/commands/lint-staged.ts b/packages/scripts/src/commands/lint-staged.ts new file mode 100644 index 0000000..aa9744d --- /dev/null +++ b/packages/scripts/src/commands/lint-staged.ts @@ -0,0 +1,5 @@ +export async function execLintStaged(config: Record) { + const lintStaged = (await import('lint-staged')).default; + + return lintStaged({ config, allowEmpty: true }); +} diff --git a/packages/scripts/src/commands/prettier.ts b/packages/scripts/src/commands/prettier.ts new file mode 100644 index 0000000..314af4a --- /dev/null +++ b/packages/scripts/src/commands/prettier.ts @@ -0,0 +1,7 @@ +import { execCommand } from '../shared'; + +export async function prettierWrite(writeGlob: string[]) { + await execCommand('npx', ['prettier', '--write', '.', ...writeGlob], { + stdio: 'inherit' + }); +} diff --git a/packages/scripts/src/commands/update-pkg.ts b/packages/scripts/src/commands/update-pkg.ts new file mode 100644 index 0000000..54d1132 --- /dev/null +++ b/packages/scripts/src/commands/update-pkg.ts @@ -0,0 +1,5 @@ +import { execCommand } from '../shared'; + +export async function updatePkg(args: string[] = ['--deep', '-u']) { + execCommand('npx', ['ncu', ...args], { stdio: 'inherit' }); +} diff --git a/packages/scripts/src/config/index.ts b/packages/scripts/src/config/index.ts new file mode 100644 index 0000000..ec2e5b9 --- /dev/null +++ b/packages/scripts/src/config/index.ts @@ -0,0 +1,74 @@ +import { loadConfig } from 'c12'; +import type { CliOption } from '../types'; + +const eslintExt = '*.{js,jsx,mjs,cjs,ts,tsx,vue}'; + +const defaultOptions: CliOption = { + cwd: process.cwd(), + cleanupDirs: [ + '**/dist', + '**/package-lock.json', + '**/yarn.lock', + '**/pnpm-lock.yaml', + '**/node_modules', + '!node_modules/**' + ], + gitCommitTypes: [ + ['feat', 'A new feature'], + ['fix', 'A bug fix'], + ['docs', 'Documentation only changes'], + ['style', 'Changes that do not affect the meaning of the code'], + ['refactor', 'A code change that neither fixes a bug nor adds a feature'], + ['perf', 'A code change that improves performance'], + ['test', 'Adding missing tests or correcting existing tests'], + ['build', 'Changes that affect the build system or external dependencies'], + ['ci', 'Changes to our CI configuration files and scripts'], + ['chore', "Other changes that don't modify src or test files"], + ['revert', 'Reverts a previous commit'] + ], + gitCommitScopes: [ + ['projects', 'project'], + ['components', 'components'], + ['hooks', 'hook functions'], + ['utils', 'utils functions'], + ['types', 'TS declaration'], + ['styles', 'style'], + ['deps', 'project dependencies'], + ['release', 'release project'], + ['other', 'other changes'] + ], + ncuCommandArgs: ['--deep', '-u'], + prettierWriteGlob: [ + `!**/${eslintExt}`, + '!*.min.*', + '!CHANGELOG.md', + '!dist', + '!LICENSE*', + '!output', + '!coverage', + '!public', + '!temp', + '!package-lock.json', + '!pnpm-lock.yaml', + '!yarn.lock', + '!.github', + '!__snapshots__', + '!node_modules' + ], + lintStagedConfig: { + [eslintExt]: 'eslint --fix', + '*': 'sa prettier-write' + } +}; + +export async function loadCliOptions(overrides?: Partial, cwd = process.cwd()) { + const { config } = await loadConfig>({ + name: 'soybean', + defaults: defaultOptions, + overrides, + cwd, + packageJson: true + }); + + return config as CliOption; +} diff --git a/packages/scripts/src/index.ts b/packages/scripts/src/index.ts new file mode 100755 index 0000000..869339f --- /dev/null +++ b/packages/scripts/src/index.ts @@ -0,0 +1,70 @@ +import cac from 'cac'; +import { blue, lightGreen } from 'kolorist'; +import { version } from '../package.json'; +import { cleanup, updatePkg, gitCommit, gitCommitVerify, prettierWrite, execLintStaged } from './commands'; +import { loadCliOptions } from './config'; + +type Command = 'cleanup' | 'update-pkg' | 'git-commit' | 'git-commit-verify' | 'prettier-write' | 'lint-staged'; + +type CommandAction = (args?: A) => Promise | void; + +type CommandWithAction = Record }>; + +interface CommandArg { + total?: boolean; +} + +export async function setupCli() { + const cliOptions = await loadCliOptions(); + + const cli = cac(blue('soybean')); + + cli.version(lightGreen(version)).help(); + + const commands: CommandWithAction = { + cleanup: { + desc: 'delete dirs: node_modules, dist, etc.', + action: async () => { + await cleanup(cliOptions.cleanupDirs); + } + }, + 'update-pkg': { + desc: 'update package.json dependencies versions', + action: async () => { + await updatePkg(cliOptions.ncuCommandArgs); + } + }, + 'git-commit': { + desc: 'git commit, generate commit message which match Conventional Commits standard', + action: async () => { + await gitCommit(cliOptions.gitCommitTypes, cliOptions.gitCommitScopes); + } + }, + 'git-commit-verify': { + desc: 'verify git commit message, make sure it match Conventional Commits standard', + action: async () => { + await gitCommitVerify(); + } + }, + 'prettier-write': { + desc: 'run prettier --write', + action: async () => { + await prettierWrite(cliOptions.prettierWriteGlob); + } + }, + 'lint-staged': { + desc: 'run lint-staged', + action: async () => { + await execLintStaged(cliOptions.lintStagedConfig); + } + } + }; + + for (const [command, { desc, action }] of Object.entries(commands)) { + cli.command(command, lightGreen(desc)).action(action); + } + + cli.parse(); +} + +setupCli(); diff --git a/packages/scripts/src/shared/index.ts b/packages/scripts/src/shared/index.ts new file mode 100644 index 0000000..80d933c --- /dev/null +++ b/packages/scripts/src/shared/index.ts @@ -0,0 +1,7 @@ +import type { Options } from 'execa'; + +export async function execCommand(cmd: string, args: string[], options?: Options) { + const { execa } = await import('execa'); + const res = await execa(cmd, args, options); + return res?.stdout?.trim() || ''; +} diff --git a/packages/scripts/src/types/index.ts b/packages/scripts/src/types/index.ts new file mode 100644 index 0000000..143f227 --- /dev/null +++ b/packages/scripts/src/types/index.ts @@ -0,0 +1,37 @@ +export interface CliOption { + /** + * the project root directory + */ + cwd: string; + /** + * cleanup dirs + * @default + * ```json + * ["** /dist", "** /pnpm-lock.yaml", "** /node_modules", "!node_modules/**"] + * ``` + * @description glob pattern syntax {@link https://github.com/isaacs/minimatch} + */ + cleanupDirs: string[]; + /** + * git commit types + */ + gitCommitTypes: [string, string][]; + /** + * git commit scopes + */ + gitCommitScopes: [string, string][]; + /** + * npm-check-updates command args + * @default ["--deep","-u"] + */ + ncuCommandArgs: string[]; + /** + * prettier write glob + * @description glob pattern syntax {@link https://github.com/micromatch/micromatch} + */ + prettierWriteGlob: string[]; + /** + * lint-staged config + */ + lintStagedConfig: Record; +} diff --git a/packages/scripts/tsconfig.json b/packages/scripts/tsconfig.json new file mode 100644 index 0000000..36f61cf --- /dev/null +++ b/packages/scripts/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "module": "ESNext", + "target": "ESNext", + "lib": ["DOM", "ESNext"], + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "jsx": "preserve", + "moduleResolution": "node", + "resolveJsonModule": true, + "noUnusedLocals": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*", "typings/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/scripts/typings/pkg.d.ts b/packages/scripts/typings/pkg.d.ts new file mode 100644 index 0000000..7904945 --- /dev/null +++ b/packages/scripts/typings/pkg.d.ts @@ -0,0 +1,15 @@ +declare module 'lint-staged' { + interface LintStagedOptions { + config?: Record; + allowEmpty?: boolean; + } + + type LintStagedFn = (options: LintStagedOptions) => Promise; + interface LintStaged extends LintStagedFn { + default: LintStagedFn; + } + + const lintStaged: LintStaged; + + export default lintStaged; +} diff --git a/packages/uno-preset/package.json b/packages/uno-preset/package.json new file mode 100644 index 0000000..f64e24d --- /dev/null +++ b/packages/uno-preset/package.json @@ -0,0 +1,14 @@ +{ + "name": "@sa/uno-preset", + "version": "1.0.0", + "exports": { + ".": "./src/index.ts" + }, + "typesVersions": { + "*": { + "*": [ + "./src/*" + ] + } + } +} diff --git a/packages/uno-preset/src/index.ts b/packages/uno-preset/src/index.ts new file mode 100644 index 0000000..4073727 --- /dev/null +++ b/packages/uno-preset/src/index.ts @@ -0,0 +1,57 @@ +// @unocss-include + +import type { Preset } from '@unocss/core'; +import type { Theme } from '@unocss/preset-uno'; + +export function presetSoybeanAdmin(): Preset { + const preset: Preset = { + name: 'preset-soybean-tool', + shortcuts: [ + { + 'wh-full': 'w-full h-full' + }, + { + 'flex-center': 'flex justify-center items-center', + 'flex-x-center': 'flex justify-center', + 'flex-y-center': 'flex items-center', + 'flex-vertical': 'flex flex-col', + 'flex-vertical-center': 'flex-center flex-col', + 'flex-vertical-stretch': 'flex-vertical items-stretch', + 'i-flex-center': 'inline-flex justify-center items-center', + 'i-flex-x-center': 'inline-flex justify-center', + 'i-flex-y-center': 'inline-flex items-center', + 'i-flex-vertical': 'inline-flex flex-col', + 'i-flex-vertical-stretch': 'i-flex-vertical items-stretch', + 'flex-1-hidden': 'flex-1 overflow-hidden' + }, + { + 'absolute-lt': 'absolute left-0 top-0', + 'absolute-lb': 'absolute left-0 bottom-0', + 'absolute-rt': 'absolute right-0 top-0', + 'absolute-rb': 'absolute right-0 bottom-0', + 'absolute-tl': 'absolute-lt', + 'absolute-tr': 'absolute-rt', + 'absolute-bl': 'absolute-lb', + 'absolute-br': 'absolute-rb', + 'absolute-center': 'absolute-lt flex-center wh-full', + 'fixed-lt': 'fixed left-0 top-0', + 'fixed-lb': 'fixed left-0 bottom-0', + 'fixed-rt': 'fixed right-0 top-0', + 'fixed-rb': 'fixed right-0 bottom-0', + 'fixed-tl': 'fixed-lt', + 'fixed-tr': 'fixed-rt', + 'fixed-bl': 'fixed-lb', + 'fixed-br': 'fixed-rb', + 'fixed-center': 'fixed-lt flex-center wh-full' + }, + { + 'nowrap-hidden': 'overflow-hidden whitespace-nowrap', + 'ellipsis-text': 'nowrap-hidden text-ellipsis' + } + ] + }; + + return preset; +} + +export default presetSoybeanAdmin; diff --git a/packages/uno-preset/tsconfig.json b/packages/uno-preset/tsconfig.json new file mode 100644 index 0000000..d9203c3 --- /dev/null +++ b/packages/uno-preset/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "module": "ESNext", + "target": "ESNext", + "lib": ["DOM", "ESNext"], + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "jsx": "preserve", + "moduleResolution": "node", + "resolveJsonModule": true, + "noUnusedLocals": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/utils/package.json b/packages/utils/package.json new file mode 100644 index 0000000..9d989a1 --- /dev/null +++ b/packages/utils/package.json @@ -0,0 +1,22 @@ +{ + "name": "@sa/utils", + "version": "1.0.0", + "exports": { + ".": "./src/index.ts" + }, + "typesVersions": { + "*": { + "*": [ + "./src/*" + ] + } + }, + "dependencies": { + "colord": "2.9.3", + "crypto-js": "4.1.1", + "localforage": "1.10.0" + }, + "devDependencies": { + "@types/crypto-js": "4.1.3" + } +} diff --git a/packages/utils/src/color.ts b/packages/utils/src/color.ts new file mode 100644 index 0000000..c45650a --- /dev/null +++ b/packages/utils/src/color.ts @@ -0,0 +1,248 @@ +import { colord, extend } from 'colord'; +import namesPlugin from 'colord/plugins/names'; +import mixPlugin from 'colord/plugins/mix'; +import type { AnyColor, HsvColor, RgbColor } from 'colord'; + +extend([namesPlugin, mixPlugin]); + +/** + * 给颜色加透明度 + * @param color - 颜色 + * @param alpha - 透明度(0 - 1) + */ +export function addColorAlpha(color: string, alpha: number) { + return colord(color).alpha(alpha).toHex(); +} + +/** + * 颜色混合 + * @param firstColor - 第一个颜色 + * @param secondColor - 第二个颜色 + * @param ratio - 第二个颜色占比 + */ +export function mixColor(firstColor: string, secondColor: string, ratio: number) { + return colord(firstColor).mix(secondColor, ratio).toHex(); +} + +/** + * 将带有透明度的颜色转换成相近的没有透明度的颜色 + * @param color - 颜色 + * @param alpha - 透明度(0 - 1) + * @param bgColor 背景颜色(一般是白色或者黑色) + */ +export function transformColorWithOpacity(color: string, alpha: number, bgColor = '#ffffff') { + const originColor = addColorAlpha(color, alpha); + const { r: oR, g: oG, b: oB } = colord(originColor).toRgb(); + + const { r: bgR, g: bgG, b: bgB } = colord(bgColor).toRgb(); + + function calRgb(or: number, bg: number, al: number) { + return bg + (or - bg) * al; + } + + const resultRgb: RgbColor = { + r: calRgb(oR, bgR, alpha), + g: calRgb(oG, bgG, alpha), + b: calRgb(oB, bgB, alpha) + }; + + return colord(resultRgb).toHex(); +} + +/** + * 是否是白颜色 + * @param color - 颜色 + */ +export function isWhiteColor(color: string) { + return colord(color).isEqual('#ffffff'); +} + +/** + * 获取颜色的rgb值 + * @param color 颜色 + */ +export function getRgbOfColor(color: string) { + return colord(color).toRgb(); +} + +/** 色相阶梯 */ +const hueStep = 2; +/** 饱和度阶梯,浅色部分 */ +const saturationStep = 16; +/** 饱和度阶梯,深色部分 */ +const saturationStep2 = 5; +/** 亮度阶梯,浅色部分 */ +const brightnessStep1 = 5; +/** 亮度阶梯,深色部分 */ +const brightnessStep2 = 15; +/** 浅色数量,主色上 */ +const lightColorCount = 5; +/** 深色数量,主色下 */ +const darkColorCount = 4; + +/** + * 调色板的颜色索引 + * @description 从左至右颜色从浅到深,6为主色号 + */ +type ColorIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; + +/** + * 根据颜色获取调色板颜色(从左至右颜色从浅到深,6为主色号) + * @param color - 颜色 + * @param index - 调色板的对应的色号(6为主色号) + * @returns 返回hex格式的颜色 + */ +export function getColorPalette(color: AnyColor, index: ColorIndex): string { + const transformColor = colord(color); + + if (!transformColor.isValid()) { + throw Error('invalid input color value'); + } + + if (index === 6) { + return colord(transformColor).toHex(); + } + + const isLight = index < 6; + const hsv = transformColor.toHsv(); + const i = isLight ? lightColorCount + 1 - index : index - lightColorCount - 1; + + const newHsv: HsvColor = { + h: getHue(hsv, i, isLight), + s: getSaturation(hsv, i, isLight), + v: getValue(hsv, i, isLight) + }; + + return colord(newHsv).toHex(); +} + +/** 暗色主题颜色映射关系表 */ +const darkColorMap = [ + { index: 7, opacity: 0.15 }, + { index: 6, opacity: 0.25 }, + { index: 5, opacity: 0.3 }, + { index: 5, opacity: 0.45 }, + { index: 5, opacity: 0.65 }, + { index: 5, opacity: 0.85 }, + { index: 4, opacity: 0.9 }, + { index: 3, opacity: 0.95 }, + { index: 2, opacity: 0.97 }, + { index: 1, opacity: 0.98 } +]; + +/** + * 根据颜色获取调色板颜色所有颜色 + * @param color - 颜色 + * @param darkTheme - 暗黑主题的调色板颜色 + * @param darkThemeMixColor - 暗黑主题的混合颜色,默认 #141414 + */ +export function getColorPalettes(color: AnyColor, darkTheme = false, darkThemeMixColor = '#141414'): string[] { + const indexes: ColorIndex[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + const patterns = indexes.map(index => getColorPalette(color, index)); + + if (darkTheme) { + const darkPatterns = darkColorMap.map(({ index, opacity }) => { + const darkColor = colord(darkThemeMixColor).mix(patterns[index], opacity); + + return darkColor; + }); + + return darkPatterns.map(item => colord(item).toHex()); + } + + return patterns; +} + +/** + * 获取色相渐变 + * @param hsv - hsv格式颜色值 + * @param i - 与6的相对距离 + * @param isLight - 是否是亮颜色 + */ +function getHue(hsv: HsvColor, i: number, isLight: boolean) { + let hue: number; + + const hsvH = Math.round(hsv.h); + + if (hsvH >= 60 && hsvH <= 240) { + // 冷色调 + // 减淡变亮 色相顺时针旋转 更暖 + // 加深变暗 色相逆时针旋转 更冷 + hue = isLight ? hsvH - hueStep * i : hsvH + hueStep * i; + } else { + // 暖色调 + // 减淡变亮 色相逆时针旋转 更暖 + // 加深变暗 色相顺时针旋转 更冷 + hue = isLight ? hsvH + hueStep * i : hsvH - hueStep * i; + } + + if (hue < 0) { + hue += 360; + } + + if (hue >= 360) { + hue -= 360; + } + + return hue; +} + +/** + * 获取饱和度渐变 + * @param hsv - hsv格式颜色值 + * @param i - 与6的相对距离 + * @param isLight - 是否是亮颜色 + */ +function getSaturation(hsv: HsvColor, i: number, isLight: boolean) { + // 灰色不渐变 + if (hsv.h === 0 && hsv.s === 0) { + return hsv.s; + } + + let saturation: number; + + if (isLight) { + saturation = hsv.s - saturationStep * i; + } else if (i === darkColorCount) { + saturation = hsv.s + saturationStep; + } else { + saturation = hsv.s + saturationStep2 * i; + } + + if (saturation > 100) { + saturation = 100; + } + + if (isLight && i === lightColorCount && saturation > 10) { + saturation = 10; + } + + if (saturation < 6) { + saturation = 6; + } + + return saturation; +} + +/** + * 获取明度渐变 + * @param hsv - hsv格式颜色值 + * @param i - 与6的相对距离 + * @param isLight - 是否是亮颜色 + */ +function getValue(hsv: HsvColor, i: number, isLight: boolean) { + let value: number; + + if (isLight) { + value = hsv.v + brightnessStep1 * i; + } else { + value = hsv.v - brightnessStep2 * i; + } + + if (value > 100) { + value = 100; + } + + return value; +} diff --git a/packages/utils/src/crypto.ts b/packages/utils/src/crypto.ts new file mode 100644 index 0000000..7d2ffdc --- /dev/null +++ b/packages/utils/src/crypto.ts @@ -0,0 +1,29 @@ +import CryptoJS from 'crypto-js'; + +export class Crypto { + /** + * secret + */ + secret: string; + + constructor(secret: string) { + this.secret = secret; + } + + encrypt(data: T): string { + const dataString = JSON.stringify(data); + const encrypted = CryptoJS.AES.encrypt(dataString, this.secret); + return encrypted.toString(); + } + + decrypt(encrypted: string) { + const decrypted = CryptoJS.AES.decrypt(encrypted, this.secret); + const dataString = decrypted.toString(CryptoJS.enc.Utf8); + try { + return JSON.parse(dataString) as T; + } catch { + // avoid parse error + return null; + } + } +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts new file mode 100644 index 0000000..cb239b1 --- /dev/null +++ b/packages/utils/src/index.ts @@ -0,0 +1,3 @@ +export * from './color'; +export * from './crypto'; +export * from './storage'; diff --git a/packages/utils/src/storage.ts b/packages/utils/src/storage.ts new file mode 100644 index 0000000..083dfe8 --- /dev/null +++ b/packages/utils/src/storage.ts @@ -0,0 +1,76 @@ +import localforage from 'localforage'; + +/** + * the storage type + */ +export type StorageType = 'local' | 'session'; + +export function createStorage(type: StorageType) { + const stg = type === 'session' ? window.sessionStorage : window.localStorage; + + const storage = { + /** + * set session + * @param key session key + * @param value session value + */ + set(key: K, value: T[K]) { + const json = JSON.stringify(value); + + stg.setItem(key as string, json); + }, + /** + * get session + * @param key session key + */ + get(key: K): T[K] | null { + const json = stg.getItem(key as string); + if (json) { + let storageData: T[K] | null = null; + + try { + storageData = JSON.parse(json); + } catch {} + + if (storageData) { + return storageData as T[K]; + } + } + + stg.removeItem(key as string); + + return null; + }, + remove(key: keyof T) { + stg.removeItem(key as string); + }, + clear() { + stg.clear(); + } + }; + return storage; +} + +type LocalForage = Omit & { + getItem(key: K, callback?: (err: any, value: T[K] | null) => void): Promise; + + setItem(key: K, value: T[K], callback?: (err: any, value: T[K]) => void): Promise; + + removeItem(key: keyof T, callback?: (err: any) => void): Promise; +}; + +type LocalforageDriver = 'local' | 'indexedDB' | 'webSQL'; + +export function createLocalforage(driver: LocalforageDriver) { + const driverMap: Record = { + local: localforage.LOCALSTORAGE, + indexedDB: localforage.INDEXEDDB, + webSQL: localforage.WEBSQL + }; + + localforage.config({ + driver: driverMap[driver] + }); + + return localforage as LocalForage; +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..dee51e9 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - "packages/*" diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..169b2ab --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1 @@ + diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..f332f04 Binary files /dev/null and b/public/logo.png differ diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..156f4b5 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/src/assets/svg-icon/activity.svg b/src/assets/svg-icon/activity.svg new file mode 100644 index 0000000..abe892f --- /dev/null +++ b/src/assets/svg-icon/activity.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/at-sign.svg b/src/assets/svg-icon/at-sign.svg new file mode 100644 index 0000000..625214d --- /dev/null +++ b/src/assets/svg-icon/at-sign.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/avatar.svg b/src/assets/svg-icon/avatar.svg new file mode 100644 index 0000000..66fe6f2 --- /dev/null +++ b/src/assets/svg-icon/avatar.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/banner.svg b/src/assets/svg-icon/banner.svg new file mode 100644 index 0000000..192b637 --- /dev/null +++ b/src/assets/svg-icon/banner.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/cast.svg b/src/assets/svg-icon/cast.svg new file mode 100644 index 0000000..4f008d3 --- /dev/null +++ b/src/assets/svg-icon/cast.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/chrome.svg b/src/assets/svg-icon/chrome.svg new file mode 100644 index 0000000..6314173 --- /dev/null +++ b/src/assets/svg-icon/chrome.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/copy.svg b/src/assets/svg-icon/copy.svg new file mode 100644 index 0000000..ab25601 --- /dev/null +++ b/src/assets/svg-icon/copy.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/custom-icon.svg b/src/assets/svg-icon/custom-icon.svg new file mode 100644 index 0000000..b33a43f --- /dev/null +++ b/src/assets/svg-icon/custom-icon.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/empty-data.svg b/src/assets/svg-icon/empty-data.svg new file mode 100644 index 0000000..293486c --- /dev/null +++ b/src/assets/svg-icon/empty-data.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/expectation.svg b/src/assets/svg-icon/expectation.svg new file mode 100644 index 0000000..1d87d5e --- /dev/null +++ b/src/assets/svg-icon/expectation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg-icon/heart.svg b/src/assets/svg-icon/heart.svg new file mode 100644 index 0000000..56e59b4 --- /dev/null +++ b/src/assets/svg-icon/heart.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/logo-fill.svg b/src/assets/svg-icon/logo-fill.svg new file mode 100644 index 0000000..4ceed1e --- /dev/null +++ b/src/assets/svg-icon/logo-fill.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/logo.svg b/src/assets/svg-icon/logo.svg new file mode 100644 index 0000000..341675d --- /dev/null +++ b/src/assets/svg-icon/logo.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/network-error.svg b/src/assets/svg-icon/network-error.svg new file mode 100644 index 0000000..52f97ab --- /dev/null +++ b/src/assets/svg-icon/network-error.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/no-icon.svg b/src/assets/svg-icon/no-icon.svg new file mode 100644 index 0000000..f6dcdd0 --- /dev/null +++ b/src/assets/svg-icon/no-icon.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/no-permission.svg b/src/assets/svg-icon/no-permission.svg new file mode 100644 index 0000000..4c408ca --- /dev/null +++ b/src/assets/svg-icon/no-permission.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/not-found.svg b/src/assets/svg-icon/not-found.svg new file mode 100644 index 0000000..a513656 --- /dev/null +++ b/src/assets/svg-icon/not-found.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/service-error.svg b/src/assets/svg-icon/service-error.svg new file mode 100644 index 0000000..0120f1e --- /dev/null +++ b/src/assets/svg-icon/service-error.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg-icon/wind.svg b/src/assets/svg-icon/wind.svg new file mode 100644 index 0000000..7c90590 --- /dev/null +++ b/src/assets/svg-icon/wind.svg @@ -0,0 +1 @@ + diff --git a/src/components/common/app-loading.vue b/src/components/common/app-loading.vue new file mode 100644 index 0000000..a6231c5 --- /dev/null +++ b/src/components/common/app-loading.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/src/components/common/color-schema-switch.vue b/src/components/common/color-schema-switch.vue new file mode 100644 index 0000000..7eb65c1 --- /dev/null +++ b/src/components/common/color-schema-switch.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/src/components/common/dark-mode-container.vue b/src/components/common/dark-mode-container.vue new file mode 100644 index 0000000..1313079 --- /dev/null +++ b/src/components/common/dark-mode-container.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/src/components/common/exception-base.vue b/src/components/common/exception-base.vue new file mode 100644 index 0000000..cb01071 --- /dev/null +++ b/src/components/common/exception-base.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/components/common/hover-container.vue b/src/components/common/hover-container.vue new file mode 100644 index 0000000..12d8ba6 --- /dev/null +++ b/src/components/common/hover-container.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/src/components/common/system-logo.vue b/src/components/common/system-logo.vue new file mode 100644 index 0000000..1f2e57e --- /dev/null +++ b/src/components/common/system-logo.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/src/components/custom/look-forward.vue b/src/components/custom/look-forward.vue new file mode 100644 index 0000000..7c0db0b --- /dev/null +++ b/src/components/custom/look-forward.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/src/components/custom/menu-toggler.vue b/src/components/custom/menu-toggler.vue new file mode 100644 index 0000000..49dfc65 --- /dev/null +++ b/src/components/custom/menu-toggler.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/src/components/custom/svg-icon.vue b/src/components/custom/svg-icon.vue new file mode 100644 index 0000000..be7e8f2 --- /dev/null +++ b/src/components/custom/svg-icon.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/components/custom/wave-bg.vue b/src/components/custom/wave-bg.vue new file mode 100644 index 0000000..2b7d035 --- /dev/null +++ b/src/components/custom/wave-bg.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/src/constants/app.ts b/src/constants/app.ts new file mode 100644 index 0000000..fdc748c --- /dev/null +++ b/src/constants/app.ts @@ -0,0 +1,47 @@ +import { $t } from '@/locales'; +import { transformObjectToOption } from '@/utils/common'; + +export const loginModuleLabels: Record = { + 'pwd-login': $t('page.login.pwdLogin.title'), + 'code-login': $t('page.login.codeLogin.title'), + register: $t('page.login.register.title'), + 'reset-pwd': $t('page.login.resetPwd.title'), + 'bind-wechat': $t('page.login.bindWeChat.title') +}; + +export const themeLayoutModeLabels: Record = { + vertical: '左侧菜单模式', + horizontal: '顶部菜单模式', + 'vertical-mix': '左侧菜单混合模式', + 'horizontal-mix': '顶部菜单混合模式' +}; +export const themeLayoutModeOptions = transformObjectToOption(themeLayoutModeLabels); + +export const themeScrollModeLabels: Record = { + wrapper: '外层滚动', + content: '主体滚动' +}; +export const themeScrollModeOptions = transformObjectToOption(themeScrollModeLabels); + +export const themeTabModeLabels: Record = { + chrome: '谷歌风格', + button: '按钮风格' +}; +export const themeTabModeOptions = transformObjectToOption(themeTabModeLabels); + +export const themeHorizontalMenuPositionLabels: Record = { + 'flex-start': '居左', + center: '居中', + 'flex-end': '居右' +}; +export const themeHorizontalMenuPositionOptions = transformObjectToOption(themeHorizontalMenuPositionLabels); + +// export const themeAnimateModeLabels: Record = { +// 'zoom-fade': '渐变', +// 'zoom-out': '闪现', +// 'fade-slide': '滑动', +// fade: '消退', +// 'fade-bottom': '底部消退', +// 'fade-scale': '缩放消退' +// }; +// export const themeAnimateModeOptions = transformObjectToOption(themeAnimateModeLabels); diff --git a/src/enum/index.ts b/src/enum/index.ts new file mode 100644 index 0000000..1a6595e --- /dev/null +++ b/src/enum/index.ts @@ -0,0 +1,5 @@ +export enum SetupStoreId { + Theme = 'theme-store', + Auth = 'auth-store', + Route = 'route-store' +} diff --git a/src/hooks/common/router.ts b/src/hooks/common/router.ts new file mode 100644 index 0000000..8abdb68 --- /dev/null +++ b/src/hooks/common/router.ts @@ -0,0 +1,92 @@ +import { toRefs } from 'vue'; +import { useRouter } from 'vue-router'; +import type { RouteLocationRaw } from 'vue-router'; +import type { RouteKey } from '@elegant-router/types'; +import { router as globalRouter } from '@/router'; + +/** + * router push + * @description jump to the specified route, it can replace function router.push + * @param inSetup + */ +export function useRouterPush(inSetup = true) { + const router = inSetup ? useRouter() : globalRouter; + const route = globalRouter.currentRoute; + + const routerPush = router.push; + + const routerBack = router.back; + + interface RouterPushOptions { + query?: Record; + params?: Record; + } + + async function routerPushByKey(key: RouteKey, options?: RouterPushOptions) { + const { query, params } = options || {}; + + const routeLocation: RouteLocationRaw = { + name: key + }; + + if (query) { + routeLocation.query = query; + } + + if (params) { + routeLocation.params = params; + } + + return routerPush(routeLocation); + } + + /** + * navigate to login page + * @param loginModule the login module + * @param redirectUrl the redirect url, if not specified, it will be the current route fullPath + */ + async function toLogin(loginModule?: UnionKey.LoginModule, redirectUrl?: string) { + const module = loginModule || 'pwd-login'; + + const options: RouterPushOptions = { + params: { + module + } + }; + + const redirect = redirectUrl || route.value.fullPath; + + options.query = { + redirect + }; + + return routerPushByKey('login', options); + } + + /** + * toggle login module + * @param module + */ + async function toggleLoginModule(module: UnionKey.LoginModule) { + const query = route.value.query as Record; + + return routerPushByKey('login', { query, params: { module } }); + } + + return { + route, + routerPush, + routerBack, + routerPushByKey, + toLogin, + toggleLoginModule + }; +} + +export function useRouteQuery>() { + const route = globalRouter.currentRoute; + + const query = route.value.query as T; + + return toRefs(query); +} diff --git a/src/layouts/base-layout/index.vue b/src/layouts/base-layout/index.vue new file mode 100644 index 0000000..6058d8f --- /dev/null +++ b/src/layouts/base-layout/index.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/src/layouts/blank-layout/index.vue b/src/layouts/blank-layout/index.vue new file mode 100644 index 0000000..2e393f0 --- /dev/null +++ b/src/layouts/blank-layout/index.vue @@ -0,0 +1,13 @@ + + + + + diff --git a/src/layouts/components/global-logo.vue b/src/layouts/components/global-logo.vue new file mode 100644 index 0000000..af97598 --- /dev/null +++ b/src/layouts/components/global-logo.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/src/layouts/modules/global-content/index.vue b/src/layouts/modules/global-content/index.vue new file mode 100644 index 0000000..202ce14 --- /dev/null +++ b/src/layouts/modules/global-content/index.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/src/layouts/modules/global-footer/index.vue b/src/layouts/modules/global-footer/index.vue new file mode 100644 index 0000000..ffec21e --- /dev/null +++ b/src/layouts/modules/global-footer/index.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/src/layouts/modules/global-header/components/switch-lang.vue b/src/layouts/modules/global-header/components/switch-lang.vue new file mode 100644 index 0000000..6056386 --- /dev/null +++ b/src/layouts/modules/global-header/components/switch-lang.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/layouts/modules/global-header/components/user-avatar.vue b/src/layouts/modules/global-header/components/user-avatar.vue new file mode 100644 index 0000000..8389add --- /dev/null +++ b/src/layouts/modules/global-header/components/user-avatar.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/layouts/modules/global-header/index.vue b/src/layouts/modules/global-header/index.vue new file mode 100644 index 0000000..73bc0b0 --- /dev/null +++ b/src/layouts/modules/global-header/index.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/layouts/modules/global-sider/index.vue b/src/layouts/modules/global-sider/index.vue new file mode 100644 index 0000000..8bba640 --- /dev/null +++ b/src/layouts/modules/global-sider/index.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/src/layouts/modules/global-tab/index.vue b/src/layouts/modules/global-tab/index.vue new file mode 100644 index 0000000..47b273c --- /dev/null +++ b/src/layouts/modules/global-tab/index.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/src/locales/index.ts b/src/locales/index.ts new file mode 100644 index 0000000..fb51986 --- /dev/null +++ b/src/locales/index.ts @@ -0,0 +1,25 @@ +import type { App } from 'vue'; +import { createI18n } from 'vue-i18n'; +import { localStg } from '@/utils/storage'; +import messages from './locale'; + +const i18n = createI18n({ + locale: localStg.get('lang') || 'zh-CN', + fallbackLocale: 'en', + messages, + legacy: false +}); + +/** + * setup plugin i18n + * @param app + */ +export function setupI18n(app: App) { + app.use(i18n); +} + +export const $t = i18n.global.t as App.I18n.$T; + +export function setLocale(locale: App.I18n.LangType) { + i18n.global.locale.value = locale; +} diff --git a/src/locales/lang/en.ts b/src/locales/lang/en.ts new file mode 100644 index 0000000..88784e7 --- /dev/null +++ b/src/locales/lang/en.ts @@ -0,0 +1,71 @@ +const local: App.I18n.Schema = { + system: { + title: 'SoybeanAdmin' + }, + common: { + tip: 'Tip', + add: 'Add', + addSuccess: 'Add Success', + edit: 'Edit', + editSuccess: 'Edit Success', + delete: 'Delete', + deleteSuccess: 'Delete Success', + batchDelete: 'Batch Delete', + confirm: 'Confirm', + cancel: 'Cancel', + pleaseCheckValue: 'Please check whether the value is valid', + action: 'Action', + backToHome: 'Back to home', + lookForward: 'Coming soon', + userCenter: 'User Center', + logout: 'Logout', + logoutConfirm: 'Are you sure you want to log out?' + }, + page: { + login: { + common: { + userNamePlaceholder: 'Please enter user name', + phonePlaceholder: 'Please enter phone number', + codePlaceholder: 'Please enter verification code', + passwordPlaceholder: 'Please enter password', + confirmPasswordPlaceholder: 'Please enter password again', + codeLogin: 'Verification code login', + confirm: 'Confirm', + back: 'Back', + validateSuccess: 'Verification passed', + loginSuccess: 'Login success', + welcomeBack: 'Welcome back, {userName}!' + }, + pwdLogin: { + title: 'Password Login', + rememberMe: 'Remember me', + forgetPassword: 'Forget password?', + register: 'Register account', + otherAccountLogin: 'Other Account Login', + otherLoginMode: 'Other Login Mode', + superAdmin: 'Super Administrator', + admin: 'Administrator', + user: 'Ordinary User' + }, + codeLogin: { + title: 'Verification Code Login', + getCode: 'Get verification code', + imageCodePlaceholder: 'Please enter image verification code' + }, + register: { + title: 'Register Account', + agreement: 'I have read and agree to', + protocol: '《User Agreement》', + policy: '《Privacy Policy》' + }, + resetPwd: { + title: 'Reset Password' + }, + bindWeChat: { + title: 'Bind WeChat' + } + } + } +}; + +export default local; diff --git a/src/locales/lang/zh-CN.ts b/src/locales/lang/zh-CN.ts new file mode 100644 index 0000000..a6edfe7 --- /dev/null +++ b/src/locales/lang/zh-CN.ts @@ -0,0 +1,71 @@ +const local: App.I18n.Schema = { + system: { + title: 'Soybean 管理系统' + }, + common: { + tip: '提示', + add: '添加', + addSuccess: '添加成功', + edit: '修改', + editSuccess: '修改成功', + delete: '删除', + deleteSuccess: '删除成功', + batchDelete: '批量删除', + confirm: '确认', + cancel: '取消', + pleaseCheckValue: '请检查输入的值是否合法', + action: '操作', + backToHome: '返回首页', + lookForward: '敬请期待', + userCenter: '个人中心', + logout: '退出登录', + logoutConfirm: '确认退出登录吗?' + }, + page: { + login: { + common: { + userNamePlaceholder: '请输入用户名', + phonePlaceholder: '请输入手机号', + codePlaceholder: '请输入验证码', + passwordPlaceholder: '请输入密码', + confirmPasswordPlaceholder: '请再次输入密码', + codeLogin: '验证码登录', + confirm: '确定', + back: '返回', + validateSuccess: '验证成功', + loginSuccess: '登录成功', + welcomeBack: '欢迎回来,{userName}!' + }, + pwdLogin: { + title: '密码登录', + rememberMe: '记住我', + forgetPassword: '忘记密码?', + register: '注册账号', + otherAccountLogin: '其他账号登录', + otherLoginMode: '其他登录方式', + superAdmin: '超级管理员', + admin: '管理员', + user: '普通用户' + }, + codeLogin: { + title: '验证码登录', + getCode: '获取验证码', + imageCodePlaceholder: '请输入图片验证码' + }, + register: { + title: '注册账号', + agreement: '我已经仔细阅读并接受', + protocol: '《用户协议》', + policy: '《隐私权政策》' + }, + resetPwd: { + title: '重置密码' + }, + bindWeChat: { + title: '绑定微信' + } + } + } +}; + +export default local; diff --git a/src/locales/locale.ts b/src/locales/locale.ts new file mode 100644 index 0000000..c7d37c0 --- /dev/null +++ b/src/locales/locale.ts @@ -0,0 +1,9 @@ +import zhCN from './lang/zh-CN'; +import en from './lang/en'; + +const locales: Record = { + 'zh-CN': zhCN, + en +}; + +export default locales; diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..a4e18dc --- /dev/null +++ b/src/main.ts @@ -0,0 +1,23 @@ +import { createApp } from 'vue'; +import './plugins/assets'; +import { setupNProgress } from './plugins'; +import { setupStore } from './store'; +import { setupRouter } from './router'; +import { setupI18n } from './locales'; +import App from './App.vue'; + +async function setupApp() { + setupNProgress(); + + const app = createApp(App); + + setupStore(app); + + await setupRouter(app); + + setupI18n(app); + + app.mount('#app'); +} + +setupApp(); diff --git a/src/plugins/assets.ts b/src/plugins/assets.ts new file mode 100644 index 0000000..4b1d9ae --- /dev/null +++ b/src/plugins/assets.ts @@ -0,0 +1,6 @@ +import 'virtual:svg-icons-register'; +import 'element-plus/es/components/message/style/css'; +import 'element-plus/es/components/message-box/style/css'; +import 'nprogress/nprogress.css'; +import 'uno.css'; +import '../styles/global.css'; diff --git a/src/plugins/index.ts b/src/plugins/index.ts new file mode 100644 index 0000000..d44606e --- /dev/null +++ b/src/plugins/index.ts @@ -0,0 +1 @@ +export * from './nprogress'; diff --git a/src/plugins/nprogress.ts b/src/plugins/nprogress.ts new file mode 100644 index 0000000..9cc18ac --- /dev/null +++ b/src/plugins/nprogress.ts @@ -0,0 +1,11 @@ +import NProgress from 'nprogress'; + +/** + * setup plugin NProgress + */ +export function setupNProgress() { + NProgress.configure({ easing: 'ease', speed: 500 }); + + // mount on window + window.NProgress = NProgress; +} diff --git a/src/router/elegant/imports.ts b/src/router/elegant/imports.ts new file mode 100644 index 0000000..3ba4ae4 --- /dev/null +++ b/src/router/elegant/imports.ts @@ -0,0 +1,28 @@ +/* eslint-disable */ +/* prettier-ignore */ +// Generated by elegant-router +// Read more: https://github.com/soybeanjs/elegant-router + +import type { RouteComponent } from "vue-router"; +import type { LastLevelRouteKey, RouteLayout } from "@elegant-router/types"; + +import BaseLayout from "@/layouts/base-layout/index.vue"; +import BlankLayout from "@/layouts/blank-layout/index.vue"; + +export const layouts: Record Promise)> = { + base: BaseLayout, + blank: BlankLayout, +}; + +export const views: Record Promise)> = { + 403: () => import("@/views/_builtin/403/index.vue"), + 404: () => import("@/views/_builtin/404/index.vue"), + 500: () => import("@/views/_builtin/500/index.vue"), + login: () => import("@/views/_builtin/login/index.vue"), + home: () => import("@/views/home/index.vue"), + "multi-menu_first_child": () => import("@/views/multi-menu/first_child/index.vue"), + "multi-menu_second_child_home": () => import("@/views/multi-menu/second_child_home/index.vue"), + "user-center": () => import("@/views/user-center/index.vue"), + user_detail: () => import("@/views/user/detail/[id].vue"), + user_list: () => import("@/views/user/list/index.vue"), +}; diff --git a/src/router/elegant/routes.ts b/src/router/elegant/routes.ts new file mode 100644 index 0000000..9ae6ad6 --- /dev/null +++ b/src/router/elegant/routes.ts @@ -0,0 +1,184 @@ +// Generated by elegant-router +// Read more: https://github.com/soybeanjs/elegant-router + +import type { ElegantRoute } from '@elegant-router/types'; + +export const autoRoutes: ElegantRoute[] = [ + { + path: '/403', + component: 'layout.base', + children: [ + { + name: '403', + path: '', + component: 'view.403', + meta: { + title: '403' + } + } + ] + }, + { + path: '/404', + component: 'layout.base', + children: [ + { + name: '404', + path: '', + component: 'view.404', + meta: { + title: '404' + } + } + ] + }, + { + path: '/500', + component: 'layout.base', + children: [ + { + name: '500', + path: '', + component: 'view.500', + meta: { + title: '500' + } + } + ] + }, + { + path: '/home', + component: 'layout.base', + children: [ + { + name: 'home', + path: '', + component: 'view.home', + meta: { + title: 'home', + requiresAuth: true + } + } + ] + }, + { + path: '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?', + component: 'layout.blank', + children: [ + { + name: 'login', + path: '', + component: 'view.login', + props: true, + meta: { + title: 'login' + } + } + ] + }, + { + name: 'multi-menu', + path: '/multi-menu', + component: 'layout.base', + redirect: { + name: 'multi-menu_first' + }, + meta: { + title: 'multi-menu', + requiresAuth: true + }, + children: [ + { + name: 'multi-menu_first', + path: '/multi-menu/first', + redirect: { + name: 'multi-menu_first_child' + }, + meta: { + title: 'multi-menu_first' + } + }, + { + name: 'multi-menu_first_child', + path: '/multi-menu/first/child', + component: 'view.multi-menu_first_child', + meta: { + title: 'multi-menu_first_child' + } + }, + { + name: 'multi-menu_second', + path: '/multi-menu/second', + redirect: { + name: 'multi-menu_second_child' + }, + meta: { + title: 'multi-menu_second' + } + }, + { + name: 'multi-menu_second_child', + path: '/multi-menu/second/child', + redirect: { + name: 'multi-menu_second_child_home' + }, + meta: { + title: 'multi-menu_second_child' + } + }, + { + name: 'multi-menu_second_child_home', + path: '/multi-menu/second/child/home', + component: 'view.multi-menu_second_child_home', + meta: { + title: 'multi-menu_second_child_home' + } + } + ] + }, + { + name: 'user', + path: '/user', + component: 'layout.base', + redirect: { + name: 'user_list' + }, + meta: { + title: 'user', + requiresAuth: true + }, + children: [ + { + name: 'user_detail', + path: '/user/detail/:id', + component: 'view.user_detail', + meta: { + title: 'user_detail' + } + }, + { + name: 'user_list', + path: '/user/list', + component: 'view.user_list', + meta: { + title: 'user_list' + } + } + ] + }, + { + path: '/user-center', + component: 'layout.base', + children: [ + { + name: 'user-center', + path: '', + component: 'view.user-center', + meta: { + title: 'user-center', + requiresAuth: true + } + } + ] + } +]; diff --git a/src/router/elegant/transform.ts b/src/router/elegant/transform.ts new file mode 100644 index 0000000..eb399d6 --- /dev/null +++ b/src/router/elegant/transform.ts @@ -0,0 +1,66 @@ +/* eslint-disable */ +/* prettier-ignore */ +// Generated by elegant-router +// Read more: https://github.com/soybeanjs/elegant-router + +import type { RouteRecordRaw, RouteComponent } from 'vue-router'; +import type { AutoRoute } from '@elegant-router/vue'; + +const LAYOUT_PREFIX = 'layout.'; +const VIEW_PREFIX = 'view.'; + +/** + * transform elegant route to vue route + * @param routes elegant routes + * @param layouts layout components + * @param views view components + */ +export function transformElegantRouteToVueRoute( + routes: AutoRoute[], + layouts: Record Promise)>, + views: Record Promise)> +) { + const vueRoutes: RouteRecordRaw[] = routes.map(route => { + const { children, component, ...rest } = route; + + const vueRoute = { ...rest } as RouteRecordRaw; + + if (component) { + if (isLayout(component)) { + const layoutName = getLayoutName(component); + + vueRoute.component = layouts[layoutName]; + } + + if (isView(component)) { + const viewName = getViewName(component); + + vueRoute.component = views[viewName]; + } + } + + if (children?.length) { + vueRoute.children = transformElegantRouteToVueRoute(children, layouts, views); + } + + return vueRoute; + }); + + return vueRoutes; +} + +function isLayout(component: string) { + return component.startsWith(LAYOUT_PREFIX); +} + +function getLayoutName(component: string) { + return component.replace(LAYOUT_PREFIX, ''); +} + +function isView(component: string) { + return component.startsWith(VIEW_PREFIX); +} + +function getViewName(component: string) { + return component.replace(VIEW_PREFIX, ''); +} diff --git a/src/router/guard/index.ts b/src/router/guard/index.ts new file mode 100644 index 0000000..0989119 --- /dev/null +++ b/src/router/guard/index.ts @@ -0,0 +1,16 @@ +import type { Router } from 'vue-router'; + +/** + * 路由守卫函数 + * @param router - 路由实例 + */ +export function createRouterGuard(router: Router) { + router.beforeEach(async (_to, _from, next) => { + window.NProgress?.start?.(); + + next(); + }); + router.afterEach(_to => { + window.NProgress?.done?.(); + }); +} diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..6fab953 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,32 @@ +import type { App } from 'vue'; +import { + createRouter, + createWebHistory, + createWebHashHistory, + createMemoryHistory, + type RouterHistory +} from 'vue-router'; +import { routes } from './routes'; +import { createRouterGuard } from './guard'; + +const { VITE_ROUTER_HISTORY_MODE = 'history', VITE_BASE_URL } = import.meta.env; + +const historyCreatorMap: Record RouterHistory> = { + hash: createWebHashHistory, + history: createWebHistory, + memory: createMemoryHistory +}; + +export const router = createRouter({ + history: historyCreatorMap[VITE_ROUTER_HISTORY_MODE](VITE_BASE_URL), + routes +}); + +/** + * setup Vue Router + */ +export async function setupRouter(app: App) { + app.use(router); + createRouterGuard(router); + await router.isReady(); +} diff --git a/src/router/routes/index.ts b/src/router/routes/index.ts new file mode 100644 index 0000000..08af590 --- /dev/null +++ b/src/router/routes/index.ts @@ -0,0 +1,29 @@ +import type { ElegantRoute, CustomRoute } from '@elegant-router/types'; +import { autoRoutes } from '../elegant/routes'; +import { layouts, views } from '../elegant/imports'; +import { transformElegantRouteToVueRoute } from '../elegant/transform'; + +const constantRoutes: CustomRoute[] = [ + { + name: 'root', + path: '/', + redirect: { + name: 'home' + } + }, + { + path: '/:pathMatch(.*)*', + component: 'layout.blank', + children: [ + { + name: 'not-found', + path: '', + component: 'view.404' + } + ] + } +]; + +const elegantRoutes: ElegantRoute[] = [...constantRoutes, ...autoRoutes]; + +export const routes = transformElegantRouteToVueRoute(elegantRoutes, layouts, views); diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..2e3b025 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,14 @@ +import type { App } from 'vue'; +import { createPinia } from 'pinia'; +import { resetSetupStore } from './plugins'; + +/** + * setup Vue store plugin pinia + */ +export function setupStore(app: App) { + const store = createPinia(); + + store.use(resetSetupStore); + + app.use(store); +} diff --git a/src/store/modules/auth/index.ts b/src/store/modules/auth/index.ts new file mode 100644 index 0000000..fd7bac0 --- /dev/null +++ b/src/store/modules/auth/index.ts @@ -0,0 +1,38 @@ +import { ref } from 'vue'; +import { defineStore } from 'pinia'; +import { useLoading } from '@sa/hooks'; +import { SetupStoreId } from '@/enum'; +import { useRouterPush } from '@/hooks/common/router'; +import { localStg } from '@/utils/storage'; + +export const useAuthStore = defineStore(SetupStoreId.Auth, () => { + const { route, toLogin } = useRouterPush(false); + const { loading: loginLoading, startLoading, endLoading } = useLoading(); + + const token = ref(localStg.get('token') || ''); + + async function resetStore() { + const auth = useAuthStore(); + + localStg.remove('token'); + + auth.$reset(); + + if (route.value.meta.requiresAuth) { + toLogin(); + } + } + + async function login() { + startLoading(); + + endLoading(); + } + + return { + token, + loginLoading, + resetStore, + login + }; +}); diff --git a/src/store/modules/route/index.ts b/src/store/modules/route/index.ts new file mode 100644 index 0000000..a2a69d2 --- /dev/null +++ b/src/store/modules/route/index.ts @@ -0,0 +1,6 @@ +import { defineStore } from 'pinia'; +import { SetupStoreId } from '@/enum'; + +export const useRouteStore = defineStore(SetupStoreId.Route, () => { + // +}); diff --git a/src/store/modules/theme/index.ts b/src/store/modules/theme/index.ts new file mode 100644 index 0000000..1c59326 --- /dev/null +++ b/src/store/modules/theme/index.ts @@ -0,0 +1,88 @@ +import { ref, computed, effectScope, onScopeDispose, watch, toRefs } from 'vue'; +import type { Ref } from 'vue'; +import { defineStore } from 'pinia'; +import { usePreferredColorScheme } from '@vueuse/core'; +import { SetupStoreId } from '@/enum'; +import { createThemeToken, initThemeSettings, setupThemeVarsToHtml, toggleCssDarkMode } from './shared'; + +/** + * theme store + */ +export const useThemeStore = defineStore(SetupStoreId.Theme, () => { + const scope = effectScope(); + const osTheme = usePreferredColorScheme(); + + const { themeTokens, darkThemeTokens } = createThemeToken(); + + /** + * theme settings + */ + const settings: Ref = ref(initThemeSettings(themeTokens.colors)); + + /** + * set color scheme + * @param colorScheme + */ + function setColorScheme(colorScheme: App.Theme.ColorScheme) { + settings.value.colorScheme = colorScheme; + } + + /** + * toggle color scheme + */ + function toggleColorScheme() { + const colorSchemes: App.Theme.ColorScheme[] = ['light', 'dark', 'auto']; + + const index = colorSchemes.findIndex(item => item === settings.value.colorScheme); + + const nextIndex = index === colorSchemes.length - 1 ? 0 : index + 1; + + const nextColorScheme = colorSchemes[nextIndex]; + + setColorScheme(nextColorScheme); + } + + /** + * dark mode + */ + const darkMode = computed(() => { + if (settings.value.colorScheme === 'auto') { + return osTheme.value === 'dark'; + } + + return settings.value.colorScheme === 'dark'; + }); + + function init() { + setupThemeVarsToHtml(themeTokens, darkThemeTokens); + } + + // watch store + scope.run(() => { + // watch dark mode + watch( + darkMode, + val => { + toggleCssDarkMode(val); + }, + { immediate: true } + ); + }); + + /** + * on scope dispose + */ + onScopeDispose(() => { + scope.stop(); + }); + + // init + init(); + + return { + ...toRefs(settings.value), + darkMode, + setColorScheme, + toggleColorScheme + }; +}); diff --git a/src/store/modules/theme/shared.ts b/src/store/modules/theme/shared.ts new file mode 100644 index 0000000..cdf9544 --- /dev/null +++ b/src/store/modules/theme/shared.ts @@ -0,0 +1,133 @@ +import { themeVars } from '@/theme/vars'; + +const DARK_CLASS = 'dark'; + +/** + * init theme settings + * @param darkMode is dark mode + */ +export function initThemeSettings(colors: App.Theme.ThemeTokenColor) { + const { primary: themeColor, info, success, warning, error } = colors; + + const themeSettings: App.Theme.ThemeSetting = { + colorScheme: 'light', + themeColor, + otherColor: { + info, + success, + warning, + error + } + }; + + return themeSettings; +} + +/** + * create theme token + * @param darkMode is dark mode + */ +export function createThemeToken() { + const themeTokens: App.Theme.ThemeToken = { + colors: { + primary: '#646cff', + info: '#2080f0', + success: '#52c41a', + warning: '#faad14', + error: '#f5222d', + container: 'rgba(255, 255, 255, 0.8)', + layout: 'rgba(247, 250, 252, 1)', + base_text: 'rgba(0, 0, 0, 0.88)' + }, + boxShadow: { + header: '0 1px 2px rgb(0, 21, 41, 0.08)', + sider: '2px 0 8px 0 rgb(29, 35, 41, 0.05)', + tab: '0 1px 2px rgb(0, 21, 41, 0.08)' + } + }; + + const darkThemeTokens: App.Theme.ThemeToken = { + colors: { + ...themeTokens.colors, + container: 'rgb(33, 33, 33)', + layout: 'rgb(18, 18, 18)', + base_text: 'rgba(255, 255, 255, 0.88)' + }, + boxShadow: { + ...themeTokens.boxShadow + } + }; + + return { + themeTokens, + darkThemeTokens + }; +} + +function getCssVarByTokens(tokens: App.Theme.BaseToken) { + const style: string[] = []; + + function removeVarPrefix(value: string) { + return value.replace('var(', '').replace(')', ''); + } + + for (const item of Object.entries(themeVars)) { + const [tokenKey, vars] = item; + + for (const varsItem of Object.entries(vars)) { + const [key, value] = varsItem; + + style.push(`${removeVarPrefix(value)}: ${tokens[tokenKey][key]}`); + } + } + + const styleStr = style.join(';'); + + return styleStr; +} + +/** + * add theme vars to html + * @param tokens + */ +export function setupThemeVarsToHtml(tokens: App.Theme.BaseToken, darkTokens: App.Theme.BaseToken) { + const cssVarStr = getCssVarByTokens(tokens); + const darkCssVarStr = getCssVarByTokens(darkTokens); + + const css = ` + html { + ${cssVarStr} + } + `; + + const darkCss = ` + html.${DARK_CLASS} { + ${darkCssVarStr} + `; + + const style = document.createElement('style'); + + style.innerText = css + darkCss; + + document.head.appendChild(style); +} + +/** + * toggle css dark mode + * @param darkMode + */ +export function toggleCssDarkMode(darkMode = false) { + function addDarkClass() { + document.documentElement.classList.add(DARK_CLASS); + } + + function removeDarkClass() { + document.documentElement.classList.remove(DARK_CLASS); + } + + if (darkMode) { + addDarkClass(); + } else { + removeDarkClass(); + } +} diff --git a/src/store/plugins/index.ts b/src/store/plugins/index.ts new file mode 100644 index 0000000..b36e114 --- /dev/null +++ b/src/store/plugins/index.ts @@ -0,0 +1,21 @@ +import type { PiniaPluginContext } from 'pinia'; +import { cloneDeep } from 'lodash-es'; +import { SetupStoreId } from '@/enum'; + +/** + * the plugin reset the state of the store which is written by setup syntax + * @param context + */ +export function resetSetupStore(context: PiniaPluginContext) { + const setupSyntaxIds = Object.values(SetupStoreId) as string[]; + + if (setupSyntaxIds.includes(context.store.$id)) { + const { $state } = context.store; + + const defaultStore = cloneDeep($state); + + context.store.$reset = () => { + context.store.$patch(defaultStore); + }; + } +} diff --git a/src/styles/global.css b/src/styles/global.css new file mode 100644 index 0000000..2ff98cc --- /dev/null +++ b/src/styles/global.css @@ -0,0 +1,7 @@ +@import "./reset.css"; + +html, +body, +#app { + height: 100%; +} diff --git a/src/styles/reset.css b/src/styles/reset.css new file mode 100644 index 0000000..c880b52 --- /dev/null +++ b/src/styles/reset.css @@ -0,0 +1,378 @@ +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +2. [UnoCSS]: allow to override the default border color with css var `--un-default-border-color` +*/ + +*, +::before, +::after { + box-sizing: border-box; /* 1 */ + border-width: 0; /* 2 */ + border-style: solid; /* 2 */ + border-color: var(--un-default-border-color, #e5e7eb); /* 2 */ +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +*/ + +html { + line-height: 1.5; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ + -moz-tab-size: 4; /* 3 */ + tab-size: 4; /* 3 */ + font-family: + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + "Helvetica Neue", + Arial, + "Noto Sans", + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji", + "Segoe UI Symbol", + "Noto Color Emoji"; /* 4 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; /* 1 */ + line-height: inherit; /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; /* 1 */ + color: inherit; /* 2 */ + border-top-width: 1px; /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font family by default. +2. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; /* 1 */ + border-color: inherit; /* 2 */ + border-collapse: collapse; /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-feature-settings: inherit; /* 1 */ + font-variation-settings: inherit; /* 1 */ + font-size: 100%; /* 1 */ + font-weight: inherit; /* 1 */ + line-height: inherit; /* 1 */ + color: inherit; /* 1 */ + margin: 0; /* 2 */ + padding: 0; /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; /* 1 */ + /* background-color: transparent; */ + background-image: none; /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::placeholder, +textarea::placeholder { + opacity: 1; /* 1 */ + color: #9ca3af; /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; /* 1 */ + vertical-align: middle; /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ +[hidden] { + display: none; +} diff --git a/src/theme/vars.ts b/src/theme/vars.ts new file mode 100644 index 0000000..f432d25 --- /dev/null +++ b/src/theme/vars.ts @@ -0,0 +1,17 @@ +export const themeVars: App.Theme.ThemeToken = { + colors: { + primary: 'var(--primary-color)', + info: 'var(--info-color)', + success: 'var(--success-color)', + warning: 'var(--warning-color)', + error: 'var(--error-color)', + container: 'var(--container-bg-color)', + layout: 'var(--layout-bg-color)', + base_text: 'var(--base-text-color)' + }, + boxShadow: { + header: 'var(--header-box-shadow)', + sider: 'var(--sider-box-shadow)', + tab: 'var(--tab-box-shadow)' + } +}; diff --git a/src/typings/app.d.ts b/src/typings/app.d.ts new file mode 100644 index 0000000..5fab792 --- /dev/null +++ b/src/typings/app.d.ts @@ -0,0 +1,167 @@ +/** + * the global namespace for the app + */ +declare namespace App { + /** + * theme namespace + */ + namespace Theme { + /** + * color scheme + */ + type ColorScheme = 'light' | 'dark' | 'auto'; + + /** + * theme setting + */ + interface ThemeSetting { + /** + * color scheme + */ + colorScheme: ColorScheme; + /** + * theme color + */ + themeColor: string; + /** + * other color + */ + otherColor: OtherColor; + } + + interface OtherColor { + info: string; + success: string; + warning: string; + error: string; + } + + type BaseToken = Record>; + + interface ThemeTokenColor extends OtherColor { + primary: string; + container: string; + layout: string; + base_text: string; + [key: string]: string; + } + + type ThemeToken = { + colors: ThemeTokenColor; + boxShadow: { + header: string; + sider: string; + tab: string; + }; + }; + } + + namespace Global { + /** + * the global menu + */ + interface Menu { + label: string; + } + } + + /** + * i18n namespace + * @description locales type + */ + namespace I18n { + type LangType = 'en' | 'zh-CN'; + + type Schema = { + system: { + title: string; + }; + common: { + tip: string; + add: string; + addSuccess: string; + edit: string; + editSuccess: string; + delete: string; + deleteSuccess: string; + batchDelete: string; + confirm: string; + cancel: string; + pleaseCheckValue: string; + action: string; + backToHome: string; + lookForward: string; + userCenter: string; + logout: string; + logoutConfirm: string; + }; + page: { + login: { + common: { + userNamePlaceholder: string; + phonePlaceholder: string; + codePlaceholder: string; + passwordPlaceholder: string; + confirmPasswordPlaceholder: string; + codeLogin: string; + confirm: string; + back: string; + validateSuccess: string; + loginSuccess: string; + welcomeBack: string; + }; + pwdLogin: { + title: string; + rememberMe: string; + forgetPassword: string; + register: string; + otherAccountLogin: string; + otherLoginMode: string; + superAdmin: string; + admin: string; + user: string; + }; + codeLogin: { + title: string; + getCode: string; + imageCodePlaceholder: string; + }; + register: { + title: string; + agreement: string; + protocol: string; + policy: string; + }; + resetPwd: { + title: string; + }; + bindWeChat: { + title: string; + }; + }; + }; + }; + + type GetI18nKey, K extends keyof T = keyof T> = K extends string + ? T[K] extends Record + ? `${K}.${GetI18nKey}` + : K + : never; + + type I18nKey = GetI18nKey; + + type TranslateOptions = import('vue-i18n').TranslateOptions; + + interface $T { + (key: I18nKey): string; + (key: I18nKey, plural: number, options?: TranslateOptions): string; + (key: I18nKey, defaultMsg: string, options?: TranslateOptions): string; + (key: I18nKey, list: unknown[], options?: TranslateOptions): string; + (key: I18nKey, list: unknown[], plural: number): string; + (key: I18nKey, list: unknown[], defaultMsg: string): string; + (key: I18nKey, named: Record, options?: TranslateOptions): string; + (key: I18nKey, named: Record, plural: number): string; + (key: I18nKey, named: Record, defaultMsg: string): string; + } + } +} diff --git a/src/typings/common.d.ts b/src/typings/common.d.ts new file mode 100644 index 0000000..5263393 --- /dev/null +++ b/src/typings/common.d.ts @@ -0,0 +1,18 @@ +/** + * the common type namespace + */ +declare namespace Common { + /** + * the strategy action type + * - 0: the condition + * - 1: the action function, which will be called when the condition is true + */ + type StrategyAction = [boolean, () => void]; + + /** + * the option type + * @property value: the option value + * @property label: the option label + */ + type Option = { value: K; label: string }; +} diff --git a/src/typings/components.d.ts b/src/typings/components.d.ts new file mode 100644 index 0000000..d6e941e --- /dev/null +++ b/src/typings/components.d.ts @@ -0,0 +1,48 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +declare module 'vue' { + export interface GlobalComponents { + AButton: typeof import('ant-design-vue/es')['Button'] + ACard: typeof import('ant-design-vue/es')['Card'] + ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] + ADivider: typeof import('ant-design-vue/es')['Divider'] + ADropdown: typeof import('ant-design-vue/es')['Dropdown'] + AForm: typeof import('ant-design-vue/es')['Form'] + AFormItem: typeof import('ant-design-vue/es')['FormItem'] + AInput: typeof import('ant-design-vue/es')['Input'] + AMenu: typeof import('ant-design-vue/es')['Menu'] + AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider'] + AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] + AppLoading: typeof import('./../components/common/app-loading.vue')['default'] + ASpace: typeof import('ant-design-vue/es')['Space'] + ColorSchemaSwitch: typeof import('./../components/common/color-schema-switch.vue')['default'] + DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default'] + ElButton: typeof import('element-plus/es')['ElButton'] + ElCard: typeof import('element-plus/es')['ElCard'] + ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] + ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] + ElDropdown: typeof import('element-plus/es')['ElDropdown'] + ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] + ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] + ElForm: typeof import('element-plus/es')['ElForm'] + ElFormItem: typeof import('element-plus/es')['ElFormItem'] + ElInput: typeof import('element-plus/es')['ElInput'] + ElSpace: typeof import('element-plus/es')['ElSpace'] + ExceptionBase: typeof import('./../components/common/exception-base.vue')['default'] + HoverContainer: typeof import('./../components/common/hover-container.vue')['default'] + IconLocalLogo: typeof import('~icons/local/logo')['default'] + IconLocalLogoFill: typeof import('~icons/local/logo-fill')['default'] + LookForward: typeof import('./../components/custom/look-forward.vue')['default'] + MenuToggler: typeof import('./../components/custom/menu-toggler.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + SvgIcon: typeof import('./../components/custom/svg-icon.vue')['default'] + SystemLogo: typeof import('./../components/common/system-logo.vue')['default'] + WaveBg: typeof import('./../components/custom/wave-bg.vue')['default'] + } +} diff --git a/src/typings/elegant-router.d.ts b/src/typings/elegant-router.d.ts new file mode 100644 index 0000000..73292db --- /dev/null +++ b/src/typings/elegant-router.d.ts @@ -0,0 +1,217 @@ +/* eslint-disable */ +/* prettier-ignore */ +// Generated by elegant-router +// Read more: https://github.com/soybeanjs/elegant-router + +declare module "@elegant-router/types" { + type RouteRecordRaw = import("vue-router").RouteRecordRaw; + + /** + * route layout + */ + export type RouteLayout = "base" | "blank"; + + /** + * route map + */ + export type RouteMap = { + "root": "/"; + "not-found": "/:pathMatch(.*)*"; + "403": "/403"; + "404": "/404"; + "500": "/500"; + "home": "/home"; + "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"; + "multi-menu": "/multi-menu"; + "multi-menu_first": "/multi-menu/first"; + "multi-menu_first_child": "/multi-menu/first/child"; + "multi-menu_second": "/multi-menu/second"; + "multi-menu_second_child": "/multi-menu/second/child"; + "multi-menu_second_child_home": "/multi-menu/second/child/home"; + "user": "/user"; + "user_detail": "/user/detail/:id"; + "user_list": "/user/list"; + "user-center": "/user-center"; + }; + + /** + * route key + */ + export type RouteKey = keyof RouteMap; + + /** + * custom route key + */ + export type CustomRouteKey = Extract< + RouteKey, + | "root" + | "not-found" + >; + + /** + * the auto generated route key + */ + export type AutoRouteKey = Exclude; + + /** + * the first level route key, which contain the layout of the route + */ + export type FirstLevelRouteKey = Extract< + RouteKey, + | "403" + | "404" + | "500" + | "home" + | "login" + | "multi-menu" + | "user" + | "user-center" + >; + + /** + * the last level route key, which has the page file + */ + export type LastLevelRouteKey = Extract< + RouteKey, + | "403" + | "404" + | "500" + | "login" + | "home" + | "multi-menu_first_child" + | "multi-menu_second_child_home" + | "user-center" + | "user_detail" + | "user_list" + >; + + /** + * the last level route key as child + */ + export type LastLevelChildRouteKey = Exclude; + + /** + * the single level route key + */ + export type SingleLevelRouteKey = FirstLevelRouteKey & LastLevelRouteKey; + + /** + * the first level route key, but not the single level + */ + export type FirstLevelRouteNotSingleKey = Exclude; + + /** + * the center level route key + */ + export type CenterLevelRouteKey = Exclude; + + /** + * the center level route key + */ + type GetChildRouteKey = T extends `${K}${infer R}` ? (R extends '' ? never : T) : never; + + /** + * the child of single level route + */ + type SingleLevelRouteChild = Omit & { + name: N; + path: ''; + component: `view.${K}`; + }; + + /** + * the single level route + */ + type SingleLevelRoute = K extends string + ? Omit & { + path: RouteMap[K]; + component: `layout.${RouteLayout}`; + children: [SingleLevelRouteChild]; + } + : never; + + /** + * the center level route + */ + type CenterLevelRoute = K extends string + ? Omit & { + name: K; + path: RouteMap[K]; + redirect: { + name: GetChildRouteKey; + }; + } + : never; + + /** + * the last level route + */ + type LastLevelRoute = K extends string + ? Omit & { + name: K; + path: RouteMap[K]; + component: `view.${K}`; + } + : never; + + /** + * the multi level route + */ + type MultiLevelRoute = K extends string + ? Omit & { + name: K; + path: RouteMap[K]; + component: `layout.${RouteLayout}`; + redirect: { + name: GetChildRouteKey; + }; + children: (CenterLevelRoute> | LastLevelRoute>)[]; + } + : never; + + /** + * the custom first level route + */ + type CustomSingleLevelRoute = K extends string + ? Omit & { + path: RouteMap[K]; + component: `layout.${RouteLayout}`; + children: [SingleLevelRouteChild]; + } + : never; + + /** + * the custom multi level route + */ + type CustomMultiLevelRoute = K extends string + ? Omit & { + name: K; + path: RouteMap[K]; + component: `layout.${RouteLayout}`; + children: (CenterLevelRoute | LastLevelRoute)[]; + } + : never; + + /** + * the custom redirect route + */ + type CustomRedirectRoute = K extends string + ? Omit & { + name: K; + path: RouteMap[K]; + redirect: { + name: Exclude; + }; + } + : never; + + /** + * the custom route + */ + type CustomRoute = CustomSingleLevelRoute | CustomMultiLevelRoute | CustomRedirectRoute; + + /** + * the elegant route + */ + type ElegantRoute = SingleLevelRoute | MultiLevelRoute | CustomRoute; +} diff --git a/src/typings/env.d.ts b/src/typings/env.d.ts new file mode 100644 index 0000000..34ffb4c --- /dev/null +++ b/src/typings/env.d.ts @@ -0,0 +1,50 @@ +/** + * namespace Env + * @description it is used to declare the type of the import.meta object + */ +declare namespace Env { + type YesOrNo = 'Y' | 'N'; + + /** + * the router history mode + */ + type RouterHistoryMode = 'hash' | 'history' | 'memory'; + + /** + * interface for import.meta + */ + interface ImportMeta extends ImportMetaEnv { + /** + * the base url of the application + */ + readonly VITE_BASE_URL: string; + /** + * the title of the application + */ + readonly VITE_APP_TITLE: string; + /** + * the description of the application + */ + readonly VITE_APP_DESC: string; + /** + * whether to enable the http proxy + * @description only valid in the development environment + */ + readonly VITE_HTTP_PROXY?: YesOrNo; + /** + * the router history mode + */ + readonly VITE_ROUTER_HISTORY_MODE?: RouterHistoryMode; + + /** + * the prefix of the iconify icon + */ + readonly VITE_ICON_PREFIX: 'icon'; + + /** + * the prefix of the local icon + * @description this prefix is start with the icon prefix + */ + readonly VITE_ICON_LOCAL_PREFIX: 'local-icon'; + } +} diff --git a/src/typings/global.d.ts b/src/typings/global.d.ts new file mode 100644 index 0000000..fc451da --- /dev/null +++ b/src/typings/global.d.ts @@ -0,0 +1,18 @@ +interface Window { + /** + * NProgress instance + */ + NProgress?: import('nprogress').NProgress; +} + +interface ViewTransition { + ready: Promise; +} + +interface Document { + startViewTransition?: (callback: () => Promise | void) => ViewTransition; +} + +interface ImportMeta { + readonly env: Env.ImportMeta; +} diff --git a/src/typings/router.d.ts b/src/typings/router.d.ts new file mode 100644 index 0000000..5b5fbae --- /dev/null +++ b/src/typings/router.d.ts @@ -0,0 +1,20 @@ +import 'vue-router'; + +declare module 'vue-router' { + interface RouteMeta { + /** + * title of the route + * @description it can be used in document title + */ + title: string; + /** + * i18n key of the route + * @description it's used in i18n, if it is set, the title will be ignored + */ + i18nKey?: App.I18n.I18nKey; + /** + * whether to require authentication + */ + requiresAuth?: boolean; + } +} diff --git a/src/typings/storage.d.ts b/src/typings/storage.d.ts new file mode 100644 index 0000000..1c0bc9d --- /dev/null +++ b/src/typings/storage.d.ts @@ -0,0 +1,21 @@ +/** + * the storage namespace + */ +declare namespace StorageType { + interface Session { + /** + * the theme color + */ + themeColor: string; + /** + * the theme settings + */ + themeSettings: App.Theme.ThemeSetting; + } + + interface Local { + token: string; + refreshToken: string; + lang: App.I18n.LangType; + } +} diff --git a/src/typings/union-key.d.ts b/src/typings/union-key.d.ts new file mode 100644 index 0000000..39a2fda --- /dev/null +++ b/src/typings/union-key.d.ts @@ -0,0 +1,156 @@ +/** + * the union key namespace + */ +declare namespace UnionKey { + /** + * the content-type of http request header + */ + type ContentType = 'application/json' | 'application/x-www-form-urlencoded' | 'multipart/form-data'; + + /** + * the login module + * - pwd-login: password login + * - code-login: phone code login + * - register: register + * - reset-pwd: reset password + * - bind-wechat: bind wechat + */ + type LoginModule = 'pwd-login' | 'code-login' | 'register' | 'reset-pwd' | 'bind-wechat'; + + /** + * the layout mode + * - vertical: the vertical menu in left + * - horizontal: the horizontal menu in top + * - vertical-mix: two vertical mixed menus in left + * - horizontal-mix: the vertical menu in left and horizontal menu in top + */ + type ThemeLayoutMode = 'vertical' | 'horizontal' | 'vertical-mix' | 'horizontal-mix'; + + /** + * the scroll mode when content overflow + * - wrapper the wrapper component's root element overflow + * - content the content component overflow + */ + type ThemeScrollMode = import('@sa/materials').LayoutScrollMode; + + /** + * tab mode + * - chrome: chrome style + * - button: button style + */ + type ThemeTabMode = import('@sa/materials').PageTabMode; + + /** + * the menu position in horizontal mode + * - flex-start: left position + * - center: center position + * - flex-end: right position + */ + type ThemeHorizontalMenuPosition = 'flex-start' | 'center' | 'flex-end'; + + /** + * the theme animate mode + */ + type ThemeAnimateMode = + | 'pulse' + | 'bounce' + | 'spin' + | 'ping' + | 'bounce-alt' + | 'flash' + | 'pulse-alt' + | 'rubber-band' + | 'shake-x' + | 'shake-y' + | 'head-shake' + | 'swing' + | 'tada' + | 'wobble' + | 'jello' + | 'heart-beat' + | 'hinge' + | 'jack-in-the-box' + | 'light-speed-in-left' + | 'light-speed-in-right' + | 'light-speed-out-left' + | 'light-speed-out-right' + | 'flip' + | 'flip-in-x' + | 'flip-in-y' + | 'flip-out-x' + | 'flip-out-y' + | 'rotate-in' + | 'rotate-in-down-left' + | 'rotate-in-down-right' + | 'rotate-in-up-left' + | 'rotate-in-up-right' + | 'rotate-out' + | 'rotate-out-down-left' + | 'rotate-out-down-right' + | 'rotate-out-up-left' + | 'rotate-out-up-right' + | 'roll-in' + | 'roll-out' + | 'zoom-in' + | 'zoom-in-down' + | 'zoom-in-left' + | 'zoom-in-right' + | 'zoom-in-up' + | 'zoom-out' + | 'zoom-out-down' + | 'zoom-out-left' + | 'zoom-out-right' + | 'zoom-out-up' + | 'bounce-in' + | 'bounce-in-down' + | 'bounce-in-left' + | 'bounce-in-right' + | 'bounce-in-up' + | 'bounce-out' + | 'bounce-out-down' + | 'bounce-out-left' + | 'bounce-out-right' + | 'bounce-out-up' + | 'slide-in-down' + | 'slide-in-left' + | 'slide-in-right' + | 'slide-in-up' + | 'slide-out-down' + | 'slide-out-left' + | 'slide-out-right' + | 'slide-out-up' + | 'fade-in' + | 'fade-in-down' + | 'fade-in-down-big' + | 'fade-in-left' + | 'fade-in-left-big' + | 'fade-in-right' + | 'fade-in-right-big' + | 'fade-in-up' + | 'fade-in-up-big' + | 'fade-in-top-left' + | 'fade-in-top-right' + | 'fade-in-bottom-left' + | 'fade-in-bottom-right' + | 'fade-out' + | 'fade-out-down' + | 'fade-out-down-big' + | 'fade-out-left' + | 'fade-out-left-big' + | 'fade-out-right' + | 'fade-out-right-big' + | 'fade-out-up' + | 'fade-out-up-big' + | 'fade-out-top-left' + | 'fade-out-top-right' + | 'fade-out-bottom-left' + | 'fade-out-bottom-right' + | 'back-in-up' + | 'back-in-down' + | 'back-in-right' + | 'back-in-left' + | 'back-out-up' + | 'back-out-down' + | 'back-out-right' + | 'back-out-left'; +} diff --git a/src/utils/common.ts b/src/utils/common.ts new file mode 100644 index 0000000..a940394 --- /dev/null +++ b/src/utils/common.ts @@ -0,0 +1,6 @@ +export function transformObjectToOption(obj: T) { + return Object.entries(obj).map(([value, label]) => ({ + value, + label + })) as Common.Option[]; +} diff --git a/src/utils/storage.ts b/src/utils/storage.ts new file mode 100644 index 0000000..0f98f06 --- /dev/null +++ b/src/utils/storage.ts @@ -0,0 +1,7 @@ +import { createStorage, createLocalforage } from '@sa/utils'; + +export const localStg = createStorage('local'); + +export const sessionStg = createStorage('session'); + +export const localforage = createLocalforage('local'); diff --git a/src/views/_builtin/403/index.vue b/src/views/_builtin/403/index.vue new file mode 100644 index 0000000..3c59ddf --- /dev/null +++ b/src/views/_builtin/403/index.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/src/views/_builtin/404/index.vue b/src/views/_builtin/404/index.vue new file mode 100644 index 0000000..4b4bf5b --- /dev/null +++ b/src/views/_builtin/404/index.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/src/views/_builtin/500/index.vue b/src/views/_builtin/500/index.vue new file mode 100644 index 0000000..0028cf2 --- /dev/null +++ b/src/views/_builtin/500/index.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/src/views/_builtin/login/components/bind-wechat.vue b/src/views/_builtin/login/components/bind-wechat.vue new file mode 100644 index 0000000..cb417bf --- /dev/null +++ b/src/views/_builtin/login/components/bind-wechat.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/src/views/_builtin/login/components/code-login.vue b/src/views/_builtin/login/components/code-login.vue new file mode 100644 index 0000000..52a7fa2 --- /dev/null +++ b/src/views/_builtin/login/components/code-login.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/views/_builtin/login/components/pwd-login.vue b/src/views/_builtin/login/components/pwd-login.vue new file mode 100644 index 0000000..aff7f53 --- /dev/null +++ b/src/views/_builtin/login/components/pwd-login.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/views/_builtin/login/components/register.vue b/src/views/_builtin/login/components/register.vue new file mode 100644 index 0000000..6324a54 --- /dev/null +++ b/src/views/_builtin/login/components/register.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/views/_builtin/login/components/reset-pwd.vue b/src/views/_builtin/login/components/reset-pwd.vue new file mode 100644 index 0000000..50ca816 --- /dev/null +++ b/src/views/_builtin/login/components/reset-pwd.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/src/views/_builtin/login/index.vue b/src/views/_builtin/login/index.vue new file mode 100644 index 0000000..9bd54a4 --- /dev/null +++ b/src/views/_builtin/login/index.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/src/views/home/index.vue b/src/views/home/index.vue new file mode 100644 index 0000000..2a654c5 --- /dev/null +++ b/src/views/home/index.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/src/views/multi-menu/first_child/index.vue b/src/views/multi-menu/first_child/index.vue new file mode 100644 index 0000000..9d218d1 --- /dev/null +++ b/src/views/multi-menu/first_child/index.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/src/views/multi-menu/second_child_home/index.vue b/src/views/multi-menu/second_child_home/index.vue new file mode 100644 index 0000000..9d218d1 --- /dev/null +++ b/src/views/multi-menu/second_child_home/index.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/src/views/user-center/index.vue b/src/views/user-center/index.vue new file mode 100644 index 0000000..2a654c5 --- /dev/null +++ b/src/views/user-center/index.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/src/views/user/detail/[id].vue b/src/views/user/detail/[id].vue new file mode 100644 index 0000000..2a654c5 --- /dev/null +++ b/src/views/user/detail/[id].vue @@ -0,0 +1,7 @@ + + + + + diff --git a/src/views/user/list/index.vue b/src/views/user/list/index.vue new file mode 100644 index 0000000..2a654c5 --- /dev/null +++ b/src/views/user/list/index.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a3a55b0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "module": "ESNext", + "target": "ESNext", + "lib": ["DOM", "ESNext"], + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "moduleResolution": "node", + "isolatedModules": true, + "resolveJsonModule": true, + "noUnusedLocals": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "paths": { + "@/*": ["./src/*"] + }, + "types": [ + "vite/client", + "node", + "unplugin-icons/types/vue", + "element-plus/global.d.ts" + ] + }, + "include": ["./**/*.ts", "./**/*.tsx", "./**/*.vue"], + "exclude": ["node_modules", "dist"] +} diff --git a/uno.config.ts b/uno.config.ts new file mode 100644 index 0000000..2a261f5 --- /dev/null +++ b/uno.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from '@unocss/vite'; +import transformerDirectives from '@unocss/transformer-directives'; +import transformerVariantGroup from '@unocss/transformer-variant-group'; +import presetUno from '@unocss/preset-uno'; +import { presetScrollbar } from 'unocss-preset-scrollbar'; +import type { Theme } from '@unocss/preset-uno'; +import { presetSoybeanAdmin } from '@sa/uno-preset'; +import { themeVars } from './src/theme/vars'; + +export default defineConfig({ + content: { + pipeline: { + exclude: ['node_modules', 'dist'] + } + }, + theme: { + ...themeVars, + fontSize: { + 'icon-xs': '0.875rem', + 'icon-small': '1rem', + icon: '1.125rem', + 'icon-large': '1.5rem', + 'icon-xl': '2rem' + } + }, + transformers: [transformerDirectives(), transformerVariantGroup()], + presets: [presetUno({ dark: 'class' }), presetScrollbar(), presetSoybeanAdmin()] +}); diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..3da9802 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,30 @@ +import { fileURLToPath, URL } from 'node:url'; +import { defineConfig, loadEnv } from 'vite'; +import { setupVitePlugins } from './build'; + +export default defineConfig(configEnv => { + const viteEnv = loadEnv(configEnv.mode, process.cwd()) as unknown as Env.ImportMeta; + + return { + base: viteEnv.VITE_BASE_URL, + resolve: { + alias: { + '~': fileURLToPath(new URL('./', import.meta.url)), + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, + plugins: setupVitePlugins(viteEnv), + server: { + host: '0.0.0.0', + port: 9527, + open: true + }, + build: { + reportCompressedSize: false, + sourcemap: false, + commonjsOptions: { + ignoreTryCatch: false + } + } + }; +});