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