diff --git a/docs/src/installation.njk b/docs/src/installation.njk index dacc504..fe3d4cb 100644 --- a/docs/src/installation.njk +++ b/docs/src/installation.njk @@ -19,6 +19,8 @@ toc: id: install-npm-basecoat - label: Import basecoat in your CSS id: install-npm-import + - label: (Optional) Import specific components + id: install-npm-import-css-components-optional - label: (Optional) Add JavaScript files id: install-npm-js - label: That's it @@ -99,6 +101,14 @@ toc: @import "basecoat-css";{% endset %} {{ code_block(code, "css") }} +
  • +

    (Optional) Import specific components

    + {% set code %}@import "tailwindcss"; +@import "basecoat-css/base"; +@import "basecoat-css/components/form"; +@import "basecoat-css/components/alert";{% endset %} + {{ code_block(code, "css") }} +
  • (Optional) Add JavaScript files

    diff --git a/package-lock.json b/package-lock.json index df412f2..6a63c20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "basecoat", - "version": "0.3.2", + "version": "0.3.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "basecoat", - "version": "0.3.2", + "version": "0.3.6", "license": "MIT", "workspaces": [ "packages/*" @@ -265,6 +265,15 @@ "lucide-static": "^0.486.0" } }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@inquirer/checkbox": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.5.tgz", @@ -311,19 +320,19 @@ } }, "node_modules/@inquirer/core": { - "version": "10.1.10", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.10.tgz", - "integrity": "sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw==", + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", "license": "MIT", "dependencies": { - "@inquirer/figures": "^1.0.11", - "@inquirer/type": "^3.0.6", - "ansi-escapes": "^4.3.2", + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -393,14 +402,14 @@ } }, "node_modules/@inquirer/editor": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.10.tgz", - "integrity": "sha512-5GVWJ+qeI6BzR6TIInLP9SXhWCEcvgFQYmcRG6d6RIlhFjM5TyG18paTGBgRYyEouvCmzeco47x9zX9tQEofkw==", + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.10", - "@inquirer/type": "^3.0.6", - "external-editor": "^3.1.0" + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" }, "engines": { "node": ">=18" @@ -436,10 +445,31 @@ } } }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@inquirer/figures": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.11.tgz", - "integrity": "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "license": "MIT", "engines": { "node": ">=18" @@ -608,9 +638,9 @@ } }, "node_modules/@inquirer/type": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.6.tgz", - "integrity": "sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "license": "MIT", "engines": { "node": ">=18" @@ -1588,9 +1618,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1649,10 +1679,9 @@ } }, "node_modules/chardet": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", - "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", - "dev": true, + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", "license": "MIT" }, "node_modules/chokidar": { @@ -2120,26 +2149,6 @@ "node": ">=0.10.0" } }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/external-editor/node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "license": "MIT" - }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -2293,9 +2302,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -2327,9 +2336,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2385,9 +2394,9 @@ } }, "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -2469,15 +2478,19 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/inherits": { @@ -2666,9 +2679,9 @@ } }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -3329,15 +3342,6 @@ "node": ">= 0.8" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -3435,6 +3439,7 @@ "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "posthtml-parser": "^0.11.0", "posthtml-render": "^3.0.0" @@ -4010,18 +4015,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4324,9 +4317,9 @@ } }, "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", "license": "MIT", "engines": { "node": ">=18" @@ -4337,7 +4330,7 @@ }, "packages/cli": { "name": "basecoat-cli", - "version": "0.3.2", + "version": "0.3.6", "license": "MIT", "dependencies": { "commander": "^13.1.0", @@ -4350,7 +4343,7 @@ }, "packages/css": { "name": "basecoat-css", - "version": "0.3.2", + "version": "0.3.6", "license": "MIT" } } diff --git a/packages/css/package.json b/packages/css/package.json index c118258..0b974ac 100644 --- a/packages/css/package.json +++ b/packages/css/package.json @@ -58,6 +58,86 @@ "./tabs.min": "./dist/js/tabs.min.js", "./toast": "./dist/js/toast.js", "./toast.min": "./dist/js/toast.min.js", - "./package.json": "./package.json" + "./package.json": "./package.json", + "./all.css": "./dist/basecoat.all.css", + "./base": "./dist/base/base.css", + "./components/accordion": { + "style": "./dist/components/accordion.css" + }, + "./components/alert": { + "style": "./dist/components/alert.css" + }, + "./components/badge": { + "style": "./dist/components/badge.css" + }, + "./components/button-group": { + "style": "./dist/components/button-group.css" + }, + "./components/button": { + "style": "./dist/components/button.css" + }, + "./components/card": { + "style": "./dist/components/card.css" + }, + "./components/command": { + "style": "./dist/components/command.css" + }, + "./components/dialog": { + "style": "./dist/components/dialog.css" + }, + "./components/dropdown-menu": { + "style": "./dist/components/dropdown-menu.css" + }, + "./components/field": { + "style": "./dist/components/field.css" + }, + "./components/form/checkbox": { + "style": "./dist/components/form/checkbox.css" + }, + "./components/form/input": { + "style": "./dist/components/form/input.css" + }, + "./components/form/label": { + "style": "./dist/components/form/label.css" + }, + "./components/form/radio": { + "style": "./dist/components/form/radio.css" + }, + "./components/form/range": { + "style": "./dist/components/form/range.css" + }, + "./components/form/select": { + "style": "./dist/components/form/select.css" + }, + "./components/form/switch": { + "style": "./dist/components/form/switch.css" + }, + "./components/form/textarea": { + "style": "./dist/components/form/textarea.css" + }, + "./components/form": { + "style": "./dist/components/form.css" + }, + "./components/kbd": { + "style": "./dist/components/kbd.css" + }, + "./components/popover": { + "style": "./dist/components/popover.css" + }, + "./components/sidebar": { + "style": "./dist/components/sidebar.css" + }, + "./components/table": { + "style": "./dist/components/table.css" + }, + "./components/tabs": { + "style": "./dist/components/tabs.css" + }, + "./components/toast": { + "style": "./dist/components/toast.css" + }, + "./components/tooltip": { + "style": "./dist/components/tooltip.css" + } } } \ No newline at end of file diff --git a/scripts/build.js b/scripts/build.js index 261c373..2113eaf 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -63,6 +63,24 @@ async function copyDirRecursive(src, dest) { } } +// Recursively adds CSS component exports +async function collectComponentCssExports(srcDir, exportsMap, relativePath = '') { + const entries = await fs.readdir(srcDir, { withFileTypes: true }); + + for (const entry of entries) { + const entryPath = path.join(srcDir, entry.name); + const subPath = path.posix.join(relativePath, entry.name); + + if (entry.isDirectory()) { + await collectComponentCssExports(entryPath, exportsMap, subPath); + } else if (entry.name.endsWith('.css')) { + const exportKey = `./components/${subPath.replace(/\.css$/, '')}`; + const stylePath = `./dist/components/${subPath}`; + exportsMap[exportKey] = { style: stylePath }; + } + } +} + // Main build function to prepare packages for publishing. async function build() { console.log('Starting build process...'); @@ -161,6 +179,63 @@ async function build() { await execPromise(`npx tailwindcss -i "${cdnCssSrc}" -o "${cssDistCdnMinPath}" --minify`); console.log(`Generated minified CSS: ${cssDistCdnMinPath}`); + // Copy basecoat.all.css + await fs.copyFile(path.join(srcCssDir, 'basecoat.all.css'), path.join(cssDistDir, 'basecoat.all.css')); + console.log(`Copied basecoat.all.css to ${cssDistDir}`); + + // Copy base/base.css + const baseSrcDir = path.join(srcCssDir, 'base'); + const baseDistDir = path.join(cssDistDir, 'base'); + await ensureDir(baseDistDir); + await fs.copyFile(path.join(baseSrcDir, 'base.css'), path.join(baseDistDir, 'base.css')); + console.log(`Copied base/base.css to ${cssDistDir}`); + + // Copy components + console.log('Copying individual CSS components...'); + const componentsSrcDir = path.join(srcCssDir, 'components'); + const componentsDistDir = path.join(cssDistDir, 'components'); + await copyDirRecursive(componentsSrcDir, componentsDistDir); + console.log(`Copied component CSS files to ${componentsDistDir}`); + + // Read package.json + const cssPackageJsonPath = path.join(cssPackageDir, 'package.json'); + const cssPackageJson = JSON.parse(await fs.readFile(cssPackageJsonPath, 'utf-8')); + + // Base exports + const exportsMap = { + ".": "./dist/basecoat.css", + "./css": "./dist/basecoat.css", + "./basecoat": "./dist/js/basecoat.js", + "./basecoat.min": "./dist/js/basecoat.min.js", + "./all": "./dist/js/all.js", + "./all.min": "./dist/js/all.min.js", + "./command": "./dist/js/command.js", + "./command.min": "./dist/js/command.min.js", + "./dropdown-menu": "./dist/js/dropdown-menu.js", + "./dropdown-menu.min": "./dist/js/dropdown-menu.min.js", + "./popover": "./dist/js/popover.js", + "./popover.min": "./dist/js/popover.min.js", + "./select": "./dist/js/select.js", + "./select.min": "./dist/js/select.min.js", + "./sidebar": "./dist/js/sidebar.js", + "./sidebar.min": "./dist/js/sidebar.min.js", + "./tabs": "./dist/js/tabs.js", + "./tabs.min": "./dist/js/tabs.min.js", + "./toast": "./dist/js/toast.js", + "./toast.min": "./dist/js/toast.min.js", + "./package.json": "./package.json", + "./all.css": "./dist/basecoat.all.css", + "./base": "./dist/base/base.css" + }; + + // Add dynamic component exports + await collectComponentCssExports(componentsSrcDir, exportsMap); + + // Write updated package.json + cssPackageJson.exports = exportsMap; + await fs.writeFile(cssPackageJsonPath, JSON.stringify(cssPackageJson, null, 2)); + console.log('Updated package.json with dynamic CSS component exports.'); + console.log('Build process finished successfully!'); } diff --git a/src/css/base/base.css b/src/css/base/base.css new file mode 100644 index 0000000..8ac8fc7 --- /dev/null +++ b/src/css/base/base.css @@ -0,0 +1,149 @@ +@custom-variant dark (&:is(.dark *)); + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); + --sidebar-width: 16rem; + --sidebar-mobile-width: 18rem; + --scrollbar-track: transparent; + --scrollbar-thumb: rgba(0, 0, 0, 0.3); + --scrollbar-width: 6px; + --scrollbar-radius: 6px; + --chevron-down-icon: url('data:image/svg+xml;utf8,'); /* --muted-foreground */ + --chevron-down-icon-50: url('data:image/svg+xml;utf8,'); /* --muted-foreground + 50% opacity */ + --check-icon: url('data:image/svg+xml;utf8,'); /* --muted-foreground */ +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.269 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.371 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.439 0 0); + --scrollbar-thumb: rgba(255, 255, 255, 0.3); + --chevron-down-icon: url('data:image/svg+xml;utf8,'); /* --muted-foreground */ + --chevron-down-icon-50: url('data:image/svg+xml;utf8,'); /* --muted-foreground + 50% opacity */ + --check-icon: url('data:image/svg+xml;utf8,');/* --muted-foreground */ + color-scheme: dark; +} + +@theme { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + html { + @apply scroll-smooth; + } + body { + @apply bg-background text-foreground overscroll-none antialiased; + } + .scrollbar { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); + + &::-webkit-scrollbar { + width: var(--scrollbar-width); + } + &::-webkit-scrollbar-track { + background: var(--scrollbar-track); + } + &::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: var(--scrollbar-radius); + } + } +} \ No newline at end of file diff --git a/src/css/basecoat.all.css b/src/css/basecoat.all.css new file mode 100644 index 0000000..7a61125 --- /dev/null +++ b/src/css/basecoat.all.css @@ -0,0 +1,21 @@ +@import "./base/base.css"; + +/* Components */ +@import "./components/alert.css"; +@import "./components/badge.css"; +@import "./components/button.css"; +@import "./components/button-group.css"; +@import "./components/card.css"; +@import "./components/form.css"; +@import "./components/accordion.css"; +@import "./components/command.css"; +@import "./components/dialog.css"; +@import "./components/dropdown-menu.css"; +@import "./components/field.css"; +@import "./components/kbd.css"; +@import "./components/popover.css"; +@import "./components/sidebar.css"; +@import "./components/table.css"; +@import "./components/tabs.css"; +@import "./components/toast.css"; +@import "./components/tooltip.css"; \ No newline at end of file diff --git a/src/css/components/accordion.css b/src/css/components/accordion.css new file mode 100644 index 0000000..6b06941 --- /dev/null +++ b/src/css/components/accordion.css @@ -0,0 +1,20 @@ +/* Accordion / Collapsible */ +@layer components { + details { + &::details-content { + block-size: 0; + @apply block opacity-0 transition-discrete transition-all; + } + &[open]::details-content { + block-size: auto; + block-size: calc-size(auto, size); + @apply opacity-100; + } + summary { + @apply inline-flex items-center cursor-pointer; + } + } + details > summary::-webkit-details-marker { + display: none; + } +} \ No newline at end of file diff --git a/src/css/components/alert.css b/src/css/components/alert.css new file mode 100644 index 0000000..596f682 --- /dev/null +++ b/src/css/components/alert.css @@ -0,0 +1,29 @@ +/* Alert */ +@layer components { + .alert, + .alert-destructive { + @apply relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current; + + h2, + h3 { + @apply col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight; + } + section { + @apply text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed; + + ul { + @apply list-inside list-disc text-sm; + } + } + } + .alert { + @apply bg-card text-card-foreground; + } + .alert-destructive { + @apply text-destructive bg-card [&>svg]:text-current; + + section { + @apply text-destructive; + } + } +} \ No newline at end of file diff --git a/src/css/components/badge.css b/src/css/components/badge.css new file mode 100644 index 0000000..7c75533 --- /dev/null +++ b/src/css/components/badge.css @@ -0,0 +1,23 @@ +/* Badge */ +@layer components { + .badge, + .badge-primary, + .badge-secondary, + .badge-destructive, + .badge-outline { + @apply inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden; + } + .badge, + .badge-primary { + @apply border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90; + } + .badge-secondary { + @apply border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90; + } + .badge-destructive { + @apply border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60; + } + .badge-outline { + @apply text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground; + } +} \ No newline at end of file diff --git a/src/css/components/button-group.css b/src/css/components/button-group.css new file mode 100644 index 0000000..cd886d9 --- /dev/null +++ b/src/css/components/button-group.css @@ -0,0 +1,42 @@ +/* Button Group */ +@layer components { + .button-group { + @apply inline-flex w-fit items-stretch isolate; + + > *:focus-visible, + > :is(.dropdown-menu, .popover, .select) > button:focus-visible { + @apply relative z-10; + } + + > hr[role='separator'] { + @apply w-0 h-auto self-stretch border border-input shrink-0 m-0; + } + + &:not([data-orientation='vertical']) { + > *:not(:first-child), + > :is(.dropdown-menu, .popover, .select):not(:first-child) > button { + @apply rounded-l-none border-l-0; + } + > *:not(:last-child), + > :is(.dropdown-menu, .popover, .select):not(:last-child) > button { + @apply rounded-r-none; + } + } + &[data-orientation='vertical'] { + @apply flex-col; + + > hr[role='separator'] { + @apply w-auto h-px; + } + + > *:not(:first-child), + > :is(.dropdown-menu, .popover, .select):not(:first-child) > button { + @apply rounded-t-none border-t-0; + } + > *:not(:last-child), + > :is(.dropdown-menu, .popover, .select):not(:last-child) > button { + @apply rounded-b-none; + } + } + } +} \ No newline at end of file diff --git a/src/css/components/button.css b/src/css/components/button.css new file mode 100644 index 0000000..3467075 --- /dev/null +++ b/src/css/components/button.css @@ -0,0 +1,177 @@ +/* Button */ +@layer components { + .btn, + .btn-primary, + .btn-secondary, + .btn-outline, + .btn-ghost, + .btn-link, + .btn-destructive, + .btn-sm, + .btn-sm-primary, + .btn-sm-secondary, + .btn-sm-outline, + .btn-sm-ghost, + .btn-sm-link, + .btn-sm-destructive, + .btn-lg, + .btn-lg-primary, + .btn-lg-secondary, + .btn-lg-outline, + .btn-lg-ghost, + .btn-lg-link, + .btn-lg-destructive, + .btn-icon, + .btn-icon-primary, + .btn-icon-secondary, + .btn-icon-outline, + .btn-icon-ghost, + .btn-icon-link, + .btn-icon-destructive, + .btn-sm-icon, + .btn-sm-icon-primary, + .btn-sm-icon-secondary, + .btn-sm-icon-outline, + .btn-sm-icon-ghost, + .btn-sm-icon-link, + .btn-sm-icon-destructive, + .btn-lg-icon, + .btn-lg-icon-primary, + .btn-lg-icon-secondary, + .btn-lg-icon-outline, + .btn-lg-icon-ghost, + .btn-lg-icon-link, + .btn-lg-icon-destructive { + @apply inline-flex items-center justify-center whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer rounded-md; + } + .btn, + .btn-primary, + .btn-secondary, + .btn-outline, + .btn-ghost, + .btn-link, + .btn-destructive { + @apply gap-2 h-9 px-4 py-2 has-[>svg]:px-3; + } + .btn-icon, + .btn-icon-primary, + .btn-icon-secondary, + .btn-icon-outline, + .btn-icon-ghost, + .btn-icon-link, + .btn-icon-destructive { + @apply size-9; + } + .btn-sm, + .btn-sm-primary, + .btn-sm-secondary, + .btn-sm-outline, + .btn-sm-ghost, + .btn-sm-link, + .btn-sm-destructive { + @apply gap-1.5 h-8 px-3 has-[>svg]:px-2.5; + } + .btn-sm-icon, + .btn-sm-icon-primary, + .btn-sm-icon-secondary, + .btn-sm-icon-outline, + .btn-sm-icon-ghost, + .btn-sm-icon-link, + .btn-sm-icon-destructive { + @apply size-8; + } + .btn-lg, + .btn-lg-primary, + .btn-lg-secondary, + .btn-lg-outline, + .btn-lg-ghost, + .btn-lg-link, + .btn-lg-destructive { + @apply gap-2 h-10 px-6 has-[>svg]:px-4; + } + .btn-lg-icon, + .btn-lg-icon-primary, + .btn-lg-icon-secondary, + .btn-lg-icon-outline, + .btn-lg-icon-ghost, + .btn-lg-icon-link, + .btn-lg-icon-destructive { + @apply size-10; + } + .btn, + .btn-primary, + .btn-sm, + .btn-sm-primary, + .btn-lg, + .btn-lg-primary, + .btn-icon, + .btn-icon-primary, + .btn-sm-icon, + .btn-sm-icon-primary, + .btn-lg-icon, + .btn-lg-icon-primary { + @apply bg-primary text-primary-foreground shadow-xs hover:bg-primary/90; + &[aria-pressed='true'] { + @apply bg-primary/90; + } + } + .btn-secondary, + .btn-sm-secondary, + .btn-lg-secondary, + .btn-icon-secondary, + .btn-sm-icon-secondary, + .btn-lg-icon-secondary { + @apply bg-secondary text-secondary-foreground shadow-xs; + &:hover, + &[aria-pressed='true'] { + @apply bg-secondary/80; + } + } + .btn-outline, + .btn-sm-outline, + .btn-lg-outline, + .btn-icon-outline, + .btn-sm-icon-outline, + .btn-lg-icon-outline { + @apply border bg-background shadow-xs dark:bg-input/30 dark:border-input; + &:hover, + &[aria-pressed='true'] { + @apply bg-accent text-accent-foreground dark:bg-accent/50; + } + } + .btn-ghost, + .btn-sm-ghost, + .btn-lg-ghost, + .btn-icon-ghost, + .btn-sm-icon-ghost, + .btn-lg-icon-ghost { + &:hover, + &[aria-pressed='true'] { + @apply bg-accent text-accent-foreground dark:bg-accent/50; + } + } + .btn-link, + .btn-sm-link, + .btn-lg-link, + .btn-icon-link, + .btn-sm-icon-link, + .btn-lg-icon-link { + @apply text-primary underline-offset-4; + &:hover, + &[aria-pressed='true'] { + @apply hover:underline; + } + } + .btn-destructive, + .btn-sm-destructive, + .btn-lg-destructive, + .btn-icon-destructive, + .btn-sm-icon-destructive, + .btn-lg-icon-destructive { + @apply bg-destructive text-white shadow-xs focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60; + &:hover, + &[aria-pressed='true'] { + @apply bg-destructive/90 dark:bg-destructive/50; + } + } +} \ No newline at end of file diff --git a/src/css/components/card.css b/src/css/components/card.css new file mode 100644 index 0000000..6b954de --- /dev/null +++ b/src/css/components/card.css @@ -0,0 +1,24 @@ +/* Card */ +@layer components { + .card { + @apply bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm; + + > header { + @apply @container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6; + /* TODO: CLEAN has-data-[slot=card-action] */ + + h2 { + @apply leading-none font-semibold; + } + p { + @apply text-muted-foreground text-sm; + } + } + > section { + @apply px-6; + } + > footer { + @apply flex items-center px-6 [.border-t]:pt-6; + } + } +} \ No newline at end of file diff --git a/src/css/components/command.css b/src/css/components/command.css new file mode 100644 index 0000000..6aabf5a --- /dev/null +++ b/src/css/components/command.css @@ -0,0 +1,108 @@ +/* Command */ +@layer components { + .command-dialog { + @apply inset-y-0 opacity-0 transition-all transition-discrete; + + &:is([open],:popover-open) { + @apply opacity-100; + + &::backdrop { + @apply opacity-100; + } + > * { + @apply scale-100; + } + + @starting-style { + @apply opacity-0; + + &::backdrop { + @apply opacity-0; + } + > * { + @apply scale-95; + } + } + } + &::backdrop { + @apply bg-black/50 opacity-0 transition-all transition-discrete; + } + > * { + @apply bg-background fixed top-[50%] left-[50%] z-50 flex flex-col w-full max-w-[calc(100%_-_2rem)] -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-lg border shadow-lg sm:max-w-lg max-h-[min(640px,calc(100%_-_2rem))]; + @apply transition-all scale-95; + + > button, + > form[method='dialog'] { + @apply absolute top-4 right-4; + } + > button, + > form[method='dialog'] > button { + @apply ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4; + } + } + + .command { + > header input { + @apply h-12; + } + [role='menu'] { + @apply px-2; + + [role='menuitem'] { + @apply py-3 px-2 [&_svg]:size-5; + } + [role='heading'] { + @apply px-2 py-1.5 font-medium text-muted-foreground; + } + } + } + } + + .command { + > header { + @apply flex items-center border-b px-3 gap-2; + + svg { + @apply size-4 shrink-0 opacity-50; + } + input { + @apply placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50; + } + } + [role='menu'] { + @apply max-h-[300px] overflow-y-auto overflow-x-hidden p-1; + + [role='menuitem'] { + @apply aria-hidden:hidden relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none aria-disabled:opacity-50 aria-disabled:pointer-events-none disabled:opacity-50 disabled:pointer-events-none w-full truncate [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4; + + &.active { + @apply bg-accent text-accent-foreground; + } + } + [role='heading'] { + @apply text-muted-foreground flex px-2 py-1.5 text-xs font-medium; + } + [role='group']:not(:has([role='menuitem']:not([aria-hidden='true']))) { + @apply hidden; + } + [role='separator'] { + @apply border-border -mx-1 my-1; + } + &:not(:has([role='menuitem']:not([aria-hidden='true'])))::before { + @apply flex items-center justify-center py-6 px-3 text-sm truncate -m-1; + } + &[data-empty]:not(:has([role='menuitem']:not([aria-hidden='true'])))::before { + @apply content-[attr(data-empty)]; + } + &:not([data-empty]):not(:has([role='menuitem']:not([aria-hidden='true'])))::before { + @apply content-['No_results_found']; + } + } + &:not([data-command-initialized]) [role='menuitem'] { + @apply hover:bg-accent hover:text-accent-foreground; + } + &:has(> header input:not(:placeholder-shown)) [role='separator'] { + @apply hidden; + } + } +} \ No newline at end of file diff --git a/src/css/components/dialog.css b/src/css/components/dialog.css new file mode 100644 index 0000000..da9f469 --- /dev/null +++ b/src/css/components/dialog.css @@ -0,0 +1,60 @@ +/* Dialog */ +@layer components { + .dialog { + @apply inset-y-0 opacity-0 transition-all transition-discrete; + + &:is([open],:popover-open) { + @apply opacity-100; + + &::backdrop { + @apply opacity-100; + } + > * { + @apply scale-100; + } + + @starting-style { + @apply opacity-0; + + &::backdrop { + @apply opacity-0; + } + > * { + @apply scale-95; + } + } + } + &::backdrop { + @apply bg-black/50 opacity-0 transition-all transition-discrete; + } + > * { + @apply bg-background fixed top-[50%] left-[50%] z-50 flex flex-col w-full max-w-[calc(100%_-_2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border p-6 shadow-lg sm:max-w-lg max-h-[calc(100%_-_2rem)]; + @apply transition-all scale-95; + + > header { + @apply flex flex-col gap-2 text-center sm:text-left; + + > h2 { + @apply text-lg leading-none font-semibold; + } + > p { + @apply text-muted-foreground text-sm; + } + } + > section { + @apply flex-1 -mx-6 px-6; + } + > footer { + @apply flex flex-col-reverse gap-2 sm:flex-row sm:justify-end; + } + > button, + > form[method='dialog'] { + @apply absolute top-4 right-4; + } + > button, + > form[method='dialog'] > button { + @apply ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4; + } + } + } +} \ No newline at end of file diff --git a/src/css/components/dropdown-menu.css b/src/css/components/dropdown-menu.css new file mode 100644 index 0000000..e91f756 --- /dev/null +++ b/src/css/components/dropdown-menu.css @@ -0,0 +1,38 @@ +/* Dropdown Menu */ +@layer components { + .dropdown-menu { + @apply relative inline-flex; + + [data-popover] { + @apply p-1; + min-width: anchor-size(width); + + [role='menuitem'], + [role='menuitemcheckbox'], + [role='menuitemradio'] { + @apply aria-hidden:hidden [&_svg]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none [&_svg]:shrink-0 [&_svg]:size-4 aria-disabled:opacity-50 aria-disabled:pointer-events-none disabled:opacity-50 disabled:pointer-events-none w-full truncate; + + &:not([aria-disabled='true']) { + @apply focus-visible:bg-accent focus-visible:text-accent-foreground; + } + + &.active { + @apply bg-accent text-accent-foreground; + } + } + [role='menu'] [role='heading'] { + @apply flex px-2 py-1.5 text-sm font-medium; + } + [role='separator'] { + @apply border-border -mx-1 my-1; + } + } + &:not([data-dropdown-menu-initialized]) [data-popover] { + [role='menuitem'], + [role='menuitemcheckbox'], + [role='menuitemradio'] { + @apply hover:bg-accent hover:text-accent-foreground; + } + } + } +} \ No newline at end of file diff --git a/src/css/components/field.css b/src/css/components/field.css new file mode 100644 index 0000000..a860edf --- /dev/null +++ b/src/css/components/field.css @@ -0,0 +1,43 @@ +/* Field */ +@layer components { + .fieldset { + @apply flex flex-col gap-6; + + > legend { + @apply text-base font-medium mb-3; + } + } + .field { + @apply flex flex-col w-full gap-3 data-[invalid=true]:text-destructive [&>*]:w-full [&>.sr-only]:w-auto; + + h2, + h3 { + @apply flex w-fit items-center gap-2 text-sm leading-snug font-medium; + } + [role="alert"] { + @apply text-sm text-destructive font-normal; + + ul { + @apply ml-4 flex list-disc flex-col gap-1; + } + } + section { + @apply flex flex-1 flex-col gap-1.5 leading-snug; + } + + &[data-orientation='horizontal'] { + @apply flex-row items-center [&>label]:flex-auto has-[>section]:items-start has-[>section]:[&>[type=checkbox],[type=radio]]:mt-px [&_p]:text-balance; + } + } + .fieldset legend + p, + .field > p, + .field section > p { + @apply text-muted-foreground text-sm leading-normal font-normal [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4; + } + .fieldset legend + p { + @apply -mt-1.5; + } + .field > p { + @apply last:mt-0 nth-last-2:-mt-1; + } +} \ No newline at end of file diff --git a/src/css/components/form.css b/src/css/components/form.css new file mode 100644 index 0000000..2f2f02a --- /dev/null +++ b/src/css/components/form.css @@ -0,0 +1,8 @@ +@import "./form/checkbox.css"; +@import "./form/input.css"; +@import "./form/label.css"; +@import "./form/radio.css"; +@import "./form/range.css"; +@import "./form/select.css"; +@import "./form/switch.css"; +@import "./form/textarea.css"; \ No newline at end of file diff --git a/src/css/components/form/checkbox.css b/src/css/components/form/checkbox.css new file mode 100644 index 0000000..6b96f8a --- /dev/null +++ b/src/css/components/form/checkbox.css @@ -0,0 +1,12 @@ +/* Checkbox */ +@layer components { + :is(.form, .field) input[type='checkbox']:not([role='switch']), + .input[type='checkbox']:not([role='switch']) { + @apply appearance-none border-input dark:bg-input/30 checked:bg-primary dark:checked:bg-primary checked:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50; + + &:checked:after { + @apply content-[''] block size-3.5 bg-primary-foreground; + @apply mask-[image:var(--check-icon)] mask-size-[0.875rem] mask-no-repeat mask-center; + } + } +} \ No newline at end of file diff --git a/src/css/components/form/input.css b/src/css/components/form/input.css new file mode 100644 index 0000000..7fd0fac --- /dev/null +++ b/src/css/components/form/input.css @@ -0,0 +1,33 @@ +/* Input */ +@layer components { + :is(.form, .field) input[type='text'], + :is(.form, .field) input[type='email'], + :is(.form, .field) input[type='password'], + :is(.form, .field) input[type='number'], + :is(.form, .field) input[type='file'], + :is(.form, .field) input[type='tel'], + :is(.form, .field) input[type='url'], + :is(.form, .field) input[type='search'], + :is(.form, .field) input[type='date'], + :is(.form, .field) input[type='datetime-local'], + :is(.form, .field) input[type='month'], + :is(.form, .field) input[type='week'], + :is(.form, .field) input[type='time'], + .input[type='text'], + .input[type='email'], + .input[type='password'], + .input[type='number'], + .input[type='file'], + .input[type='tel'], + .input[type='url'], + .input[type='search'], + .input[type='date'], + .input[type='datetime-local'], + .input[type='month'], + .input[type='week'], + .input[type='time'] { + @apply appearance-none file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm; + @apply focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]; + @apply aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive; + } +} \ No newline at end of file diff --git a/src/css/components/form/label.css b/src/css/components/form/label.css new file mode 100644 index 0000000..2ce4de4 --- /dev/null +++ b/src/css/components/form/label.css @@ -0,0 +1,12 @@ +/* Label */ +@layer components { + :is(.form, .field) label, + .label { + @apply flex items-center gap-2 text-sm leading-none font-medium select-none peer-disabled:pointer-events-none peer-disabled:opacity-50; + + &:has(>*:disabled), + &:has(+*:disabled) { + @apply opacity-50 pointer-events-none; + } + } +} \ No newline at end of file diff --git a/src/css/components/form/radio.css b/src/css/components/form/radio.css new file mode 100644 index 0000000..7043560 --- /dev/null +++ b/src/css/components/form/radio.css @@ -0,0 +1,11 @@ +/* Radio */ +@layer components { + :is(.form, .field) input[type='radio'], + .input[type='radio'] { + @apply appearance-none border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 relative; + + &:checked:before { + @apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 content-[''] rounded-full size-2 bg-primary; + } + } +} \ No newline at end of file diff --git a/src/css/components/form/range.css b/src/css/components/form/range.css new file mode 100644 index 0000000..3e379a0 --- /dev/null +++ b/src/css/components/form/range.css @@ -0,0 +1,48 @@ +/* Range */ +@layer components { + :is(.form, .field) input[type='range'], + .input[type='range'] { + @apply appearance-none flex items-center p-0 outline-none; + --slider-value: 20%; + + &:hover, + &:focus-visible { + &::-webkit-slider-thumb { + @apply ring-4; + } + &::-moz-range-thumb { + @apply ring-4; + } + &::-ms-thumb { + @apply ring-4; + } + } + + &::-webkit-slider-thumb { + @apply appearance-none border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm -mt-1.25; + } + &::-webkit-slider-runnable-track { + @apply appearance-none rounded-full h-1.5 w-full; + background: linear-gradient(to right, var(--primary) var(--slider-value), var(--muted) var(--slider-value)); + } + &::-moz-range-thumb { + @apply appearance-none border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm -mt-1.25; + } + &::-moz-range-track { + @apply appearance-none rounded-full h-1.5 w-full; + background: linear-gradient(to right, var(--primary) var(--slider-value), var(--muted) var(--slider-value)); + } + &::-ms-thumb { + @apply appearance-none border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm -mt-1.25; + } + &::-ms-track { + @apply appearance-none rounded-full h-1.5 w-full; + } + &::-ms-fill-lower { + @apply bg-primary rounded-full; + } + &::-ms-fill-upper { + @apply bg-muted rounded-full; + } + } +} \ No newline at end of file diff --git a/src/css/components/form/select.css b/src/css/components/form/select.css new file mode 100644 index 0000000..a39a822 --- /dev/null +++ b/src/css/components/form/select.css @@ -0,0 +1,66 @@ +/* Select */ +@layer components { + :is(.form, .field) select, + select.select { + @apply appearance-none border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent pl-3 pr-9 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 h-9; + @apply bg-[image:var(--chevron-down-icon-50)] bg-no-repeat bg-position-[center_right_0.75rem] bg-size-[1rem]; + + option, + optgroup { + @apply bg-popover text-popover-foreground; + } + } + *:not(select).select { + @apply relative inline-flex; + + [data-popover] { + @apply p-1; + + [role='option'] { + @apply aria-hidden:hidden [&_svg]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm pl-2 py-1.5 pr-7.5 text-sm outline-hidden select-none [&_svg]:shrink-0 [&_svg]:size-4 aria-disabled:opacity-50 aria-disabled:pointer-events-none disabled:opacity-50 disabled:pointer-events-none w-full truncate; + + &[aria-selected='true'] { + @apply bg-[image:var(--check-icon)] bg-no-repeat bg-position-[center_right_0.5rem] bg-size-[0.875rem]; + } + &.active, + &:focus-visible { + @apply bg-accent text-accent-foreground; + } + } + [role='listbox'] [role='heading'] { + @apply flex text-muted-foreground px-2 py-1.5 text-xs; + } + [role='listbox'] [role='group']:not(:has([role='option']:not([aria-hidden='true']))) { + @apply hidden; + } + [role='separator'] { + @apply border-border -mx-1 my-1; + } + > header { + @apply flex h-9 items-center gap-2 border-b px-3 -mx-1 -mt-1 mb-1; + + svg { + @apply size-4 shrink-0 opacity-50; + } + input[role='combobox'] { + @apply placeholder:text-muted-foreground flex h-10 flex-1 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50 min-w-0; + } + } + [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before { + @apply flex items-center justify-center p-6 text-sm truncate; + } + [role='listbox'][data-empty]:not(:has([data-value]:not([aria-hidden='true'])))::before { + @apply content-[attr(data-empty)]; + } + [role='listbox']:not([data-empty]):not(:has([data-value]:not([aria-hidden='true'])))::before { + @apply content-['No_results_found']; + } + } + [data-popover]:has(> header input:not(:placeholder-shown)) [role='separator'] { + @apply hidden; + } + &:not([data-select-initialized]) [data-popover] [role='option'] { + @apply hover:bg-accent hover:text-accent-foreground; + } + } +} \ No newline at end of file diff --git a/src/css/components/form/switch.css b/src/css/components/form/switch.css new file mode 100644 index 0000000..d03551b --- /dev/null +++ b/src/css/components/form/switch.css @@ -0,0 +1,11 @@ +/* Switch */ +@layer components { + :is(.form, .field) input[type='checkbox'][role='switch'], + .input[type='checkbox'][role='switch'] { + @apply appearance-none focus-visible:border-ring focus-visible:ring-ring/50 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50; + @apply bg-input dark:bg-input/80 checked:bg-primary dark:checked:bg-primary; + @apply before:content-[''] before:pointer-events-none before:block before:size-4 before:rounded-full before:ring-0 before:transition-all; + @apply before:bg-background dark:before:bg-foreground; + @apply dark:checked:before:bg-primary-foreground checked:before:ms-3.5; + } +} \ No newline at end of file diff --git a/src/css/components/form/textarea.css b/src/css/components/form/textarea.css new file mode 100644 index 0000000..f3d0692 --- /dev/null +++ b/src/css/components/form/textarea.css @@ -0,0 +1,7 @@ +/* Textarea */ +@layer components { + :is(.form, .field) textarea, + .textarea { + @apply border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm; + } +} \ No newline at end of file diff --git a/src/css/components/kbd.css b/src/css/components/kbd.css new file mode 100644 index 0000000..cc234c7 --- /dev/null +++ b/src/css/components/kbd.css @@ -0,0 +1,6 @@ +/* Kbd */ +@layer components { + .kbd { + @apply bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none [&_svg:not([class*='size-'])]:size-3; + } +} \ No newline at end of file diff --git a/src/css/components/popover.css b/src/css/components/popover.css new file mode 100644 index 0000000..f2c321f --- /dev/null +++ b/src/css/components/popover.css @@ -0,0 +1,71 @@ +/* Popover */ +@layer components { + [data-popover] { + @apply absolute bg-popover text-popover-foreground overflow-x-hidden overflow-y-auto rounded-md border shadow-md z-50 visible opacity-100 scale-100 min-w-full w-max transition-all; + + &[aria-hidden='true'] { + @apply invisible opacity-0 scale-95; + + &:not([data-side]), + &[data-side='bottom'] { + @apply -translate-y-2; + } + &[data-side='top'] { + @apply translate-y-2; + } + &[data-side='left'] { + @apply translate-x-2; + } + &[data-side='right'] { + @apply -translate-x-2; + } + } + &:not([data-side]), + &[data-side='bottom'] { + @apply mt-1 top-full; + } + &[data-side='top'] { + @apply mb-1 bottom-full; + } + &[data-side='left'] { + @apply mr-1 right-full; + } + &[data-side='right'] { + @apply ml-1 left-full; + } + &:not([data-side]), + &[data-side='bottom'], + &[data-side='top'] { + &:not([data-align]), + &[data-align='start'] { + @apply left-0; + } + &[data-align='end'] { + @apply right-0; + } + &[data-align='center'] { + @apply left-1/2 -translate-x-1/2; + } + } + &[data-side='left'], + &[data-side='right'] { + &:not([data-align]), + &[data-align='start'] { + @apply top-0; + } + &[data-align='end'] { + @apply bottom-0; + } + &[data-align='center'] { + @apply top-1/2 -translate-y-1/2; + } + } + } + .popover { + @apply relative inline-flex; + + [data-popover] { + @apply p-4; + } + } +} \ No newline at end of file diff --git a/src/css/components/sidebar.css b/src/css/components/sidebar.css new file mode 100644 index 0000000..a44538c --- /dev/null +++ b/src/css/components/sidebar.css @@ -0,0 +1,121 @@ +/* Sidebar */ +@layer components { + .sidebar { + &:not([data-sidebar-initialized]) { + @apply max-md:hidden; + } + &:not([aria-hidden]), + &[aria-hidden=false] { + @apply max-md:bg-black/50 max-md:fixed max-md:inset-0 max-md:z-40; + } + nav { + @apply bg-sidebar text-sidebar-foreground flex flex-col w-(--sidebar-mobile-width) md:w-(--sidebar-width) fixed inset-y-0 z-50 transition-transform ease-in-out duration-300; + } + & + * { + @apply transition-[margin] ease-in-out duration-300; + } + &:not([data-side]), + &[data-side=left] { + nav { + @apply left-0 border-r; + } + & + * { + @apply relative md:ml-(--sidebar-width); + } + &[aria-hidden=true] { + nav { + @apply -translate-x-full; + } + & + * { + @apply md:ml-0; + } + } + } + &[data-side=right] { + nav { + @apply right-0 border-l; + } + & + * { + @apply relative md:mr-(--sidebar-width); + } + &[aria-hidden=true] { + nav { + @apply translate-x-full; + } + & + * { + @apply md:mr-0; + } + } + } + nav { + > header, + > footer { + @apply flex flex-col gap-2 p-2; + } + [role=separator] { + @apply border-sidebar-border mx-2 w-auto; + } + > section { + @apply flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto; + + > [role=group] { + @apply relative flex w-full min-w-0 flex-col p-2; + } + h3 { + @apply text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0; + } + ul { + @apply flex w-full min-w-0 flex-col gap-1; + + li { + @apply relative; + + > a, + > details > summary { + @apply flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&[aria-current=page]]:bg-sidebar-accent [&[aria-current=page]]:font-medium [&[aria-current=page]]:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0; + + &:not([data-variant]), + &[data-variant=default] { + @apply hover:bg-sidebar-accent hover:text-sidebar-accent-foreground; + } + &[data-variant=outline] { + @apply bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]; + } + &:not([data-size]), + &[data-size=default] { + @apply h-8 text-sm; + } + &[data-size=sm] { + @apply h-7 text-xs; + } + &[data-size=lg] { + @apply h-12 text-sm group-data-[collapsible=icon]:p-0!; + } + } + > details { + &:not([open]) { + > summary { + &::after { + @apply -rotate-90; + } + } + } + > summary { + &::after { + @apply content-[''] block size-3.5 bg-primary ml-auto transition-transform ease-linear; + @apply mask-[image:var(--chevron-down-icon)] mask-size-[1rem] mask-no-repeat mask-center; + } + } + &::details-content { + @apply px-3.5; + } + } + } + ul { + @apply border-sidebar-border flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5 w-full; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/css/components/table.css b/src/css/components/table.css new file mode 100644 index 0000000..619fe2b --- /dev/null +++ b/src/css/components/table.css @@ -0,0 +1,27 @@ +/* Tables */ +@layer components { + .table { + @apply w-full caption-bottom text-sm; + thead { + @apply [&_tr]:border-b; + } + tbody { + @apply [&_tr:last-child]:border-0; + } + tfoot { + @apply bg-muted/50 border-t font-medium [&>tr]:last:border-b-0; + } + tr { + @apply hover:bg-muted/50 border-b transition-colors; + } + th { + @apply text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]; + } + td { + @apply p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]; + } + caption { + @apply text-muted-foreground mt-4 text-sm; + } + } +} \ No newline at end of file diff --git a/src/css/components/tabs.css b/src/css/components/tabs.css new file mode 100644 index 0000000..30aca66 --- /dev/null +++ b/src/css/components/tabs.css @@ -0,0 +1,21 @@ +/* Tabs */ +@layer components { + .tabs { + @apply flex flex-col gap-2; + + [role='tablist'] { + @apply bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]; + + [role='tab'] { + @apply focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground dark:text-muted-foreground inline-flex h-[calc(100%_-_1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4; + + &[aria-selected='true'] { + @apply bg-background dark:text-foreground dark:border-input dark:bg-input/30 shadow-sm; + } + } + } + [role='tabpanel'] { + @apply flex-1 outline-none; + } + } +} \ No newline at end of file diff --git a/src/css/components/toast.css b/src/css/components/toast.css new file mode 100644 index 0000000..a407e3c --- /dev/null +++ b/src/css/components/toast.css @@ -0,0 +1,57 @@ +/* Toasts */ +@layer components { + .toaster { + @apply fixed bottom-0 p-4 pointer-events-none z-50 w-full sm:max-w-90 flex flex-col-reverse; + + &:not([data-align]), + &[data-align='end'] { + @apply right-0; + } + &[data-align='start'] { + @apply left-0; + } + &[data-align='center'] { + @apply left-1/2 -translate-x-1/2; + } + .toast { + @apply pointer-events-auto w-full mt-4 animate-[toast-up_0.3s_ease-in-out] grid grid-rows-[1fr] transition-[grid-template-rows,opacity,margin] duration-300 ease-in-out; + + .toast-content { + @apply text-popover-foreground text-[13px] bg-popover border shadow-lg rounded-lg overflow-hidden flex gap-2.5 p-3 items-center; + + svg { + @apply size-4 shrink-0; + } + section { + h2 { + @apply font-medium tracking-tight; + } + p { + @apply text-muted-foreground break-all; + } + } + footer { + @apply ml-auto flex flex-col gap-2; + + [data-toast-action], + [data-toast-cancel] { + @apply h-6 px-2.5 text-xs; + } + } + } + &[aria-hidden='true'] { + @apply grid-rows-[0fr] opacity-0 m-0 border-0 p-0 overflow-hidden; + + .toast-content { + @apply border-0; + } + } + } + } +} +@keyframes toast-up { + from { + opacity: 0; + transform: translateY(100%); + } +} \ No newline at end of file diff --git a/src/css/components/tooltip.css b/src/css/components/tooltip.css new file mode 100644 index 0000000..7e080ad --- /dev/null +++ b/src/css/components/tooltip.css @@ -0,0 +1,56 @@ +/* Tooltip */ +@layer components { + [data-tooltip] { + @apply relative; + + &:before { + @apply absolute content-[attr(data-tooltip)] bg-primary text-primary-foreground z-[60] truncate max-w-xs w-fit rounded-md px-3 py-1.5 text-xs invisible opacity-0 scale-95 transition-all pointer-events-none; + } + &:hover:before { + @apply visible opacity-100 scale-100; + } + &:focus-visible:not(:hover):before { + @apply hidden; + } + &:not([data-side]), + &[data-side='top'] { + @apply before:bottom-full before:mb-1.5 before:translate-y-2 hover:before:translate-y-0 ; + } + &[data-side='bottom'] { + @apply before:top-full before:mt-1.5 before:-translate-y-2 hover:before:translate-y-0 ; + } + &:not([data-side]), + &[data-side='top'], + &[data-side='bottom'] { + &[data-align='start'] { + @apply before:left-0; + } + &[data-align='end'] { + @apply before:right-0; + } + &:not([data-align]), + &[data-align='center'] { + @apply before:left-1/2 before:-translate-x-1/2; + } + } + &[data-side='left'] { + @apply before:right-full before:mr-1.5 before:translate-x-2 hover:before:translate-x-0; + } + &[data-side='right'] { + @apply before:left-full before:ml-1.5 before:-translate-x-2 hover:before:translate-x-0; + } + &[data-side='left'], + &[data-side='right'] { + &[data-align='start'] { + @apply before:top-0; + } + &[data-align='end'] { + @apply before:bottom-0; + } + &:not([data-align]), + &[data-align='center'] { + @apply before:top-1/2 before:-translate-y-1/2; + } + } + } +} \ No newline at end of file