├── .eslintrc.js ├── .github ├── FUNDING.yml └── workflows │ ├── deploy-docs.yml │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── Cargo.toml ├── book.toml └── src │ ├── README.md │ ├── SUMMARY.md │ ├── chapter_1.md │ ├── configuration.md │ ├── contributing.md │ ├── faq.md │ ├── installation.md │ ├── license.md │ ├── macros.md │ └── usage.md ├── examples └── leptos-demo │ ├── Cargo.toml │ ├── LICENSE │ ├── Makefile.toml │ ├── README.md │ ├── Trunk.toml │ ├── dist │ ├── favicon-e9cbd8f50cc65bf2.ico │ ├── index.html │ ├── output-eebef1fbc202efb9.css │ ├── tailwind-csr-trunk-4ed9e9e4c35d1fac.js │ └── tailwind-csr-trunk-4ed9e9e4c35d1fac_bg.wasm │ ├── index.html │ ├── input.css │ ├── package-lock.json │ ├── package.json │ ├── public │ └── favicon.ico │ ├── src │ ├── app.rs │ └── main.rs │ ├── style │ └── output.css │ ├── tailwind.config.js │ └── tailwind.config.json ├── tailwind.config.json ├── tailwind.config.old.json ├── tailwind ├── Cargo.toml ├── src │ ├── lib.rs │ └── main.rs ├── tailwind.config.json └── tests │ └── tailwind.rs ├── twust ├── Cargo.toml └── src │ └── lib.rs └── twust_macro ├── Cargo.toml └── src ├── config ├── classes.rs ├── macros.rs ├── mod.rs ├── modifiers.rs └── noconfig.rs ├── lib.rs ├── plugins ├── daisyui.rs └── mod.rs └── tailwind ├── colorful.rs ├── default_classnames.rs ├── lengthy.rs ├── mod.rs ├── modifiers.rs ├── signable.rs ├── tailwind_config.rs └── valid_baseclass_names.rs /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['tailwindcss'], 3 | rules: { 4 | 'tailwindcss/class-order': 'warn', 5 | 'tailwindcss/no-contradicting-classname': 'error', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Oyelowo] 2 | patreon: Oyelowo 3 | open_collective: Oyelowo 4 | # ko_fi: your_kofi_username 5 | # tidelift: npm/your_package_name 6 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy mdBook Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v3 14 | 15 | - name: Install mdBook 16 | run: cargo install mdbook 17 | 18 | - name: Build the book 19 | run: mdbook build docs 20 | 21 | - name: Deploy to GitHub Pages 22 | uses: peaceiris/actions-gh-pages@v3 23 | with: 24 | github_token: ${{ secrets.GITHUB_TOKEN }} 25 | publish_dir: docs/book 26 | keep_files: false # Ensures a clean deployment 27 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: twust 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | check_and_test_rust_monorepo: 12 | name: Run code checks for rust workspace 13 | timeout-minutes: 80 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v3 19 | 20 | - name: Install stable toolchain 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | profile: minimal 24 | toolchain: stable 25 | 26 | - name: Cache Rust dependencies 27 | uses: Swatinem/rust-cache@v1 28 | 29 | - name: Run cargo test in tailwind directory 30 | run: cargo test --verbose 31 | working-directory: tailwind 32 | 33 | clippy: 34 | name: Clippy 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - uses: actions/checkout@v3 39 | - uses: actions-rs/toolchain@v1 40 | with: 41 | profile: minimal 42 | toolchain: stable 43 | 44 | - run: rustup component add clippy 45 | - run: cargo clippy -- -D warnings 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | 17 | # Added by cargo 18 | 19 | /target 20 | /Cargo.lock 21 | 22 | node_modules/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | workspace.resolver = "2" 3 | members = ["twust", "twust_macro", "tailwind"] 4 | 5 | 6 | [workspace.package] 7 | name = "twust" 8 | version = "1.1.0" 9 | edition = "2021" 10 | authors = ["Oyelowo Oyedayo"] 11 | email = ["oyelowo.oss@gmai.com"] 12 | readme = "README.md" 13 | documentation = "https://docs.rs/twust" 14 | # documentation = "https://codebreather.com/oyelowo" 15 | repository = "https://github.com/Oyelowo/twust" 16 | description = "Zero-config Static type-checker for Tailwind CSS" 17 | license = "MIT/Apache-2.0" 18 | categories = ["UI", "css", "tailwindcss", "web-programming"] 19 | keywords = ["tailwind", "css", "tailwindcss", "ui", "web"] 20 | 21 | 22 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 23 | 24 | 25 | # exclude = ["examples", "tests"] 26 | 27 | 28 | [workspace.dependencies] 29 | twust = { path = "twust" } 30 | twust_macro = { version = "1.1.0", features = ["daisyui"] } 31 | # twust_macro = { path = "twust_macro", features = ["daisyui"] } 32 | proc-macro2 = "1.0.69" 33 | quote = "1.0.33" 34 | syn = { version = "2.0.38", features = ["full"] } 35 | nom = "7.1.3" 36 | serde = { version = "1.0.188", features = ["derive"] } 37 | serde_json = "1.0.107" 38 | 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Oyelowo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `twust` 2 | [Book](https://oyelowo.github.io/twust) | [doc.rs](https://docs.rs/twust/latest/twust/) | [Discord](https://discord.gg/Vrkq8KhGwN) 3 | 4 | Twust is a powerful static checker in Rust for TailwindCSS class names at compile-time. It ensures correctness by validating class names before runtime, preventing errors and improving developer experience. 5 | 6 | Screenshot 2023-10-11 at 1 40 26 7 | 8 | 9 | - [`twust`](#twust) 10 | - [Features](#features) 11 | - [Installation](#installation) 12 | - [Usage](#usage) 13 | - [`tw!` - Compile-time Type-Checked Tailwind Classes](#tw-compile-time-type-checked-tailwind-classes) 14 | - [`tws!` - Compile-time Checked Array of Classes](#tws-compile-time-checked-array-of-classes) 15 | - [`tw1!` - Single Tailwind Class Only](#tw1-single-tailwind-class-only) 16 | - [`tws1!` - Array of Single-Class Items Only](#tws1-array-of-single-class-items-only) 17 | - [`tailwind.config.json` Overview](#tailwindconfigjson-overview) 18 | - [Basic Structure:](#basic-structure) 19 | - [Statement of Problem](#statement-of-problem) 20 | - [Solution](#solution) 21 | - [How does this compare with Other Rust Libraries?](#how-does-this-compare-with-other-rust-libraries) 22 | - [`tailwindcss-to-rust`](#tailwindcss-to-rust) 23 | - [`twust`](#twust) 24 | - [License](#license) 25 | - [⭐ Show Some Love!](#show-some-love) 26 | 27 | 28 | --- 29 | 30 | ## Features 31 | 32 | - **Compile-time validation** of Tailwind CSS class names. Prevents typos and invalid Tailwind class names. 33 | - **No runtime overhead** – errors are caught at compile time. 34 | - **Supports single and multiple class formats.** 35 | 36 | - **Supports DaisyU under a feature flag**. 37 | - **Configurable: Supports overriding, extending, custom classes, custom modifiers, Plugins and many more**. 38 | - **Flexible macro-based API**. 39 | - **Returns class names as a string or an array** for easy use in UI frameworks. 40 | - **Works seamlessly in Rust UI frameworks like Leptos, Dioxus, Yew, and Sycamore**. 41 | - **Lightweight and blazing fast!** 42 | 43 | --- 44 | 45 | ## Installation 46 | 47 | ```toml 48 | [dependencies] 49 | twust = "1.0.7" 50 | ``` 51 | 52 | Enable optional features (e.g., `daisyui` support): 53 | 54 | ```toml 55 | [dependencies] 56 | twust = { version = "1.0.7", features = ["daisyui"] } 57 | ``` 58 | 59 | --- 60 | 61 | ## Usage 62 | 63 | ### `tw!` - Compile-time Type-Checked Tailwind Classes 64 | 65 | ```rust 66 | use twust::tw; 67 | 68 | let class = tw!("bg-red-500 text-lg"); 69 | assert_eq!(class, "bg-red-500 text-lg"); 70 | 71 | // You can also separate classnames by space, these will be merged 72 | let classes_list = tw!["bg-blue-500 hover:bg-blue-700", "bg-purple", "py-sm md:py-md tablet:py-sm lg:py-lg xl:py-xl"]; 73 | assert_eq!(classes_list, "bg-blue-500 hover:bg-blue-700 bg-purple py-sm md:py-md tablet:py-sm lg:py-lg xl:py-xl") 74 | 75 | // You can override/extend color/background color in tailwind.config.json 76 | tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-purple bg-red-50 bg-tahiti-800 border-s-tahiti-800"); 77 | tw!("md:text-red-50 text-slate-50 text-purple text-tahiti-500"); 78 | tw!("py-sm md:py-md tablet:py-sm lg:py-lg xl:py-xl"); 79 | tw!("group"); 80 | tw!("hover:-translate-y-0.5 transition motion-reduce:hover:translate-y-0 motion-reduce:transition-none"); 81 | tw!("group/edit block invisible md:hover:bg-slate-200 group-hover/item:visible"); 82 | tw!("group-[:nth-of-type(3)_&]:block group-hover/edit:text-gray-700 group-[:nth-of-type(3)_&]:block"); 83 | tw!("scroll-m-15 group-aria-[sort=ascending]:rotate-0"); 84 | 85 | // Even scroll margin can also be configured, here we add, sm and md under the Spacing/scrollMargin field in the config file 86 | tw!("scroll-mx-sm scroll-mx-md"); 87 | tw!("px-[-45px] px-[-45cm] px-[-45rem] px-[-45em] px-[-45%] px-[-45vh]"); 88 | tw!("m-4 last:first:invalid:last:first:p-4 last:m-4 pb-[calc(100%-34px)] pb-[23px] [mask-type:luminance] 89 | [mask-type:luminance] hover:[mask-type:alpha] lg:[--scroll-offset:44px] oyelowo oyedayo break-after-avoid" 90 | ); 91 | tw!("h-full border-2 border-opacity-60 rounded-lg overflow-hidden"); 92 | ``` 93 | 94 | ### `tws!` - Compile-time Checked Array of Classes 95 | 96 | ```rust 97 | use twust::tws; 98 | 99 | let class_list = tws!["bg-red-500", "text-lg", "p-4"]; 100 | assert_eq!(class_list, ["bg-red-500", "text-lg", "p-4"]); 101 | ``` 102 | 103 | ### `tw1!` - Single Tailwind Class Only 104 | 105 | ```rust 106 | use twust::tw1; 107 | 108 | let class = tw1!("bg-red-500"); 109 | assert_eq!(class, "bg-red-500"); 110 | ``` 111 | 112 | ### `tws1!` - Array of Single-Class Items Only 113 | 114 | ```rust 115 | use twust::tws1; 116 | 117 | let class_list = tws1!["text-xl", "border", "m-4"]; 118 | assert_eq!(class_list, ["text-xl", "border", "m-4"]); 119 | ``` 120 | 121 | --- 122 | 123 | ## `tailwind.config.json` Overview 124 | 125 | ### Basic Structure: 126 | 127 | ```json 128 | { 129 | "corePlugins": {}, 130 | "allowedLists": { 131 | "classes": [], 132 | "modifiers": [] 133 | }, 134 | "theme": { 135 | "extend": {} 136 | }, 137 | "variants": {}, 138 | "plugins": {} 139 | } 140 | ``` 141 | 142 | --- 143 | 144 | ## Statement of Problem 145 | 146 | TailwindCSS offers developers a flexible utility-first approach to styling web applications. However, its flexibility can also lead to potential pitfalls: 147 | 148 | - **Runtime Errors:** Invalid TailwindCSS class names can cause unexpected styling issues that are only caught during runtime. 149 | - **Developer Experience:** Manually validating class names can be tedious and error-prone. 150 | - **Plugin Compatibility:** Some TailwindCSS utilities extend their functionality with plugins like DaisyUI, which traditional methods might not support. 151 | - **Increased Build Size:** Invalid class names that slip into the production code can increase the final CSS bundle size. 152 | 153 | --- 154 | 155 | ## Solution 156 | 157 | Twust addresses these challenges by offering: 158 | 159 | - **Compile-time Validation:** Ensures that only valid TailwindCSS class names are used, preventing errors in production. 160 | - **Seamless Integration:** Works within Rust macros for an improved developer experience. 161 | - **Plugin Support:** Easily integrate popular plugins like DaisyUI with feature flags. 162 | - **Optimized Builds:** Reduces unnecessary CSS bloat. 163 | 164 | --- 165 | 166 | ## How does this compare with Other Rust Libraries? 167 | 168 | ### `tailwindcss-to-rust` 169 | 170 | - Requires complex setup and external dependencies. 171 | - Generates Rust code that must be maintained manually. 172 | - Lacks full support for all Tailwind utilities. 173 | 174 | ### `twust` 175 | 176 | - No setup required – just use the macros. 177 | - Works in real-time at compile-time. 178 | - Self-contained with no external dependencies. 179 | - Supports all standard TailwindCSS class names, including responsive variants. 180 | 181 | --- 182 | 183 | ## License 184 | 185 | Twust is licensed under MIT/Apache-2.0. See LICENSE for details. 186 | 187 | © [Oyelowo Oyedayo](https://github.com/Oyelowo) 188 | 189 | **Email: oyelowo.oss@gmail.com** 190 | 191 | --- 192 | 193 | ## ⭐ Show Some Love! 194 | 195 | If you find Twust useful, give it a ⭐ on [GitHub](https://github.com/Oyelowo/twust)! 196 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /docs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | twust = { path = "../" } 3 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "Twust Documentation" 3 | authors = ["oyelowo"] 4 | author = "Oyelowo Oyedayo" 5 | description = "Zero-config TailwindCSS static typechecker in Rust" 6 | language = "en" 7 | multilingual = false 8 | src = "src" 9 | 10 | [build] 11 | rustc-opts = ["--edition=2021"] 12 | 13 | [output.html] 14 | site-url = "/twust/" 15 | git-repository-url = "https://github.com/oyelowo/twust" 16 | default-theme = "light" 17 | preferred-dark-theme = "ayu" 18 | -------------------------------------------------------------------------------- /docs/src/README.md: -------------------------------------------------------------------------------- 1 | # Twust - Static TailwindCSS Validation in Rust 2 | 3 | Twust is a **compile-time TailwindCSS static checker** for Rust. It validates class names before runtime, preventing typos, incorrect class names, and misconfigurations. 4 | 5 | ## Why Twust? 6 | - **Compile-time validation**: Catch invalid TailwindCSS classes before your Rust app runs. 7 | - **Zero runtime overhead**: Errors are flagged at compile time. 8 | - **Supports Tailwind plugins** like DaisyUI (via feature flags). 9 | - **Works with Rust UI frameworks** (Leptos, Dioxus, Yew, Sycamore). 10 | 11 | ```rust 12 | use twust::tw; 13 | 14 | let class = tw!("bg-blue-500 text-lg hover:bg-blue-600"); 15 | assert_eq!(class, "bg-blue-500 text-lg hover:bg-blue-600"); 16 | -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](README.md) 4 | [Installation](installation.md) 5 | [Usage](usage.md) 6 | [Macros](macros.md) 7 | [Configuration](configuration.md) 8 | [Frequently Asked Questions](faq.md) 9 | [Contributing](contributing.md) 10 | [License](license.md) 11 | -------------------------------------------------------------------------------- /docs/src/chapter_1.md: -------------------------------------------------------------------------------- 1 | # Chapter 1 2 | -------------------------------------------------------------------------------- /docs/src/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | # Tailwind Configuration 4 | 5 | Twust reads custom Tailwind settings from `tailwind.config.json`. 6 | 7 | ## Example Configuration 8 | 9 | ```json 10 | { 11 | "theme": { 12 | "extend": { 13 | "colors": { 14 | "primary": "#ff6347", 15 | "secondary": "#4a90e2" 16 | } 17 | } 18 | }, 19 | "corePlugins": { 20 | "preflight": false 21 | }, 22 | "variants": { 23 | "backgroundColor": ["responsive", "hover", "focus"] 24 | } 25 | } 26 | ``` 27 | 28 | 29 | 30 | ## Using Custom Classes 31 | 32 | ```rust 33 | tw!("bg-primary text-secondary hover:bg-secondary"); 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/src/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Steps 4 | 5 | 1. Fork the repository. 6 | 2. Clone it locally: 7 | 8 | ```sh 9 | git clone https://github.com/oyelowo/twust.git 10 | ``` 11 | 12 | 3. Create a feature branch: 13 | 14 | ```sh 15 | git checkout -b feature-name 16 | ``` 17 | 18 | 4. Make changes and commit: 19 | 20 | ```sh 21 | git commit -m "Add new feature" 22 | ``` 23 | 24 | 5. Push your branch and open a PR. 25 | 26 | -------------------------------------------------------------------------------- /docs/src/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ### Why use Twust instead of tailwindcss-to-rust? 4 | Twust is simpler, requires no setup, and validates classes at **compile-time** instead of generating code. 5 | 6 | ### Can I use this with DaisyUI? 7 | Yes! Enable the feature flag: 8 | 9 | ```toml 10 | twust = { version = "1.0.7", features = ["daisyui"] } 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/src/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Twust is available on [crates.io](https://crates.io/crates/twust). Add it to your `Cargo.toml`: 4 | 5 | ```toml 6 | [dependencies] 7 | twust = "1.0.7" 8 | 9 | # Enable optional features like daisyui: 10 | twust = { version = "1.0.7", features = ["daisyui"] } 11 | 12 | -------------------------------------------------------------------------------- /docs/src/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | 4 | # License 5 | 6 | Twust is licensed under **MIT/Apache-2.0**. 7 | 8 | © [Oyelowo Oyedayo](https://github.com/Oyelowo). See `LICENSE` for details. 9 | -------------------------------------------------------------------------------- /docs/src/macros.md: -------------------------------------------------------------------------------- 1 | # Macros 2 | 3 | Twust provides the following macros: 4 | 5 | ### `tw!` - Type-Checked Tailwind Classes 6 | 7 | ```rust 8 | 9 | use twust::tw; 10 | 11 | let class = tw!("hover:bg-green-500 text-white font-bold"); 12 | assert_eq!(class, "hover:bg-green-500 text-white font-bold"); 13 | 14 | ``` 15 | 16 | ### `tws!` - Compile-time Checked Array of Classes 17 | 18 | ```rust 19 | 20 | use twust::tws; 21 | 22 | let class_list = tws!["border", "rounded-md", "shadow"]; 23 | assert_eq!(class_list, ["border", "rounded-md", "shadow"]); 24 | 25 | ``` 26 | 27 | ### `tw1!` - Single Tailwind Class Only 28 | 29 | ```rust 30 | 31 | use twust::tw1; 32 | 33 | let single_class = tw1!("flex"); 34 | assert_eq!(single_class, "flex"); 35 | 36 | ``` 37 | 38 | ### `tws1!` - Array of Single-Class Items Only 39 | 40 | ```rust 41 | 42 | use twust::tws1; 43 | 44 | let class_list = tws1!["text-xl", "border", "m-4"]; 45 | assert_eq!(class_list, ["text-xl", "border", "m-4"]); 46 | 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/src/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | Twust provides macros to validate TailwindCSS classes at compile time. 4 | 5 | ## Basic Example 6 | 7 | ```rust 8 | 9 | use twust::tw; 10 | 11 | let class = tw!("bg-red-500 text-lg"); 12 | assert_eq!(class, "bg-red-500 text-lg"); 13 | 14 | ``` 15 | 16 | ## Restricting to One Class 17 | 18 | ```rust 19 | 20 | use twust::tw1; 21 | 22 | let single_class = tw1!("bg-blue-500"); 23 | assert_eq!(single_class, "bg-blue-500"); 24 | 25 | ``` 26 | 27 | ## Using Multiple Classes As Array 28 | 29 | ```rust 30 | 31 | use twust::tws; 32 | 33 | let class_list = tws!["bg-red-500 text-lg", "p-4 bg-blue-500"]; 34 | assert_eq!(class_list, ["bg-red-500", "text-lg", "p-4"]); 35 | 36 | ``` 37 | 38 | 39 | ## Using Multiple Classes As Array: One class per item 40 | 41 | ```rust 42 | 43 | use twust::tws1; 44 | 45 | let class_list = tws1!["bg-red-500", "text-lg", "p-4"]; 46 | assert_eq!(class_list, ["bg-red-500", "text-lg", "p-4"]); 47 | 48 | ``` 49 | 50 | -------------------------------------------------------------------------------- /examples/leptos-demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tailwind-csr-trunk" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [workspace] 7 | 8 | [dependencies] 9 | leptos = { version = "0.7.4", features = ["csr"] } 10 | leptos_meta = { version = "0.7.4" } 11 | leptos_router = { version = "0.7.4" } 12 | log = "0.4.25" 13 | gloo-net = { version = "0.6.0", features = ["http"] } 14 | twust = { version = "*", features = ["daisyui"] } 15 | # twust = { git = "https://github.com/oyelowo/twust", features = ["daisyui"] } 16 | 17 | 18 | # dependecies for client (enable when csr or hydrate set) 19 | wasm-bindgen = { version = "0.2" } 20 | console_log = { version = "1" } 21 | console_error_panic_hook = { version = "0.1" } 22 | -------------------------------------------------------------------------------- /examples/leptos-demo/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 henrik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/leptos-demo/Makefile.toml: -------------------------------------------------------------------------------- 1 | extend = [{ path = "../cargo-make/main.toml" }] 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/leptos-demo/README.md: -------------------------------------------------------------------------------- 1 | # Leptos Starter Template 2 | 3 | This is a template demonstrating how to integrate [TailwindCSS](https://tailwindcss.com/) with the [Leptos](https://github.com/leptos-rs/leptos) web framework and the [trunk](https://github.com/thedodd/trunk) tool. 4 | 5 | 6 | Install Tailwind and build the CSS: 7 | 8 | `Trunk.toml` is configured to build the CSS automatically. 9 | 10 | Install trunk to client side render this bundle. 11 | 12 | `cargo install trunk` 13 | Then the site can be served with `trunk serve --open` 14 | 15 | The browser will automatically open [http://127.0.0.1:8080//](http://127.0.0.1:8080//) 16 | 17 | You can begin editing your app at `src/app.rs`. 18 | 19 | ## Installing Tailwind 20 | 21 | You can install Tailwind using `npm`: 22 | 23 | ```bash 24 | npm install -D tailwindcss 25 | ``` 26 | 27 | If you'd rather not use `npm`, you can install the Tailwind binary [here](https://github.com/tailwindlabs/tailwindcss/releases). 28 | 29 | ## Setting up with VS Code and Additional Tools 30 | 31 | If you're using VS Code, add the following to your `settings.json` 32 | 33 | ```json 34 | "emmet.includeLanguages": { 35 | "rust": "html", 36 | "*.rs": "html" 37 | }, 38 | "tailwindCSS.includeLanguages": { 39 | "rust": "html", 40 | "*.rs": "html" 41 | }, 42 | "files.associations": { 43 | "*.rs": "rust" 44 | }, 45 | "editor.quickSuggestions": { 46 | "other": "on", 47 | "comments": "on", 48 | "strings": true 49 | }, 50 | "css.validate": false, 51 | ``` 52 | 53 | Install [Tailwind CSS Intellisense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss). 54 | 55 | Install [VS Browser](https://marketplace.visualstudio.com/items?itemName=Phu1237.vs-browser) extension (allows you to open a browser at the right window. 56 | 57 | Allow vscode Ports forward: 3000, 3001. 58 | 59 | ## Notes about Tooling 60 | 61 | By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If you run into any trouble, you may need to install one or more of these tools. 62 | 63 | 1. `rustup toolchain install nightly --allow-downgrade` - make sure you have Rust nightly 64 | 2. `rustup default nightly` - setup nightly as default, or you can use rust-toolchain file later on 65 | 3. `rustup target add wasm32-unknown-unknown` - add the ability to compile Rust to WebAssembly 66 | 4. `cargo install cargo-generate` - install `cargo-generate` binary (should be installed automatically in future) 67 | 5. `npm install -g sass` - install `dart-sass` (should be optional in future 68 | 69 | 70 | ## Attribution 71 | This is based on the original Tailwind example (../examples/tailwind) 72 | -------------------------------------------------------------------------------- /examples/leptos-demo/Trunk.toml: -------------------------------------------------------------------------------- 1 | [[hooks]] 2 | stage = "pre_build" 3 | command = "sh" 4 | command_arguments = ["-c", "npx tailwindcss -i input.css -o style/output.css"] 5 | -------------------------------------------------------------------------------- /examples/leptos-demo/dist/favicon-e9cbd8f50cc65bf2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oyelowo/twust/01fa4850ebeff6277518e49bdead32e858e9249d/examples/leptos-demo/dist/favicon-e9cbd8f50cc65bf2.ico -------------------------------------------------------------------------------- /examples/leptos-demo/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Leptos • Counter with Tailwind 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/leptos-demo/dist/output-eebef1fbc202efb9.css: -------------------------------------------------------------------------------- 1 | /* 2 | ! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com 3 | */ 4 | 5 | /* 6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) 7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) 8 | */ 9 | 10 | *, 11 | ::before, 12 | ::after { 13 | box-sizing: border-box; 14 | /* 1 */ 15 | border-width: 0; 16 | /* 2 */ 17 | border-style: solid; 18 | /* 2 */ 19 | border-color: #e5e7eb; 20 | /* 2 */ 21 | } 22 | 23 | ::before, 24 | ::after { 25 | --tw-content: ''; 26 | } 27 | 28 | /* 29 | 1. Use a consistent sensible line-height in all browsers. 30 | 2. Prevent adjustments of font size after orientation changes in iOS. 31 | 3. Use a more readable tab size. 32 | 4. Use the user's configured `sans` font-family by default. 33 | 5. Use the user's configured `sans` font-feature-settings by default. 34 | 6. Use the user's configured `sans` font-variation-settings by default. 35 | */ 36 | 37 | html { 38 | line-height: 1.5; 39 | /* 1 */ 40 | -webkit-text-size-adjust: 100%; 41 | /* 2 */ 42 | -moz-tab-size: 4; 43 | /* 3 */ 44 | -o-tab-size: 4; 45 | tab-size: 4; 46 | /* 3 */ 47 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 48 | /* 4 */ 49 | font-feature-settings: normal; 50 | /* 5 */ 51 | font-variation-settings: normal; 52 | /* 6 */ 53 | } 54 | 55 | /* 56 | 1. Remove the margin in all browsers. 57 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. 58 | */ 59 | 60 | body { 61 | margin: 0; 62 | /* 1 */ 63 | line-height: inherit; 64 | /* 2 */ 65 | } 66 | 67 | /* 68 | 1. Add the correct height in Firefox. 69 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) 70 | 3. Ensure horizontal rules are visible by default. 71 | */ 72 | 73 | hr { 74 | height: 0; 75 | /* 1 */ 76 | color: inherit; 77 | /* 2 */ 78 | border-top-width: 1px; 79 | /* 3 */ 80 | } 81 | 82 | /* 83 | Add the correct text decoration in Chrome, Edge, and Safari. 84 | */ 85 | 86 | abbr:where([title]) { 87 | -webkit-text-decoration: underline dotted; 88 | text-decoration: underline dotted; 89 | } 90 | 91 | /* 92 | Remove the default font size and weight for headings. 93 | */ 94 | 95 | h1, 96 | h2, 97 | h3, 98 | h4, 99 | h5, 100 | h6 { 101 | font-size: inherit; 102 | font-weight: inherit; 103 | } 104 | 105 | /* 106 | Reset links to optimize for opt-in styling instead of opt-out. 107 | */ 108 | 109 | a { 110 | color: inherit; 111 | text-decoration: inherit; 112 | } 113 | 114 | /* 115 | Add the correct font weight in Edge and Safari. 116 | */ 117 | 118 | b, 119 | strong { 120 | font-weight: bolder; 121 | } 122 | 123 | /* 124 | 1. Use the user's configured `mono` font family by default. 125 | 2. Correct the odd `em` font sizing in all browsers. 126 | */ 127 | 128 | code, 129 | kbd, 130 | samp, 131 | pre { 132 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 133 | /* 1 */ 134 | font-size: 1em; 135 | /* 2 */ 136 | } 137 | 138 | /* 139 | Add the correct font size in all browsers. 140 | */ 141 | 142 | small { 143 | font-size: 80%; 144 | } 145 | 146 | /* 147 | Prevent `sub` and `sup` elements from affecting the line height in all browsers. 148 | */ 149 | 150 | sub, 151 | sup { 152 | font-size: 75%; 153 | line-height: 0; 154 | position: relative; 155 | vertical-align: baseline; 156 | } 157 | 158 | sub { 159 | bottom: -0.25em; 160 | } 161 | 162 | sup { 163 | top: -0.5em; 164 | } 165 | 166 | /* 167 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) 168 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) 169 | 3. Remove gaps between table borders by default. 170 | */ 171 | 172 | table { 173 | text-indent: 0; 174 | /* 1 */ 175 | border-color: inherit; 176 | /* 2 */ 177 | border-collapse: collapse; 178 | /* 3 */ 179 | } 180 | 181 | /* 182 | 1. Change the font styles in all browsers. 183 | 2. Remove the margin in Firefox and Safari. 184 | 3. Remove default padding in all browsers. 185 | */ 186 | 187 | button, 188 | input, 189 | optgroup, 190 | select, 191 | textarea { 192 | font-family: inherit; 193 | /* 1 */ 194 | font-feature-settings: inherit; 195 | /* 1 */ 196 | font-variation-settings: inherit; 197 | /* 1 */ 198 | font-size: 100%; 199 | /* 1 */ 200 | font-weight: inherit; 201 | /* 1 */ 202 | line-height: inherit; 203 | /* 1 */ 204 | color: inherit; 205 | /* 1 */ 206 | margin: 0; 207 | /* 2 */ 208 | padding: 0; 209 | /* 3 */ 210 | } 211 | 212 | /* 213 | Remove the inheritance of text transform in Edge and Firefox. 214 | */ 215 | 216 | button, 217 | select { 218 | text-transform: none; 219 | } 220 | 221 | /* 222 | 1. Correct the inability to style clickable types in iOS and Safari. 223 | 2. Remove default button styles. 224 | */ 225 | 226 | button, 227 | [type='button'], 228 | [type='reset'], 229 | [type='submit'] { 230 | -webkit-appearance: button; 231 | /* 1 */ 232 | background-color: transparent; 233 | /* 2 */ 234 | background-image: none; 235 | /* 2 */ 236 | } 237 | 238 | /* 239 | Use the modern Firefox focus style for all focusable elements. 240 | */ 241 | 242 | :-moz-focusring { 243 | outline: auto; 244 | } 245 | 246 | /* 247 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) 248 | */ 249 | 250 | :-moz-ui-invalid { 251 | box-shadow: none; 252 | } 253 | 254 | /* 255 | Add the correct vertical alignment in Chrome and Firefox. 256 | */ 257 | 258 | progress { 259 | vertical-align: baseline; 260 | } 261 | 262 | /* 263 | Correct the cursor style of increment and decrement buttons in Safari. 264 | */ 265 | 266 | ::-webkit-inner-spin-button, 267 | ::-webkit-outer-spin-button { 268 | height: auto; 269 | } 270 | 271 | /* 272 | 1. Correct the odd appearance in Chrome and Safari. 273 | 2. Correct the outline style in Safari. 274 | */ 275 | 276 | [type='search'] { 277 | -webkit-appearance: textfield; 278 | /* 1 */ 279 | outline-offset: -2px; 280 | /* 2 */ 281 | } 282 | 283 | /* 284 | Remove the inner padding in Chrome and Safari on macOS. 285 | */ 286 | 287 | ::-webkit-search-decoration { 288 | -webkit-appearance: none; 289 | } 290 | 291 | /* 292 | 1. Correct the inability to style clickable types in iOS and Safari. 293 | 2. Change font properties to `inherit` in Safari. 294 | */ 295 | 296 | ::-webkit-file-upload-button { 297 | -webkit-appearance: button; 298 | /* 1 */ 299 | font: inherit; 300 | /* 2 */ 301 | } 302 | 303 | /* 304 | Add the correct display in Chrome and Safari. 305 | */ 306 | 307 | summary { 308 | display: list-item; 309 | } 310 | 311 | /* 312 | Removes the default spacing and border for appropriate elements. 313 | */ 314 | 315 | blockquote, 316 | dl, 317 | dd, 318 | h1, 319 | h2, 320 | h3, 321 | h4, 322 | h5, 323 | h6, 324 | hr, 325 | figure, 326 | p, 327 | pre { 328 | margin: 0; 329 | } 330 | 331 | fieldset { 332 | margin: 0; 333 | padding: 0; 334 | } 335 | 336 | legend { 337 | padding: 0; 338 | } 339 | 340 | ol, 341 | ul, 342 | menu { 343 | list-style: none; 344 | margin: 0; 345 | padding: 0; 346 | } 347 | 348 | /* 349 | Reset default styling for dialogs. 350 | */ 351 | 352 | dialog { 353 | padding: 0; 354 | } 355 | 356 | /* 357 | Prevent resizing textareas horizontally by default. 358 | */ 359 | 360 | textarea { 361 | resize: vertical; 362 | } 363 | 364 | /* 365 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) 366 | 2. Set the default placeholder color to the user's configured gray 400 color. 367 | */ 368 | 369 | input::-moz-placeholder, textarea::-moz-placeholder { 370 | opacity: 1; 371 | /* 1 */ 372 | color: #9ca3af; 373 | /* 2 */ 374 | } 375 | 376 | input::placeholder, 377 | textarea::placeholder { 378 | opacity: 1; 379 | /* 1 */ 380 | color: #9ca3af; 381 | /* 2 */ 382 | } 383 | 384 | /* 385 | Set the default cursor for buttons. 386 | */ 387 | 388 | button, 389 | [role="button"] { 390 | cursor: pointer; 391 | } 392 | 393 | /* 394 | Make sure disabled buttons don't get the pointer cursor. 395 | */ 396 | 397 | :disabled { 398 | cursor: default; 399 | } 400 | 401 | /* 402 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) 403 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) 404 | This can trigger a poorly considered lint error in some tools but is included by design. 405 | */ 406 | 407 | img, 408 | svg, 409 | video, 410 | canvas, 411 | audio, 412 | iframe, 413 | embed, 414 | object { 415 | display: block; 416 | /* 1 */ 417 | vertical-align: middle; 418 | /* 2 */ 419 | } 420 | 421 | /* 422 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) 423 | */ 424 | 425 | img, 426 | video { 427 | max-width: 100%; 428 | height: auto; 429 | } 430 | 431 | /* Make elements with the HTML hidden attribute stay hidden by default */ 432 | 433 | [hidden] { 434 | display: none; 435 | } 436 | 437 | :root, 438 | [data-theme] { 439 | background-color: hsl(var(--b1) / var(--tw-bg-opacity, 1)); 440 | color: hsl(var(--bc) / var(--tw-text-opacity, 1)); 441 | } 442 | 443 | html { 444 | -webkit-tap-highlight-color: transparent; 445 | } 446 | 447 | :root { 448 | color-scheme: light; 449 | --pf: 259 94% 44%; 450 | --sf: 314 100% 40%; 451 | --af: 174 75% 39%; 452 | --nf: 214 20% 14%; 453 | --in: 198 93% 60%; 454 | --su: 158 64% 52%; 455 | --wa: 43 96% 56%; 456 | --er: 0 91% 71%; 457 | --inc: 198 100% 12%; 458 | --suc: 158 100% 10%; 459 | --wac: 43 100% 11%; 460 | --erc: 0 100% 14%; 461 | --rounded-box: 1rem; 462 | --rounded-btn: 0.5rem; 463 | --rounded-badge: 1.9rem; 464 | --animation-btn: 0.25s; 465 | --animation-input: .2s; 466 | --btn-text-case: uppercase; 467 | --btn-focus-scale: 0.95; 468 | --border-btn: 1px; 469 | --tab-border: 1px; 470 | --tab-radius: 0.5rem; 471 | --p: 259 94% 51%; 472 | --pc: 259 96% 91%; 473 | --s: 314 100% 47%; 474 | --sc: 314 100% 91%; 475 | --a: 174 75% 46%; 476 | --ac: 174 75% 11%; 477 | --n: 214 20% 21%; 478 | --nc: 212 19% 87%; 479 | --b1: 0 0% 100%; 480 | --b2: 0 0% 95%; 481 | --b3: 180 2% 90%; 482 | --bc: 215 28% 17%; 483 | } 484 | 485 | @media (prefers-color-scheme: dark) { 486 | :root { 487 | color-scheme: dark; 488 | --pf: 262 80% 43%; 489 | --sf: 316 70% 43%; 490 | --af: 175 70% 34%; 491 | --in: 198 93% 60%; 492 | --su: 158 64% 52%; 493 | --wa: 43 96% 56%; 494 | --er: 0 91% 71%; 495 | --inc: 198 100% 12%; 496 | --suc: 158 100% 10%; 497 | --wac: 43 100% 11%; 498 | --erc: 0 100% 14%; 499 | --rounded-box: 1rem; 500 | --rounded-btn: 0.5rem; 501 | --rounded-badge: 1.9rem; 502 | --animation-btn: 0.25s; 503 | --animation-input: .2s; 504 | --btn-text-case: uppercase; 505 | --btn-focus-scale: 0.95; 506 | --border-btn: 1px; 507 | --tab-border: 1px; 508 | --tab-radius: 0.5rem; 509 | --p: 262 80% 50%; 510 | --pc: 0 0% 100%; 511 | --s: 316 70% 50%; 512 | --sc: 0 0% 100%; 513 | --a: 175 70% 41%; 514 | --ac: 0 0% 100%; 515 | --n: 213 18% 20%; 516 | --nf: 212 17% 17%; 517 | --nc: 220 13% 69%; 518 | --b1: 212 18% 14%; 519 | --b2: 213 18% 12%; 520 | --b3: 213 18% 10%; 521 | --bc: 220 13% 69%; 522 | } 523 | } 524 | 525 | [data-theme=light] { 526 | color-scheme: light; 527 | --pf: 259 94% 44%; 528 | --sf: 314 100% 40%; 529 | --af: 174 75% 39%; 530 | --nf: 214 20% 14%; 531 | --in: 198 93% 60%; 532 | --su: 158 64% 52%; 533 | --wa: 43 96% 56%; 534 | --er: 0 91% 71%; 535 | --inc: 198 100% 12%; 536 | --suc: 158 100% 10%; 537 | --wac: 43 100% 11%; 538 | --erc: 0 100% 14%; 539 | --rounded-box: 1rem; 540 | --rounded-btn: 0.5rem; 541 | --rounded-badge: 1.9rem; 542 | --animation-btn: 0.25s; 543 | --animation-input: .2s; 544 | --btn-text-case: uppercase; 545 | --btn-focus-scale: 0.95; 546 | --border-btn: 1px; 547 | --tab-border: 1px; 548 | --tab-radius: 0.5rem; 549 | --p: 259 94% 51%; 550 | --pc: 259 96% 91%; 551 | --s: 314 100% 47%; 552 | --sc: 314 100% 91%; 553 | --a: 174 75% 46%; 554 | --ac: 174 75% 11%; 555 | --n: 214 20% 21%; 556 | --nc: 212 19% 87%; 557 | --b1: 0 0% 100%; 558 | --b2: 0 0% 95%; 559 | --b3: 180 2% 90%; 560 | --bc: 215 28% 17%; 561 | } 562 | 563 | [data-theme=dark] { 564 | color-scheme: dark; 565 | --pf: 262 80% 43%; 566 | --sf: 316 70% 43%; 567 | --af: 175 70% 34%; 568 | --in: 198 93% 60%; 569 | --su: 158 64% 52%; 570 | --wa: 43 96% 56%; 571 | --er: 0 91% 71%; 572 | --inc: 198 100% 12%; 573 | --suc: 158 100% 10%; 574 | --wac: 43 100% 11%; 575 | --erc: 0 100% 14%; 576 | --rounded-box: 1rem; 577 | --rounded-btn: 0.5rem; 578 | --rounded-badge: 1.9rem; 579 | --animation-btn: 0.25s; 580 | --animation-input: .2s; 581 | --btn-text-case: uppercase; 582 | --btn-focus-scale: 0.95; 583 | --border-btn: 1px; 584 | --tab-border: 1px; 585 | --tab-radius: 0.5rem; 586 | --p: 262 80% 50%; 587 | --pc: 0 0% 100%; 588 | --s: 316 70% 50%; 589 | --sc: 0 0% 100%; 590 | --a: 175 70% 41%; 591 | --ac: 0 0% 100%; 592 | --n: 213 18% 20%; 593 | --nf: 212 17% 17%; 594 | --nc: 220 13% 69%; 595 | --b1: 212 18% 14%; 596 | --b2: 213 18% 12%; 597 | --b3: 213 18% 10%; 598 | --bc: 220 13% 69%; 599 | } 600 | 601 | *, ::before, ::after { 602 | --tw-border-spacing-x: 0; 603 | --tw-border-spacing-y: 0; 604 | --tw-translate-x: 0; 605 | --tw-translate-y: 0; 606 | --tw-rotate: 0; 607 | --tw-skew-x: 0; 608 | --tw-skew-y: 0; 609 | --tw-scale-x: 1; 610 | --tw-scale-y: 1; 611 | --tw-pan-x: ; 612 | --tw-pan-y: ; 613 | --tw-pinch-zoom: ; 614 | --tw-scroll-snap-strictness: proximity; 615 | --tw-gradient-from-position: ; 616 | --tw-gradient-via-position: ; 617 | --tw-gradient-to-position: ; 618 | --tw-ordinal: ; 619 | --tw-slashed-zero: ; 620 | --tw-numeric-figure: ; 621 | --tw-numeric-spacing: ; 622 | --tw-numeric-fraction: ; 623 | --tw-ring-inset: ; 624 | --tw-ring-offset-width: 0px; 625 | --tw-ring-offset-color: #fff; 626 | --tw-ring-color: rgb(59 130 246 / 0.5); 627 | --tw-ring-offset-shadow: 0 0 #0000; 628 | --tw-ring-shadow: 0 0 #0000; 629 | --tw-shadow: 0 0 #0000; 630 | --tw-shadow-colored: 0 0 #0000; 631 | --tw-blur: ; 632 | --tw-brightness: ; 633 | --tw-contrast: ; 634 | --tw-grayscale: ; 635 | --tw-hue-rotate: ; 636 | --tw-invert: ; 637 | --tw-saturate: ; 638 | --tw-sepia: ; 639 | --tw-drop-shadow: ; 640 | --tw-backdrop-blur: ; 641 | --tw-backdrop-brightness: ; 642 | --tw-backdrop-contrast: ; 643 | --tw-backdrop-grayscale: ; 644 | --tw-backdrop-hue-rotate: ; 645 | --tw-backdrop-invert: ; 646 | --tw-backdrop-opacity: ; 647 | --tw-backdrop-saturate: ; 648 | --tw-backdrop-sepia: ; 649 | } 650 | 651 | ::backdrop { 652 | --tw-border-spacing-x: 0; 653 | --tw-border-spacing-y: 0; 654 | --tw-translate-x: 0; 655 | --tw-translate-y: 0; 656 | --tw-rotate: 0; 657 | --tw-skew-x: 0; 658 | --tw-skew-y: 0; 659 | --tw-scale-x: 1; 660 | --tw-scale-y: 1; 661 | --tw-pan-x: ; 662 | --tw-pan-y: ; 663 | --tw-pinch-zoom: ; 664 | --tw-scroll-snap-strictness: proximity; 665 | --tw-gradient-from-position: ; 666 | --tw-gradient-via-position: ; 667 | --tw-gradient-to-position: ; 668 | --tw-ordinal: ; 669 | --tw-slashed-zero: ; 670 | --tw-numeric-figure: ; 671 | --tw-numeric-spacing: ; 672 | --tw-numeric-fraction: ; 673 | --tw-ring-inset: ; 674 | --tw-ring-offset-width: 0px; 675 | --tw-ring-offset-color: #fff; 676 | --tw-ring-color: rgb(59 130 246 / 0.5); 677 | --tw-ring-offset-shadow: 0 0 #0000; 678 | --tw-ring-shadow: 0 0 #0000; 679 | --tw-shadow: 0 0 #0000; 680 | --tw-shadow-colored: 0 0 #0000; 681 | --tw-blur: ; 682 | --tw-brightness: ; 683 | --tw-contrast: ; 684 | --tw-grayscale: ; 685 | --tw-hue-rotate: ; 686 | --tw-invert: ; 687 | --tw-saturate: ; 688 | --tw-sepia: ; 689 | --tw-drop-shadow: ; 690 | --tw-backdrop-blur: ; 691 | --tw-backdrop-brightness: ; 692 | --tw-backdrop-contrast: ; 693 | --tw-backdrop-grayscale: ; 694 | --tw-backdrop-hue-rotate: ; 695 | --tw-backdrop-invert: ; 696 | --tw-backdrop-opacity: ; 697 | --tw-backdrop-saturate: ; 698 | --tw-backdrop-sepia: ; 699 | } 700 | 701 | .chat { 702 | display: grid; 703 | grid-template-columns: repeat(2, minmax(0, 1fr)); 704 | -moz-column-gap: 0.75rem; 705 | column-gap: 0.75rem; 706 | padding-top: 0.25rem; 707 | padding-bottom: 0.25rem; 708 | } 709 | 710 | .chat-bubble { 711 | position: relative; 712 | display: block; 713 | width: -moz-fit-content; 714 | width: fit-content; 715 | padding-left: 1rem; 716 | padding-right: 1rem; 717 | padding-top: 0.5rem; 718 | padding-bottom: 0.5rem; 719 | max-width: 90%; 720 | border-radius: var(--rounded-box, 1rem); 721 | min-height: 2.75rem; 722 | min-width: 2.75rem; 723 | --tw-bg-opacity: 1; 724 | background-color: hsl(var(--n) / var(--tw-bg-opacity)); 725 | --tw-text-opacity: 1; 726 | color: hsl(var(--nc) / var(--tw-text-opacity)); 727 | } 728 | 729 | .chat-bubble:before { 730 | position: absolute; 731 | bottom: 0px; 732 | height: 0.75rem; 733 | width: 0.75rem; 734 | background-color: inherit; 735 | content: ""; 736 | -webkit-mask-size: contain; 737 | mask-size: contain; 738 | -webkit-mask-repeat: no-repeat; 739 | mask-repeat: no-repeat; 740 | -webkit-mask-position: center; 741 | mask-position: center; 742 | } 743 | 744 | .chat-start { 745 | place-items: start; 746 | grid-template-columns: auto 1fr; 747 | } 748 | 749 | .chat-start .chat-header { 750 | grid-column-start: 2; 751 | } 752 | 753 | .chat-start .chat-footer { 754 | grid-column-start: 2; 755 | } 756 | 757 | .chat-start .chat-image { 758 | grid-column-start: 1; 759 | } 760 | 761 | .chat-start .chat-bubble { 762 | grid-column-start: 2; 763 | border-bottom-left-radius: 0px; 764 | } 765 | 766 | .chat-start .chat-bubble:before { 767 | -webkit-mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 3 3 L 3 0 C 3 1 1 3 0 3'/%3e%3c/svg%3e"); 768 | mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 3 3 L 3 0 C 3 1 1 3 0 3'/%3e%3c/svg%3e"); 769 | left: -0.749rem; 770 | } 771 | 772 | [dir="rtl"] .chat-start .chat-bubble:before { 773 | -webkit-mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 1 3 L 3 3 C 2 3 0 1 0 0'/%3e%3c/svg%3e"); 774 | mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 1 3 L 3 3 C 2 3 0 1 0 0'/%3e%3c/svg%3e"); 775 | } 776 | 777 | .chat-end { 778 | place-items: end; 779 | grid-template-columns: 1fr auto; 780 | } 781 | 782 | .chat-end .chat-header { 783 | grid-column-start: 1; 784 | } 785 | 786 | .chat-end .chat-footer { 787 | grid-column-start: 1; 788 | } 789 | 790 | .chat-end .chat-image { 791 | grid-column-start: 2; 792 | } 793 | 794 | .chat-end .chat-bubble { 795 | grid-column-start: 1; 796 | border-bottom-right-radius: 0px; 797 | } 798 | 799 | .chat-end .chat-bubble:before { 800 | -webkit-mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 1 3 L 3 3 C 2 3 0 1 0 0'/%3e%3c/svg%3e"); 801 | mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 1 3 L 3 3 C 2 3 0 1 0 0'/%3e%3c/svg%3e"); 802 | left: 99.9%; 803 | } 804 | 805 | [dir="rtl"] .chat-end .chat-bubble:before { 806 | -webkit-mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 3 3 L 3 0 C 3 1 1 3 0 3'/%3e%3c/svg%3e"); 807 | mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 3 3 L 3 0 C 3 1 1 3 0 3'/%3e%3c/svg%3e"); 808 | } 809 | 810 | .link { 811 | cursor: pointer; 812 | text-decoration-line: underline; 813 | } 814 | 815 | .mockup-code { 816 | position: relative; 817 | overflow: hidden; 818 | overflow-x: auto; 819 | min-width: 18rem; 820 | --tw-bg-opacity: 1; 821 | background-color: hsl(var(--n) / var(--tw-bg-opacity)); 822 | padding-top: 1.25rem; 823 | padding-bottom: 1.25rem; 824 | --tw-text-opacity: 1; 825 | color: hsl(var(--nc) / var(--tw-text-opacity)); 826 | border-radius: var(--rounded-box, 1rem); 827 | } 828 | 829 | .mockup-code pre[data-prefix]:before { 830 | content: attr(data-prefix); 831 | display: inline-block; 832 | text-align: right; 833 | width: 2rem; 834 | opacity: 0.5; 835 | } 836 | 837 | .stats { 838 | display: inline-grid; 839 | --tw-bg-opacity: 1; 840 | background-color: hsl(var(--b1) / var(--tw-bg-opacity)); 841 | --tw-text-opacity: 1; 842 | color: hsl(var(--bc) / var(--tw-text-opacity)); 843 | border-radius: var(--rounded-box, 1rem); 844 | } 845 | 846 | :where(.stats) { 847 | grid-auto-flow: column; 848 | overflow-x: auto; 849 | } 850 | 851 | .stat { 852 | display: inline-grid; 853 | width: 100%; 854 | grid-template-columns: repeat(1, 1fr); 855 | -moz-column-gap: 1rem; 856 | column-gap: 1rem; 857 | border-color: hsl(var(--bc) / var(--tw-border-opacity)); 858 | --tw-border-opacity: 0.1; 859 | padding-left: 1.5rem; 860 | padding-right: 1.5rem; 861 | padding-top: 1rem; 862 | padding-bottom: 1rem; 863 | } 864 | 865 | .stat-title { 866 | grid-column-start: 1; 867 | white-space: nowrap; 868 | color: hsl(var(--bc) / 0.6); 869 | } 870 | 871 | .stat-value { 872 | grid-column-start: 1; 873 | white-space: nowrap; 874 | font-size: 2.25rem; 875 | line-height: 2.5rem; 876 | font-weight: 800; 877 | } 878 | 879 | .stat-desc { 880 | grid-column-start: 1; 881 | white-space: nowrap; 882 | font-size: 0.75rem; 883 | line-height: 1rem; 884 | color: hsl(var(--bc) / 0.6); 885 | } 886 | 887 | @keyframes button-pop { 888 | 0% { 889 | transform: scale(var(--btn-focus-scale, 0.98)); 890 | } 891 | 892 | 40% { 893 | transform: scale(1.02); 894 | } 895 | 896 | 100% { 897 | transform: scale(1); 898 | } 899 | } 900 | 901 | .chat-bubble-primary { 902 | --tw-bg-opacity: 1; 903 | background-color: hsl(var(--p) / var(--tw-bg-opacity)); 904 | --tw-text-opacity: 1; 905 | color: hsl(var(--pc) / var(--tw-text-opacity)); 906 | } 907 | 908 | .chat-bubble-secondary { 909 | --tw-bg-opacity: 1; 910 | background-color: hsl(var(--s) / var(--tw-bg-opacity)); 911 | --tw-text-opacity: 1; 912 | color: hsl(var(--sc) / var(--tw-text-opacity)); 913 | } 914 | 915 | .chat-bubble-accent { 916 | --tw-bg-opacity: 1; 917 | background-color: hsl(var(--a) / var(--tw-bg-opacity)); 918 | --tw-text-opacity: 1; 919 | color: hsl(var(--ac) / var(--tw-text-opacity)); 920 | } 921 | 922 | .chat-bubble-info { 923 | --tw-bg-opacity: 1; 924 | background-color: hsl(var(--in) / var(--tw-bg-opacity)); 925 | --tw-text-opacity: 1; 926 | color: hsl(var(--inc) / var(--tw-text-opacity)); 927 | } 928 | 929 | .chat-bubble-success { 930 | --tw-bg-opacity: 1; 931 | background-color: hsl(var(--su) / var(--tw-bg-opacity)); 932 | --tw-text-opacity: 1; 933 | color: hsl(var(--suc) / var(--tw-text-opacity)); 934 | } 935 | 936 | .chat-bubble-warning { 937 | --tw-bg-opacity: 1; 938 | background-color: hsl(var(--wa) / var(--tw-bg-opacity)); 939 | --tw-text-opacity: 1; 940 | color: hsl(var(--wac) / var(--tw-text-opacity)); 941 | } 942 | 943 | .chat-bubble-error { 944 | --tw-bg-opacity: 1; 945 | background-color: hsl(var(--er) / var(--tw-bg-opacity)); 946 | --tw-text-opacity: 1; 947 | color: hsl(var(--erc) / var(--tw-text-opacity)); 948 | } 949 | 950 | @keyframes checkmark { 951 | 0% { 952 | background-position-y: 5px; 953 | } 954 | 955 | 50% { 956 | background-position-y: -2px; 957 | } 958 | 959 | 100% { 960 | background-position-y: 0; 961 | } 962 | } 963 | 964 | .link:focus { 965 | outline: 2px solid transparent; 966 | outline-offset: 2px; 967 | } 968 | 969 | .link:focus-visible { 970 | outline: 2px solid currentColor; 971 | outline-offset: 2px; 972 | } 973 | 974 | .mockup-code:before { 975 | content: ""; 976 | margin-bottom: 1rem; 977 | display: block; 978 | height: 0.75rem; 979 | width: 0.75rem; 980 | border-radius: 9999px; 981 | opacity: 0.3; 982 | box-shadow: 1.4em 0, 983 | 2.8em 0, 984 | 4.2em 0; 985 | } 986 | 987 | .mockup-code pre { 988 | padding-right: 1.25rem; 989 | } 990 | 991 | .mockup-code pre:before { 992 | content: ""; 993 | margin-right: 2ch; 994 | } 995 | 996 | @keyframes modal-pop { 997 | 0% { 998 | opacity: 0; 999 | } 1000 | } 1001 | 1002 | @keyframes progress-loading { 1003 | 50% { 1004 | background-position-x: -115%; 1005 | } 1006 | } 1007 | 1008 | @keyframes radiomark { 1009 | 0% { 1010 | box-shadow: 0 0 0 12px hsl(var(--b1)) inset, 1011 | 0 0 0 12px hsl(var(--b1)) inset; 1012 | } 1013 | 1014 | 50% { 1015 | box-shadow: 0 0 0 3px hsl(var(--b1)) inset, 1016 | 0 0 0 3px hsl(var(--b1)) inset; 1017 | } 1018 | 1019 | 100% { 1020 | box-shadow: 0 0 0 4px hsl(var(--b1)) inset, 1021 | 0 0 0 4px hsl(var(--b1)) inset; 1022 | } 1023 | } 1024 | 1025 | @keyframes rating-pop { 1026 | 0% { 1027 | transform: translateY(-0.125em); 1028 | } 1029 | 1030 | 40% { 1031 | transform: translateY(-0.125em); 1032 | } 1033 | 1034 | 100% { 1035 | transform: translateY(0); 1036 | } 1037 | } 1038 | 1039 | :where(.stats) > :not([hidden]) ~ :not([hidden]) { 1040 | --tw-divide-x-reverse: 0; 1041 | border-right-width: calc(1px * var(--tw-divide-x-reverse)); 1042 | border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse))); 1043 | --tw-divide-y-reverse: 0; 1044 | border-top-width: calc(0px * calc(1 - var(--tw-divide-y-reverse))); 1045 | border-bottom-width: calc(0px * var(--tw-divide-y-reverse)); 1046 | } 1047 | 1048 | @keyframes toast-pop { 1049 | 0% { 1050 | transform: scale(0.9); 1051 | opacity: 0; 1052 | } 1053 | 1054 | 100% { 1055 | transform: scale(1); 1056 | opacity: 1; 1057 | } 1058 | } 1059 | 1060 | .mx-auto { 1061 | margin-left: auto; 1062 | margin-right: auto; 1063 | } 1064 | 1065 | .my-5 { 1066 | margin-top: 1.25rem; 1067 | margin-bottom: 1.25rem; 1068 | } 1069 | 1070 | .w-\[50px\] { 1071 | width: 50px; 1072 | } 1073 | 1074 | .max-w-3xl { 1075 | max-width: 48rem; 1076 | } 1077 | 1078 | .p-2 { 1079 | padding: 0.5rem; 1080 | } 1081 | 1082 | .p-6 { 1083 | padding: 1.5rem; 1084 | } 1085 | 1086 | .text-center { 1087 | text-align: center; 1088 | } 1089 | 1090 | .text-2xl { 1091 | font-size: 1.5rem; 1092 | line-height: 2rem; 1093 | } 1094 | 1095 | .text-4xl { 1096 | font-size: 2.25rem; 1097 | line-height: 2.5rem; 1098 | } 1099 | 1100 | .text-success { 1101 | --tw-text-opacity: 1; 1102 | color: hsl(var(--su) / var(--tw-text-opacity)); 1103 | } 1104 | 1105 | .text-warning { 1106 | --tw-text-opacity: 1; 1107 | color: hsl(var(--wa) / var(--tw-text-opacity)); 1108 | } 1109 | 1110 | .shadow { 1111 | --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); 1112 | --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); 1113 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 1114 | } 1115 | 1116 | .\[margin\:auto\] { 1117 | margin: auto; 1118 | } 1119 | -------------------------------------------------------------------------------- /examples/leptos-demo/dist/tailwind-csr-trunk-4ed9e9e4c35d1fac_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oyelowo/twust/01fa4850ebeff6277518e49bdead32e858e9249d/examples/leptos-demo/dist/tailwind-csr-trunk-4ed9e9e4c35d1fac_bg.wasm -------------------------------------------------------------------------------- /examples/leptos-demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Leptos • Counter with Tailwind 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/leptos-demo/input.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /examples/leptos-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "daisyui": "^3.7.3", 4 | "tailwindcss": "^3.3.3" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/leptos-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oyelowo/twust/01fa4850ebeff6277518e49bdead32e858e9249d/examples/leptos-demo/public/favicon.ico -------------------------------------------------------------------------------- /examples/leptos-demo/src/app.rs: -------------------------------------------------------------------------------- 1 | use leptos::prelude::*; 2 | use twust::tw; 3 | 4 | #[component] 5 | pub fn Home() -> impl IntoView { 6 | // Try to break mistype any of the class name and see what happens. 7 | // Daisyui classes are also ssupported via a feature flag 8 | let _x = tw!("my-5 mx-auto max-w-3xl text-center"); 9 | view! { 10 |
11 |

"Twust"

12 | "Check your tailwind classes instantly with twust." 13 |

"We also support daisyui plugin."

14 | 15 |
16 |
cargo add twust
17 |
installing...
18 |
Done!
19 |
20 | 21 |
22 |
23 |
Total Downloads
24 |
193,245,999
25 |
74% more than last year
26 |
27 |
28 | 29 |
30 |
31 | "🤖 What's your name?" 32 |
33 |
34 |
35 |
36 | "🙂 My name is Oyelowo. How can I assist you today?" 37 |
38 |
39 |
40 |
41 | "🍯 Favorite type of syrup?" 42 |
43 |
44 |
45 |
46 | "🍁 Oh, it's definitely Maple syrup!" 47 |
48 |
49 |
50 |
51 | "⚽ Do you have a favorite sports team?" 52 |
53 |
54 |
55 |
56 | "🏒 Absolutely! I'm a big fan of The Oilers!" 57 |
58 |
59 |
60 |
61 | "🎉 Coool! I'll remember that!" 62 |
63 |
64 |
65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/leptos-demo/src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | 3 | use app::*; 4 | use leptos::*; 5 | 6 | use leptos_router::components::Router; 7 | 8 | pub fn main() { 9 | console_error_panic_hook::set_once(); 10 | leptos::mount::mount_to_body(|| { 11 | view! { 12 | 13 | 14 | 15 | } 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /examples/leptos-demo/style/output.css: -------------------------------------------------------------------------------- 1 | /* 2 | ! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com 3 | */ 4 | 5 | /* 6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) 7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) 8 | */ 9 | 10 | *, 11 | ::before, 12 | ::after { 13 | box-sizing: border-box; 14 | /* 1 */ 15 | border-width: 0; 16 | /* 2 */ 17 | border-style: solid; 18 | /* 2 */ 19 | border-color: #e5e7eb; 20 | /* 2 */ 21 | } 22 | 23 | ::before, 24 | ::after { 25 | --tw-content: ''; 26 | } 27 | 28 | /* 29 | 1. Use a consistent sensible line-height in all browsers. 30 | 2. Prevent adjustments of font size after orientation changes in iOS. 31 | 3. Use a more readable tab size. 32 | 4. Use the user's configured `sans` font-family by default. 33 | 5. Use the user's configured `sans` font-feature-settings by default. 34 | 6. Use the user's configured `sans` font-variation-settings by default. 35 | */ 36 | 37 | html { 38 | line-height: 1.5; 39 | /* 1 */ 40 | -webkit-text-size-adjust: 100%; 41 | /* 2 */ 42 | -moz-tab-size: 4; 43 | /* 3 */ 44 | -o-tab-size: 4; 45 | tab-size: 4; 46 | /* 3 */ 47 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 48 | /* 4 */ 49 | font-feature-settings: normal; 50 | /* 5 */ 51 | font-variation-settings: normal; 52 | /* 6 */ 53 | } 54 | 55 | /* 56 | 1. Remove the margin in all browsers. 57 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. 58 | */ 59 | 60 | body { 61 | margin: 0; 62 | /* 1 */ 63 | line-height: inherit; 64 | /* 2 */ 65 | } 66 | 67 | /* 68 | 1. Add the correct height in Firefox. 69 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) 70 | 3. Ensure horizontal rules are visible by default. 71 | */ 72 | 73 | hr { 74 | height: 0; 75 | /* 1 */ 76 | color: inherit; 77 | /* 2 */ 78 | border-top-width: 1px; 79 | /* 3 */ 80 | } 81 | 82 | /* 83 | Add the correct text decoration in Chrome, Edge, and Safari. 84 | */ 85 | 86 | abbr:where([title]) { 87 | -webkit-text-decoration: underline dotted; 88 | text-decoration: underline dotted; 89 | } 90 | 91 | /* 92 | Remove the default font size and weight for headings. 93 | */ 94 | 95 | h1, 96 | h2, 97 | h3, 98 | h4, 99 | h5, 100 | h6 { 101 | font-size: inherit; 102 | font-weight: inherit; 103 | } 104 | 105 | /* 106 | Reset links to optimize for opt-in styling instead of opt-out. 107 | */ 108 | 109 | a { 110 | color: inherit; 111 | text-decoration: inherit; 112 | } 113 | 114 | /* 115 | Add the correct font weight in Edge and Safari. 116 | */ 117 | 118 | b, 119 | strong { 120 | font-weight: bolder; 121 | } 122 | 123 | /* 124 | 1. Use the user's configured `mono` font family by default. 125 | 2. Correct the odd `em` font sizing in all browsers. 126 | */ 127 | 128 | code, 129 | kbd, 130 | samp, 131 | pre { 132 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 133 | /* 1 */ 134 | font-size: 1em; 135 | /* 2 */ 136 | } 137 | 138 | /* 139 | Add the correct font size in all browsers. 140 | */ 141 | 142 | small { 143 | font-size: 80%; 144 | } 145 | 146 | /* 147 | Prevent `sub` and `sup` elements from affecting the line height in all browsers. 148 | */ 149 | 150 | sub, 151 | sup { 152 | font-size: 75%; 153 | line-height: 0; 154 | position: relative; 155 | vertical-align: baseline; 156 | } 157 | 158 | sub { 159 | bottom: -0.25em; 160 | } 161 | 162 | sup { 163 | top: -0.5em; 164 | } 165 | 166 | /* 167 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) 168 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) 169 | 3. Remove gaps between table borders by default. 170 | */ 171 | 172 | table { 173 | text-indent: 0; 174 | /* 1 */ 175 | border-color: inherit; 176 | /* 2 */ 177 | border-collapse: collapse; 178 | /* 3 */ 179 | } 180 | 181 | /* 182 | 1. Change the font styles in all browsers. 183 | 2. Remove the margin in Firefox and Safari. 184 | 3. Remove default padding in all browsers. 185 | */ 186 | 187 | button, 188 | input, 189 | optgroup, 190 | select, 191 | textarea { 192 | font-family: inherit; 193 | /* 1 */ 194 | font-feature-settings: inherit; 195 | /* 1 */ 196 | font-variation-settings: inherit; 197 | /* 1 */ 198 | font-size: 100%; 199 | /* 1 */ 200 | font-weight: inherit; 201 | /* 1 */ 202 | line-height: inherit; 203 | /* 1 */ 204 | color: inherit; 205 | /* 1 */ 206 | margin: 0; 207 | /* 2 */ 208 | padding: 0; 209 | /* 3 */ 210 | } 211 | 212 | /* 213 | Remove the inheritance of text transform in Edge and Firefox. 214 | */ 215 | 216 | button, 217 | select { 218 | text-transform: none; 219 | } 220 | 221 | /* 222 | 1. Correct the inability to style clickable types in iOS and Safari. 223 | 2. Remove default button styles. 224 | */ 225 | 226 | button, 227 | [type='button'], 228 | [type='reset'], 229 | [type='submit'] { 230 | -webkit-appearance: button; 231 | /* 1 */ 232 | background-color: transparent; 233 | /* 2 */ 234 | background-image: none; 235 | /* 2 */ 236 | } 237 | 238 | /* 239 | Use the modern Firefox focus style for all focusable elements. 240 | */ 241 | 242 | :-moz-focusring { 243 | outline: auto; 244 | } 245 | 246 | /* 247 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) 248 | */ 249 | 250 | :-moz-ui-invalid { 251 | box-shadow: none; 252 | } 253 | 254 | /* 255 | Add the correct vertical alignment in Chrome and Firefox. 256 | */ 257 | 258 | progress { 259 | vertical-align: baseline; 260 | } 261 | 262 | /* 263 | Correct the cursor style of increment and decrement buttons in Safari. 264 | */ 265 | 266 | ::-webkit-inner-spin-button, 267 | ::-webkit-outer-spin-button { 268 | height: auto; 269 | } 270 | 271 | /* 272 | 1. Correct the odd appearance in Chrome and Safari. 273 | 2. Correct the outline style in Safari. 274 | */ 275 | 276 | [type='search'] { 277 | -webkit-appearance: textfield; 278 | /* 1 */ 279 | outline-offset: -2px; 280 | /* 2 */ 281 | } 282 | 283 | /* 284 | Remove the inner padding in Chrome and Safari on macOS. 285 | */ 286 | 287 | ::-webkit-search-decoration { 288 | -webkit-appearance: none; 289 | } 290 | 291 | /* 292 | 1. Correct the inability to style clickable types in iOS and Safari. 293 | 2. Change font properties to `inherit` in Safari. 294 | */ 295 | 296 | ::-webkit-file-upload-button { 297 | -webkit-appearance: button; 298 | /* 1 */ 299 | font: inherit; 300 | /* 2 */ 301 | } 302 | 303 | /* 304 | Add the correct display in Chrome and Safari. 305 | */ 306 | 307 | summary { 308 | display: list-item; 309 | } 310 | 311 | /* 312 | Removes the default spacing and border for appropriate elements. 313 | */ 314 | 315 | blockquote, 316 | dl, 317 | dd, 318 | h1, 319 | h2, 320 | h3, 321 | h4, 322 | h5, 323 | h6, 324 | hr, 325 | figure, 326 | p, 327 | pre { 328 | margin: 0; 329 | } 330 | 331 | fieldset { 332 | margin: 0; 333 | padding: 0; 334 | } 335 | 336 | legend { 337 | padding: 0; 338 | } 339 | 340 | ol, 341 | ul, 342 | menu { 343 | list-style: none; 344 | margin: 0; 345 | padding: 0; 346 | } 347 | 348 | /* 349 | Reset default styling for dialogs. 350 | */ 351 | 352 | dialog { 353 | padding: 0; 354 | } 355 | 356 | /* 357 | Prevent resizing textareas horizontally by default. 358 | */ 359 | 360 | textarea { 361 | resize: vertical; 362 | } 363 | 364 | /* 365 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) 366 | 2. Set the default placeholder color to the user's configured gray 400 color. 367 | */ 368 | 369 | input::-moz-placeholder, textarea::-moz-placeholder { 370 | opacity: 1; 371 | /* 1 */ 372 | color: #9ca3af; 373 | /* 2 */ 374 | } 375 | 376 | input::placeholder, 377 | textarea::placeholder { 378 | opacity: 1; 379 | /* 1 */ 380 | color: #9ca3af; 381 | /* 2 */ 382 | } 383 | 384 | /* 385 | Set the default cursor for buttons. 386 | */ 387 | 388 | button, 389 | [role="button"] { 390 | cursor: pointer; 391 | } 392 | 393 | /* 394 | Make sure disabled buttons don't get the pointer cursor. 395 | */ 396 | 397 | :disabled { 398 | cursor: default; 399 | } 400 | 401 | /* 402 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) 403 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) 404 | This can trigger a poorly considered lint error in some tools but is included by design. 405 | */ 406 | 407 | img, 408 | svg, 409 | video, 410 | canvas, 411 | audio, 412 | iframe, 413 | embed, 414 | object { 415 | display: block; 416 | /* 1 */ 417 | vertical-align: middle; 418 | /* 2 */ 419 | } 420 | 421 | /* 422 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) 423 | */ 424 | 425 | img, 426 | video { 427 | max-width: 100%; 428 | height: auto; 429 | } 430 | 431 | /* Make elements with the HTML hidden attribute stay hidden by default */ 432 | 433 | [hidden] { 434 | display: none; 435 | } 436 | 437 | :root, 438 | [data-theme] { 439 | background-color: hsl(var(--b1) / var(--tw-bg-opacity, 1)); 440 | color: hsl(var(--bc) / var(--tw-text-opacity, 1)); 441 | } 442 | 443 | html { 444 | -webkit-tap-highlight-color: transparent; 445 | } 446 | 447 | :root { 448 | color-scheme: light; 449 | --pf: 259 94% 44%; 450 | --sf: 314 100% 40%; 451 | --af: 174 75% 39%; 452 | --nf: 214 20% 14%; 453 | --in: 198 93% 60%; 454 | --su: 158 64% 52%; 455 | --wa: 43 96% 56%; 456 | --er: 0 91% 71%; 457 | --inc: 198 100% 12%; 458 | --suc: 158 100% 10%; 459 | --wac: 43 100% 11%; 460 | --erc: 0 100% 14%; 461 | --rounded-box: 1rem; 462 | --rounded-btn: 0.5rem; 463 | --rounded-badge: 1.9rem; 464 | --animation-btn: 0.25s; 465 | --animation-input: .2s; 466 | --btn-text-case: uppercase; 467 | --btn-focus-scale: 0.95; 468 | --border-btn: 1px; 469 | --tab-border: 1px; 470 | --tab-radius: 0.5rem; 471 | --p: 259 94% 51%; 472 | --pc: 259 96% 91%; 473 | --s: 314 100% 47%; 474 | --sc: 314 100% 91%; 475 | --a: 174 75% 46%; 476 | --ac: 174 75% 11%; 477 | --n: 214 20% 21%; 478 | --nc: 212 19% 87%; 479 | --b1: 0 0% 100%; 480 | --b2: 0 0% 95%; 481 | --b3: 180 2% 90%; 482 | --bc: 215 28% 17%; 483 | } 484 | 485 | @media (prefers-color-scheme: dark) { 486 | :root { 487 | color-scheme: dark; 488 | --pf: 262 80% 43%; 489 | --sf: 316 70% 43%; 490 | --af: 175 70% 34%; 491 | --in: 198 93% 60%; 492 | --su: 158 64% 52%; 493 | --wa: 43 96% 56%; 494 | --er: 0 91% 71%; 495 | --inc: 198 100% 12%; 496 | --suc: 158 100% 10%; 497 | --wac: 43 100% 11%; 498 | --erc: 0 100% 14%; 499 | --rounded-box: 1rem; 500 | --rounded-btn: 0.5rem; 501 | --rounded-badge: 1.9rem; 502 | --animation-btn: 0.25s; 503 | --animation-input: .2s; 504 | --btn-text-case: uppercase; 505 | --btn-focus-scale: 0.95; 506 | --border-btn: 1px; 507 | --tab-border: 1px; 508 | --tab-radius: 0.5rem; 509 | --p: 262 80% 50%; 510 | --pc: 0 0% 100%; 511 | --s: 316 70% 50%; 512 | --sc: 0 0% 100%; 513 | --a: 175 70% 41%; 514 | --ac: 0 0% 100%; 515 | --n: 213 18% 20%; 516 | --nf: 212 17% 17%; 517 | --nc: 220 13% 69%; 518 | --b1: 212 18% 14%; 519 | --b2: 213 18% 12%; 520 | --b3: 213 18% 10%; 521 | --bc: 220 13% 69%; 522 | } 523 | } 524 | 525 | [data-theme=light] { 526 | color-scheme: light; 527 | --pf: 259 94% 44%; 528 | --sf: 314 100% 40%; 529 | --af: 174 75% 39%; 530 | --nf: 214 20% 14%; 531 | --in: 198 93% 60%; 532 | --su: 158 64% 52%; 533 | --wa: 43 96% 56%; 534 | --er: 0 91% 71%; 535 | --inc: 198 100% 12%; 536 | --suc: 158 100% 10%; 537 | --wac: 43 100% 11%; 538 | --erc: 0 100% 14%; 539 | --rounded-box: 1rem; 540 | --rounded-btn: 0.5rem; 541 | --rounded-badge: 1.9rem; 542 | --animation-btn: 0.25s; 543 | --animation-input: .2s; 544 | --btn-text-case: uppercase; 545 | --btn-focus-scale: 0.95; 546 | --border-btn: 1px; 547 | --tab-border: 1px; 548 | --tab-radius: 0.5rem; 549 | --p: 259 94% 51%; 550 | --pc: 259 96% 91%; 551 | --s: 314 100% 47%; 552 | --sc: 314 100% 91%; 553 | --a: 174 75% 46%; 554 | --ac: 174 75% 11%; 555 | --n: 214 20% 21%; 556 | --nc: 212 19% 87%; 557 | --b1: 0 0% 100%; 558 | --b2: 0 0% 95%; 559 | --b3: 180 2% 90%; 560 | --bc: 215 28% 17%; 561 | } 562 | 563 | [data-theme=dark] { 564 | color-scheme: dark; 565 | --pf: 262 80% 43%; 566 | --sf: 316 70% 43%; 567 | --af: 175 70% 34%; 568 | --in: 198 93% 60%; 569 | --su: 158 64% 52%; 570 | --wa: 43 96% 56%; 571 | --er: 0 91% 71%; 572 | --inc: 198 100% 12%; 573 | --suc: 158 100% 10%; 574 | --wac: 43 100% 11%; 575 | --erc: 0 100% 14%; 576 | --rounded-box: 1rem; 577 | --rounded-btn: 0.5rem; 578 | --rounded-badge: 1.9rem; 579 | --animation-btn: 0.25s; 580 | --animation-input: .2s; 581 | --btn-text-case: uppercase; 582 | --btn-focus-scale: 0.95; 583 | --border-btn: 1px; 584 | --tab-border: 1px; 585 | --tab-radius: 0.5rem; 586 | --p: 262 80% 50%; 587 | --pc: 0 0% 100%; 588 | --s: 316 70% 50%; 589 | --sc: 0 0% 100%; 590 | --a: 175 70% 41%; 591 | --ac: 0 0% 100%; 592 | --n: 213 18% 20%; 593 | --nf: 212 17% 17%; 594 | --nc: 220 13% 69%; 595 | --b1: 212 18% 14%; 596 | --b2: 213 18% 12%; 597 | --b3: 213 18% 10%; 598 | --bc: 220 13% 69%; 599 | } 600 | 601 | *, ::before, ::after { 602 | --tw-border-spacing-x: 0; 603 | --tw-border-spacing-y: 0; 604 | --tw-translate-x: 0; 605 | --tw-translate-y: 0; 606 | --tw-rotate: 0; 607 | --tw-skew-x: 0; 608 | --tw-skew-y: 0; 609 | --tw-scale-x: 1; 610 | --tw-scale-y: 1; 611 | --tw-pan-x: ; 612 | --tw-pan-y: ; 613 | --tw-pinch-zoom: ; 614 | --tw-scroll-snap-strictness: proximity; 615 | --tw-gradient-from-position: ; 616 | --tw-gradient-via-position: ; 617 | --tw-gradient-to-position: ; 618 | --tw-ordinal: ; 619 | --tw-slashed-zero: ; 620 | --tw-numeric-figure: ; 621 | --tw-numeric-spacing: ; 622 | --tw-numeric-fraction: ; 623 | --tw-ring-inset: ; 624 | --tw-ring-offset-width: 0px; 625 | --tw-ring-offset-color: #fff; 626 | --tw-ring-color: rgb(59 130 246 / 0.5); 627 | --tw-ring-offset-shadow: 0 0 #0000; 628 | --tw-ring-shadow: 0 0 #0000; 629 | --tw-shadow: 0 0 #0000; 630 | --tw-shadow-colored: 0 0 #0000; 631 | --tw-blur: ; 632 | --tw-brightness: ; 633 | --tw-contrast: ; 634 | --tw-grayscale: ; 635 | --tw-hue-rotate: ; 636 | --tw-invert: ; 637 | --tw-saturate: ; 638 | --tw-sepia: ; 639 | --tw-drop-shadow: ; 640 | --tw-backdrop-blur: ; 641 | --tw-backdrop-brightness: ; 642 | --tw-backdrop-contrast: ; 643 | --tw-backdrop-grayscale: ; 644 | --tw-backdrop-hue-rotate: ; 645 | --tw-backdrop-invert: ; 646 | --tw-backdrop-opacity: ; 647 | --tw-backdrop-saturate: ; 648 | --tw-backdrop-sepia: ; 649 | } 650 | 651 | ::backdrop { 652 | --tw-border-spacing-x: 0; 653 | --tw-border-spacing-y: 0; 654 | --tw-translate-x: 0; 655 | --tw-translate-y: 0; 656 | --tw-rotate: 0; 657 | --tw-skew-x: 0; 658 | --tw-skew-y: 0; 659 | --tw-scale-x: 1; 660 | --tw-scale-y: 1; 661 | --tw-pan-x: ; 662 | --tw-pan-y: ; 663 | --tw-pinch-zoom: ; 664 | --tw-scroll-snap-strictness: proximity; 665 | --tw-gradient-from-position: ; 666 | --tw-gradient-via-position: ; 667 | --tw-gradient-to-position: ; 668 | --tw-ordinal: ; 669 | --tw-slashed-zero: ; 670 | --tw-numeric-figure: ; 671 | --tw-numeric-spacing: ; 672 | --tw-numeric-fraction: ; 673 | --tw-ring-inset: ; 674 | --tw-ring-offset-width: 0px; 675 | --tw-ring-offset-color: #fff; 676 | --tw-ring-color: rgb(59 130 246 / 0.5); 677 | --tw-ring-offset-shadow: 0 0 #0000; 678 | --tw-ring-shadow: 0 0 #0000; 679 | --tw-shadow: 0 0 #0000; 680 | --tw-shadow-colored: 0 0 #0000; 681 | --tw-blur: ; 682 | --tw-brightness: ; 683 | --tw-contrast: ; 684 | --tw-grayscale: ; 685 | --tw-hue-rotate: ; 686 | --tw-invert: ; 687 | --tw-saturate: ; 688 | --tw-sepia: ; 689 | --tw-drop-shadow: ; 690 | --tw-backdrop-blur: ; 691 | --tw-backdrop-brightness: ; 692 | --tw-backdrop-contrast: ; 693 | --tw-backdrop-grayscale: ; 694 | --tw-backdrop-hue-rotate: ; 695 | --tw-backdrop-invert: ; 696 | --tw-backdrop-opacity: ; 697 | --tw-backdrop-saturate: ; 698 | --tw-backdrop-sepia: ; 699 | } 700 | 701 | .chat { 702 | display: grid; 703 | grid-template-columns: repeat(2, minmax(0, 1fr)); 704 | -moz-column-gap: 0.75rem; 705 | column-gap: 0.75rem; 706 | padding-top: 0.25rem; 707 | padding-bottom: 0.25rem; 708 | } 709 | 710 | .chat-bubble { 711 | position: relative; 712 | display: block; 713 | width: -moz-fit-content; 714 | width: fit-content; 715 | padding-left: 1rem; 716 | padding-right: 1rem; 717 | padding-top: 0.5rem; 718 | padding-bottom: 0.5rem; 719 | max-width: 90%; 720 | border-radius: var(--rounded-box, 1rem); 721 | min-height: 2.75rem; 722 | min-width: 2.75rem; 723 | --tw-bg-opacity: 1; 724 | background-color: hsl(var(--n) / var(--tw-bg-opacity)); 725 | --tw-text-opacity: 1; 726 | color: hsl(var(--nc) / var(--tw-text-opacity)); 727 | } 728 | 729 | .chat-bubble:before { 730 | position: absolute; 731 | bottom: 0px; 732 | height: 0.75rem; 733 | width: 0.75rem; 734 | background-color: inherit; 735 | content: ""; 736 | -webkit-mask-size: contain; 737 | mask-size: contain; 738 | -webkit-mask-repeat: no-repeat; 739 | mask-repeat: no-repeat; 740 | -webkit-mask-position: center; 741 | mask-position: center; 742 | } 743 | 744 | .chat-start { 745 | place-items: start; 746 | grid-template-columns: auto 1fr; 747 | } 748 | 749 | .chat-start .chat-header { 750 | grid-column-start: 2; 751 | } 752 | 753 | .chat-start .chat-footer { 754 | grid-column-start: 2; 755 | } 756 | 757 | .chat-start .chat-image { 758 | grid-column-start: 1; 759 | } 760 | 761 | .chat-start .chat-bubble { 762 | grid-column-start: 2; 763 | border-bottom-left-radius: 0px; 764 | } 765 | 766 | .chat-start .chat-bubble:before { 767 | -webkit-mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 3 3 L 3 0 C 3 1 1 3 0 3'/%3e%3c/svg%3e"); 768 | mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 3 3 L 3 0 C 3 1 1 3 0 3'/%3e%3c/svg%3e"); 769 | left: -0.749rem; 770 | } 771 | 772 | [dir="rtl"] .chat-start .chat-bubble:before { 773 | -webkit-mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 1 3 L 3 3 C 2 3 0 1 0 0'/%3e%3c/svg%3e"); 774 | mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 1 3 L 3 3 C 2 3 0 1 0 0'/%3e%3c/svg%3e"); 775 | } 776 | 777 | .chat-end { 778 | place-items: end; 779 | grid-template-columns: 1fr auto; 780 | } 781 | 782 | .chat-end .chat-header { 783 | grid-column-start: 1; 784 | } 785 | 786 | .chat-end .chat-footer { 787 | grid-column-start: 1; 788 | } 789 | 790 | .chat-end .chat-image { 791 | grid-column-start: 2; 792 | } 793 | 794 | .chat-end .chat-bubble { 795 | grid-column-start: 1; 796 | border-bottom-right-radius: 0px; 797 | } 798 | 799 | .chat-end .chat-bubble:before { 800 | -webkit-mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 1 3 L 3 3 C 2 3 0 1 0 0'/%3e%3c/svg%3e"); 801 | mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 1 3 L 3 3 C 2 3 0 1 0 0'/%3e%3c/svg%3e"); 802 | left: 99.9%; 803 | } 804 | 805 | [dir="rtl"] .chat-end .chat-bubble:before { 806 | -webkit-mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 3 3 L 3 0 C 3 1 1 3 0 3'/%3e%3c/svg%3e"); 807 | mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 3 3 L 3 0 C 3 1 1 3 0 3'/%3e%3c/svg%3e"); 808 | } 809 | 810 | .link { 811 | cursor: pointer; 812 | text-decoration-line: underline; 813 | } 814 | 815 | .mockup-code { 816 | position: relative; 817 | overflow: hidden; 818 | overflow-x: auto; 819 | min-width: 18rem; 820 | --tw-bg-opacity: 1; 821 | background-color: hsl(var(--n) / var(--tw-bg-opacity)); 822 | padding-top: 1.25rem; 823 | padding-bottom: 1.25rem; 824 | --tw-text-opacity: 1; 825 | color: hsl(var(--nc) / var(--tw-text-opacity)); 826 | border-radius: var(--rounded-box, 1rem); 827 | } 828 | 829 | .mockup-code pre[data-prefix]:before { 830 | content: attr(data-prefix); 831 | display: inline-block; 832 | text-align: right; 833 | width: 2rem; 834 | opacity: 0.5; 835 | } 836 | 837 | .stats { 838 | display: inline-grid; 839 | --tw-bg-opacity: 1; 840 | background-color: hsl(var(--b1) / var(--tw-bg-opacity)); 841 | --tw-text-opacity: 1; 842 | color: hsl(var(--bc) / var(--tw-text-opacity)); 843 | border-radius: var(--rounded-box, 1rem); 844 | } 845 | 846 | :where(.stats) { 847 | grid-auto-flow: column; 848 | overflow-x: auto; 849 | } 850 | 851 | .stat { 852 | display: inline-grid; 853 | width: 100%; 854 | grid-template-columns: repeat(1, 1fr); 855 | -moz-column-gap: 1rem; 856 | column-gap: 1rem; 857 | border-color: hsl(var(--bc) / var(--tw-border-opacity)); 858 | --tw-border-opacity: 0.1; 859 | padding-left: 1.5rem; 860 | padding-right: 1.5rem; 861 | padding-top: 1rem; 862 | padding-bottom: 1rem; 863 | } 864 | 865 | .stat-title { 866 | grid-column-start: 1; 867 | white-space: nowrap; 868 | color: hsl(var(--bc) / 0.6); 869 | } 870 | 871 | .stat-value { 872 | grid-column-start: 1; 873 | white-space: nowrap; 874 | font-size: 2.25rem; 875 | line-height: 2.5rem; 876 | font-weight: 800; 877 | } 878 | 879 | .stat-desc { 880 | grid-column-start: 1; 881 | white-space: nowrap; 882 | font-size: 0.75rem; 883 | line-height: 1rem; 884 | color: hsl(var(--bc) / 0.6); 885 | } 886 | 887 | @keyframes button-pop { 888 | 0% { 889 | transform: scale(var(--btn-focus-scale, 0.98)); 890 | } 891 | 892 | 40% { 893 | transform: scale(1.02); 894 | } 895 | 896 | 100% { 897 | transform: scale(1); 898 | } 899 | } 900 | 901 | .chat-bubble-primary { 902 | --tw-bg-opacity: 1; 903 | background-color: hsl(var(--p) / var(--tw-bg-opacity)); 904 | --tw-text-opacity: 1; 905 | color: hsl(var(--pc) / var(--tw-text-opacity)); 906 | } 907 | 908 | .chat-bubble-secondary { 909 | --tw-bg-opacity: 1; 910 | background-color: hsl(var(--s) / var(--tw-bg-opacity)); 911 | --tw-text-opacity: 1; 912 | color: hsl(var(--sc) / var(--tw-text-opacity)); 913 | } 914 | 915 | .chat-bubble-accent { 916 | --tw-bg-opacity: 1; 917 | background-color: hsl(var(--a) / var(--tw-bg-opacity)); 918 | --tw-text-opacity: 1; 919 | color: hsl(var(--ac) / var(--tw-text-opacity)); 920 | } 921 | 922 | .chat-bubble-info { 923 | --tw-bg-opacity: 1; 924 | background-color: hsl(var(--in) / var(--tw-bg-opacity)); 925 | --tw-text-opacity: 1; 926 | color: hsl(var(--inc) / var(--tw-text-opacity)); 927 | } 928 | 929 | .chat-bubble-success { 930 | --tw-bg-opacity: 1; 931 | background-color: hsl(var(--su) / var(--tw-bg-opacity)); 932 | --tw-text-opacity: 1; 933 | color: hsl(var(--suc) / var(--tw-text-opacity)); 934 | } 935 | 936 | .chat-bubble-warning { 937 | --tw-bg-opacity: 1; 938 | background-color: hsl(var(--wa) / var(--tw-bg-opacity)); 939 | --tw-text-opacity: 1; 940 | color: hsl(var(--wac) / var(--tw-text-opacity)); 941 | } 942 | 943 | .chat-bubble-error { 944 | --tw-bg-opacity: 1; 945 | background-color: hsl(var(--er) / var(--tw-bg-opacity)); 946 | --tw-text-opacity: 1; 947 | color: hsl(var(--erc) / var(--tw-text-opacity)); 948 | } 949 | 950 | @keyframes checkmark { 951 | 0% { 952 | background-position-y: 5px; 953 | } 954 | 955 | 50% { 956 | background-position-y: -2px; 957 | } 958 | 959 | 100% { 960 | background-position-y: 0; 961 | } 962 | } 963 | 964 | .link:focus { 965 | outline: 2px solid transparent; 966 | outline-offset: 2px; 967 | } 968 | 969 | .link:focus-visible { 970 | outline: 2px solid currentColor; 971 | outline-offset: 2px; 972 | } 973 | 974 | .mockup-code:before { 975 | content: ""; 976 | margin-bottom: 1rem; 977 | display: block; 978 | height: 0.75rem; 979 | width: 0.75rem; 980 | border-radius: 9999px; 981 | opacity: 0.3; 982 | box-shadow: 1.4em 0, 983 | 2.8em 0, 984 | 4.2em 0; 985 | } 986 | 987 | .mockup-code pre { 988 | padding-right: 1.25rem; 989 | } 990 | 991 | .mockup-code pre:before { 992 | content: ""; 993 | margin-right: 2ch; 994 | } 995 | 996 | @keyframes modal-pop { 997 | 0% { 998 | opacity: 0; 999 | } 1000 | } 1001 | 1002 | @keyframes progress-loading { 1003 | 50% { 1004 | background-position-x: -115%; 1005 | } 1006 | } 1007 | 1008 | @keyframes radiomark { 1009 | 0% { 1010 | box-shadow: 0 0 0 12px hsl(var(--b1)) inset, 1011 | 0 0 0 12px hsl(var(--b1)) inset; 1012 | } 1013 | 1014 | 50% { 1015 | box-shadow: 0 0 0 3px hsl(var(--b1)) inset, 1016 | 0 0 0 3px hsl(var(--b1)) inset; 1017 | } 1018 | 1019 | 100% { 1020 | box-shadow: 0 0 0 4px hsl(var(--b1)) inset, 1021 | 0 0 0 4px hsl(var(--b1)) inset; 1022 | } 1023 | } 1024 | 1025 | @keyframes rating-pop { 1026 | 0% { 1027 | transform: translateY(-0.125em); 1028 | } 1029 | 1030 | 40% { 1031 | transform: translateY(-0.125em); 1032 | } 1033 | 1034 | 100% { 1035 | transform: translateY(0); 1036 | } 1037 | } 1038 | 1039 | :where(.stats) > :not([hidden]) ~ :not([hidden]) { 1040 | --tw-divide-x-reverse: 0; 1041 | border-right-width: calc(1px * var(--tw-divide-x-reverse)); 1042 | border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse))); 1043 | --tw-divide-y-reverse: 0; 1044 | border-top-width: calc(0px * calc(1 - var(--tw-divide-y-reverse))); 1045 | border-bottom-width: calc(0px * var(--tw-divide-y-reverse)); 1046 | } 1047 | 1048 | @keyframes toast-pop { 1049 | 0% { 1050 | transform: scale(0.9); 1051 | opacity: 0; 1052 | } 1053 | 1054 | 100% { 1055 | transform: scale(1); 1056 | opacity: 1; 1057 | } 1058 | } 1059 | 1060 | .mx-auto { 1061 | margin-left: auto; 1062 | margin-right: auto; 1063 | } 1064 | 1065 | .my-5 { 1066 | margin-top: 1.25rem; 1067 | margin-bottom: 1.25rem; 1068 | } 1069 | 1070 | .w-\[50px\] { 1071 | width: 50px; 1072 | } 1073 | 1074 | .max-w-3xl { 1075 | max-width: 48rem; 1076 | } 1077 | 1078 | .p-2 { 1079 | padding: 0.5rem; 1080 | } 1081 | 1082 | .p-6 { 1083 | padding: 1.5rem; 1084 | } 1085 | 1086 | .text-center { 1087 | text-align: center; 1088 | } 1089 | 1090 | .text-2xl { 1091 | font-size: 1.5rem; 1092 | line-height: 2rem; 1093 | } 1094 | 1095 | .text-4xl { 1096 | font-size: 2.25rem; 1097 | line-height: 2.5rem; 1098 | } 1099 | 1100 | .text-success { 1101 | --tw-text-opacity: 1; 1102 | color: hsl(var(--su) / var(--tw-text-opacity)); 1103 | } 1104 | 1105 | .text-warning { 1106 | --tw-text-opacity: 1; 1107 | color: hsl(var(--wa) / var(--tw-text-opacity)); 1108 | } 1109 | 1110 | .shadow { 1111 | --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); 1112 | --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); 1113 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 1114 | } 1115 | 1116 | .\[margin\:auto\] { 1117 | margin: auto; 1118 | } 1119 | -------------------------------------------------------------------------------- /examples/leptos-demo/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: { 4 | files: ["*.html", "./src/**/*.rs"], 5 | }, 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [require("daisyui")], 10 | } 11 | -------------------------------------------------------------------------------- /examples/leptos-demo/tailwind.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowedLists": { 3 | "classes": ["text-warning", "text-success"] 4 | }, 5 | "theme":{ 6 | "extend":{} 7 | } 8 | } -------------------------------------------------------------------------------- /tailwind.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "corePlugins": {}, 3 | "allowedLists": { 4 | "classes": ["oyelowo", "oyedayo", "codebreather", "title-font"], 5 | "modifiers": [] 6 | }, 7 | "theme": { 8 | "screens": {}, 9 | "colors": {}, 10 | "spacing": {}, 11 | "backgroundPosition": {}, 12 | "backgroundSize": {}, 13 | "borderColor": { 14 | "mana": { 15 | "DEFAULT": "#cffafe", 16 | "53": "#cffafe", 17 | "200": "#a5f3fc", 18 | "300": "#67e8f9", 19 | "400": "#22d3ee", 20 | "500": "#06b6d4", 21 | "600": "#0891b2", 22 | "700": "#0e7490", 23 | "800": "#155e75", 24 | "900": "#164e63" 25 | } 26 | }, 27 | "borderWidth": {}, 28 | "borderRadius": {}, 29 | "width": {}, 30 | "height": {}, 31 | "minWidth": {}, 32 | "minHeight": {}, 33 | "maxWidth": {}, 34 | "maxHeight": {}, 35 | "padding": {}, 36 | "margin": {}, 37 | "negativeMargin": {}, 38 | "shadows": {}, 39 | "zIndex": {}, 40 | "opacity": {}, 41 | "fill": {}, 42 | "stroke": {}, 43 | "breakAfter": {}, 44 | "extend": { 45 | "screens": { 46 | "tablet": "640px" 47 | }, 48 | "aria": { 49 | "asc": "sort=\"ascending\"", 50 | "desc": "sort=\"descending\"" 51 | }, 52 | "data": { 53 | "checked": "ui~=\"checked\"" 54 | }, 55 | "supports": { 56 | "grid": "display: grid" 57 | }, 58 | "colors": { 59 | "transparent": "transparent", 60 | "current": "currentColor", 61 | "white": "#ffffff", 62 | "purple": "#3f3cbb", 63 | "midnight": "#121063", 64 | "metal": "#565584", 65 | "taxvhiti": "#3ab7bf", 66 | "silver": "#ecebff", 67 | "bubble-gum": "#ff77e9", 68 | "bermuda": "#78dcca", 69 | "tahiti": { 70 | "DEFAULT": "#cffafe", 71 | "100": "#cffafe", 72 | "200": "#a5f3fc", 73 | "300": "#67e8f9", 74 | "400": "#22d3ee", 75 | "500": "#06b6d4", 76 | "600": "#0891b2", 77 | "700": "#0e7490", 78 | "800": "#155e75", 79 | "900": "#164e63" 80 | } 81 | }, 82 | "spacing": {}, 83 | "backgroundPosition": {}, 84 | "backgroundSize": {}, 85 | "borderColor": {}, 86 | "borderWidth": {}, 87 | "borderRadius": {}, 88 | "width": {}, 89 | "height": {}, 90 | "minWidth": {}, 91 | "minHeight": {}, 92 | "maxWidth": {}, 93 | "maxHeight": {}, 94 | "padding": { 95 | "DEFAULT": "1rem", 96 | "sm": "1rem", 97 | "md": "1rem", 98 | "lg": "1rem", 99 | "xl": "1rem", 100 | "2xl": "1rem" 101 | }, 102 | "margin": {}, 103 | "scrollMargin": { 104 | "DEFAULT": "0px", 105 | "sm": "0px", 106 | "md": "0px", 107 | "lg": "0px", 108 | "xl": "0px", 109 | "2xl": "0px" 110 | }, 111 | "negativeMargin": {}, 112 | "shadows": {}, 113 | "zIndex": {}, 114 | "opacity": {}, 115 | "fill": {}, 116 | "stroke": {}, 117 | "breakAfter": {} 118 | } 119 | }, 120 | "variants": {}, 121 | "plugins": {} 122 | } 123 | -------------------------------------------------------------------------------- /tailwind.config.old.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme": { 3 | "screens": {}, 4 | "colors": { 5 | "transparent": "transparent", 6 | "current": "currentColor", 7 | "white": "#ffffff", 8 | "purple": "#3f3cbb", 9 | "midnight": "#121063", 10 | "metal": "#565584", 11 | "taxvhiti": "#3ab7bf", 12 | "silver": "#ecebff", 13 | "bubble-gum": "#ff77e9", 14 | "bermuda": "#78dcca", 15 | "tahiti": { 16 | "DEFAULT": "#cffafe", 17 | "100": "#cffafe", 18 | "200": "#a5f3fc", 19 | "300": "#67e8f9", 20 | "400": "#22d3ee", 21 | "500": "#06b6d4", 22 | "600": "#0891b2", 23 | "700": "#0e7490", 24 | "800": "#155e75", 25 | "900": "#164e63" 26 | } 27 | }, 28 | "spacing": {}, 29 | "backgroundPosition": {}, 30 | "backgroundSize": {}, 31 | "borderColors": {}, 32 | "borderWidths": {}, 33 | "borderRadius": {}, 34 | "width": {}, 35 | "height": {}, 36 | "minWidth": {}, 37 | "minHeight": {}, 38 | "maxWidth": {}, 39 | "maxHeight": {}, 40 | "padding": {}, 41 | "margin": {}, 42 | "negativeMargin": {}, 43 | "shadows": {}, 44 | "zIndex": {}, 45 | "opacity": {}, 46 | "fill": {}, 47 | "stroke": {}, 48 | "breakAfter": {}, 49 | "extend": { 50 | "screens": {}, 51 | "colors": {}, 52 | "spacing": {}, 53 | "backgroundPosition": {}, 54 | "backgroundSize": {}, 55 | "borderColors": {}, 56 | "borderWidths": {}, 57 | "borderRadius": {}, 58 | "width": {}, 59 | "height": {}, 60 | "minWidth": {}, 61 | "minHeight": {}, 62 | "maxWidth": {}, 63 | "maxHeight": {}, 64 | "padding": {}, 65 | "margin": {}, 66 | "negativeMargin": {}, 67 | "shadows": {}, 68 | "zIndex": {}, 69 | "opacity": {}, 70 | "fill": {}, 71 | "stroke": {}, 72 | "breakAfter": {} 73 | } 74 | }, 75 | "variants": {}, 76 | "plugins": {} 77 | } 78 | -------------------------------------------------------------------------------- /tailwind/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tailwind" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors.workspace = true 6 | description.workspace = true 7 | documentation.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | twust = { workspace = true, features = ["daisyui"] } 13 | serde = "1.0.188" 14 | serde_json = "1.0.105" 15 | 16 | 17 | [features] 18 | daisyui = [] 19 | -------------------------------------------------------------------------------- /tailwind/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Oyelowo Oyedayo 3 | * Email: oyelowo.oss@gmail.com 4 | * Copyright (c) 2023 Oyelowo Oyedayo 5 | * Licensed under the MIT license 6 | */ 7 | use twust::{tw, tw1, tws, tws1}; 8 | 9 | /// Invalid character in class name. 10 | /// 11 | /// ```compile_fail 12 | /// use twust::tw; 13 | /// tw!("bg-taxvhiti$"); 14 | /// ``` 15 | fn _invalid_character_in_class_name() {} 16 | 17 | /// Unsupported unit. 18 | /// 19 | /// ```compile_fail 20 | /// use twust::tw; 21 | /// tw!("px-[45xyz]"); 22 | /// ``` 23 | fn _unsupported_unit() {} 24 | 25 | /// Mixing classes without spaces. 26 | /// 27 | /// ```compile_fail 28 | /// use twust::tw; 29 | /// tw!("bg-taxvhiti.bg-tahiti-500"); 30 | /// ``` 31 | fn _mixing_classes_without_spaces() {} 32 | 33 | /// Unsupported combination of classes. 34 | /// 35 | /// ```compile_fail 36 | /// use twust::tw; 37 | /// tw!("bg-taxvhiti hover"); 38 | /// ``` 39 | fn _unsupported_combination_of_classes() {} 40 | 41 | /// Missing `-` in between hover and class name. 42 | /// 43 | /// ```compile_fail 44 | /// use twust::tw; 45 | /// tw!("hover[bg-taxvhiti]"); 46 | /// ``` 47 | fn _missing_dash_in_class_name() {} 48 | 49 | /// Missing `[]` for arbitrary values. 50 | /// 51 | /// ```compile_fail 52 | /// use twust::tw; 53 | /// tw!("px-45pc"); 54 | /// ``` 55 | fn _missing_brackets_for_arbitrary_values() {} 56 | 57 | /// Invalid usage of negative sign. 58 | /// 59 | /// ```compile_fail 60 | /// use twust::tw; 61 | /// tw!("-[&_p]mt-4"); 62 | /// ``` 63 | fn _invalid_usage_of_negative_sign() {} 64 | 65 | /// Improper use of brackets. No units present for length dimension. 66 | /// 67 | /// ```compile_fail 68 | /// use twust::tw; 69 | /// tw!("px-[45"); 70 | /// ``` 71 | fn _improper_use_of_brackets() {} 72 | 73 | /// Unsupported media query. 74 | /// 75 | /// ```compile_fail 76 | /// use twust::tw; 77 | /// tw!("![feature(slice_as_chunks)]"); 78 | /// ``` 79 | fn _unsupported_media_query() {} 80 | 81 | /// Missing unit after arbitrary value. 82 | /// 83 | /// ```compile_fail 84 | /// use twust::tw; 85 | /// tw!("px-45]"); 86 | /// ``` 87 | fn _malformed_arbitrary_value() {} 88 | 89 | /// Invalid group usage. 90 | /// 91 | /// ```compile_fail 92 | /// use twust::tw; 93 | /// tw!("group-unknownmodifier:underline"); 94 | /// ``` 95 | fn _invalid_group_usage() {} 96 | 97 | /// Wont compile overriden border color. 98 | /// ```compile_fail 99 | /// use twust::tw; 100 | /// tw!("border-red-500"); 101 | /// ``` 102 | fn _wont_compile_overriden_border_color() {} 103 | 104 | /// Invalid group usage with no slash. 105 | /// ```compile_fail 106 | /// use twust::tw; 107 | /// tw!("groupedit block invisible md:hover:bg-slate-200 md:group-hover/item:visible"); 108 | /// ``` 109 | fn _invalid_group_usage_2() {} 110 | 111 | /// Invalid group usage 3 with invalid class 112 | /// ```compile_fail 113 | /// use twust::tw; 114 | /// tw!("md:hover:bg-slate-200 md:group-hover/item:isible"); 115 | /// ``` 116 | fn _invalid_group_usage_3() {} 117 | 118 | // invalid tw1 119 | 120 | /// tw1! macro does not support multiple classes 121 | /// ```compile_fail 122 | /// use twust::tw1; 123 | /// let _classnames = tw1!["btn group-data-[selected=Right]:w-[30px]"]; 124 | /// let _classnames = tw1!("btn group-data-[selected=Right]:w-[30px]"); 125 | /// let _classnames = tw1!("btn", "group-data-[selected=Right]:w-[30px]"); 126 | /// ``` 127 | fn _invalid_tw1_multiple_classes() {} 128 | 129 | /// tws1! macro does not support multiple classes 130 | /// ```compile_fail 131 | /// use twust::tws1; 132 | /// let _classnames = tws1!["btn group-data-[selected=Right]:w-[30px]"]; 133 | /// let _classnames = tws1!["btn bg-tahiti"]; 134 | /// ``` 135 | fn _invalid_tws1_multiple_classes() {} 136 | 137 | fn _happy_paths() { 138 | fn main() { 139 | // Returns an array of strings 140 | let _classnames = tws!["btn", "group-data-[selected=Right]:w-[30px]", "bg-sky-700"]; 141 | let _classnames = tws!["btn group-data-[selected=Right]:w-[30px]"]; 142 | // let _classnames = tws1!["btn group-data-[selected=Right]:w-[30px]"]; 143 | let _classnames = tws1!["btn", "group-data-[selected=Right]:w-[30px]"]; 144 | let _classnames = tws1!["btn", "bg-tahiti"]; 145 | 146 | // Takes a single class and returns a single string 147 | let _classnames = tw1!("btn"); 148 | let _classnames = tw1!["btn"]; 149 | let _classnames = tw1!["bg-tahiti"]; 150 | let _classnames = tw1!("group-data-[selected=Right]:w-[30px]"); 151 | 152 | // Returns a single string 153 | let _classnames = tw!["btn bg-tahiti"]; 154 | let _classnames = tw!["btn", "group-data-[selected=Right]:w-[30px]", "bg-sky-700"]; 155 | let _classnames = 156 | tw!("block text-sm transition group-data-[selected=Right]:w-[30px] bg-tahiti"); 157 | let _classnames = tw!("group-data-[selected=Right]:w-[30px] bg-tahiti btn"); 158 | let _classnames = tw!("group-data-[selected=Right]:w-[30px] bg-tahiti badge-secondary"); 159 | let _classnames = tw!("group group-data-[selected=Right]:w-[30px] bg-tahiti"); 160 | let _classnames = tw!("group-aria-[main-page=false]/main:hidden"); 161 | let _classnames = tw!("group-data-[main-page=false]/main:hidden"); 162 | let _classnames = tw!("*:overflow-scroll"); 163 | 164 | let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-red-50"); 165 | let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-purple bg-red-50 bg-tahiti-800 border-s-tahiti-800"); 166 | let _classnames = tw!("md:text-red-50 text-slate-50 text-purple text-tahiti-500"); 167 | let _classnames = tw!("py-sm md:py-md tablet:py-sm lg:py-lg xl:py-xl"); 168 | let _classnames = tw!("group"); 169 | let _classnames = tw!("text-sm font-medium text-slate-300 group-hover:text-white"); 170 | let _classnames = tw!("text-sm font-medium text-slate-500 group-hover:text-slate-300"); 171 | let _classnames = tw!("hover:-translate-y-0.5 transition motion-reduce:hover:translate-y-0 motion-reduce:transition-none"); 172 | let _classnames = tw!("motion-safe:hover:-translate-x-0.5 motion-safe:transition"); 173 | 174 | // You can even conditionally statically support plugins such as daisyui 175 | // This lets you use daisyui classes in your tailwind macro 176 | let _classnames = tw!("btn"); 177 | let _classnames = tw!("btn collapse-arrow badge-secondary checkbox-warning"); 178 | 179 | let _classnames = 180 | tw!("group/edit block invisible md:hover:bg-slate-200 group-hover/item:visible"); 181 | let _classnames = tw!("group-[:nth-of-type(3)_&]:block group-hover/edit:text-gray-700 group-[:nth-of-type(3)_&]:block"); 182 | 183 | let _classnames = tw!("tracking-widest text-xs title-font font-medium text-gray-400 mb-1"); 184 | 185 | let _classnames = 186 | tw!("bg-gray-600 aria-checked:bg-sky-700 aria-asc:bg-midnight data-checked:underline"); 187 | let _classnames = tw!("scroll-m-14 flex supports-grid:grid supports-[display:grid]:grid"); 188 | let _classnames = tw!("scroll-m-sm group-aria-[sort=ascending]:rotate-0"); 189 | let _classnames = tw!("scroll-mx-sm"); 190 | let _classnames = tw!("scroll-mx-md"); 191 | let _classnames = tw!("scroll-my-md"); 192 | let _classnames = tw!("px-sm pt-sm pb-sm pr-sm pl-sm"); 193 | let _classnames = tw!("px-md pt-md pb-md pr-md pl-md"); 194 | let _classnames = tw!("scroll-m-14 scroll-mx-14"); 195 | let _classnames = tw!("m-4 p-4 p-4"); 196 | let _classnames = tw!("-m-[4px] p-4 p-4"); 197 | let _classnames = tw!("-m-4 p-4 p-4"); 198 | let _classnames = tw!("lg:[&:nth-child(3)]:hover:underline"); 199 | let _classnames = // tw!("[0]"); 200 | // tw!("[color:red]/dark"); 201 | // tw!("![feature(slice_as_chunks)]"); 202 | // tw!("!-[feature(slice_as_chunks)]"); 203 | tw!("[@supports(display:grid)]:grid"); 204 | let _classnames = tw!("[@media(any-hover:hover){&:hover}]:opacity-100"); 205 | let _classnames = tw!("underline-offset-[3px]"); 206 | 207 | let _classnames = tw!("[&_p]:mt-4"); 208 | // tw!("-[&_p]:mt-4"); 209 | let _classnames = tw!("lg:[&:nth-child(3)]:hover:underline"); 210 | let _classnames = tw!("outline-blue-500/50"); 211 | let _classnames = tw!("text-blue-600/[.07]"); 212 | 213 | // tw!("[something]"); 214 | let _classnames = tw!("px-[45.43px]"); 215 | let _classnames = tw!("px-[-45cm]"); 216 | let _classnames = tw!("px-[-45rem]"); 217 | let _classnames = tw!("px-[-45em]"); 218 | let _classnames = tw!("px-[45em]"); 219 | let _classnames = tw!("px-[-45%]"); 220 | let _classnames = tw!("px-[-45in]"); 221 | let _classnames = tw!("px-[-45vh]"); 222 | let _classnames = tw!("px-[-45vw]"); 223 | let _classnames = tw!("px-[-45vmin]"); 224 | let _classnames = tw!("px-[-45vmax]"); 225 | let _classnames = tw!("px-[-45mm]"); 226 | let _classnames = tw!("px-[-45pc]"); 227 | let _classnames = tw!("px-[0px]"); 228 | let _classnames = tw!("px-[0]"); 229 | let _classnames = tw!("px-[45px]"); 230 | let _classnames = tw!("px-[45cm]"); 231 | let _classnames = tw!("px-[45rem]"); 232 | 233 | let _classnames = tw!("px-[45em]"); 234 | let _classnames = tw!("px-[45%]"); 235 | let _classnames = tw!("px-[45in]"); 236 | let _classnames = tw!("px-[45vh]"); 237 | let _classnames = tw!("px-[45vw]"); 238 | let _classnames = tw!("px-[45vmin]"); 239 | let _classnames = tw!("px-[45vmax]"); 240 | let _classnames = tw!("px-[45.5mm]"); 241 | let _classnames = tw!("px-[45pc]"); 242 | let _classnames = tw!("py-[0]"); 243 | let _classnames = tw!("px-[45pc]"); 244 | let _classnames = tw!("-px-[45pc]"); 245 | let _classnames = tw!("hover:[mask-type:alpha]"); 246 | let _classnames = tw!( 247 | "m-4 last:first:invalid:last:first:p-4 last:m-4 pb-[calc(100%-34px)] pb-[23px] [mask-type:luminance] 248 | [mask-type:luminance] hover:[mask-type:alpha] lg:[--scroll-offset:44px] oyelowo oyedayo break-after-avoid" 249 | ); 250 | let _classnames = tw!("p-4 md:w-1/3"); 251 | 252 | let _classnames = tw!("opacity-50 md:opacity-100 hover:opacity-100"); 253 | let _classnames = tw!("tracking-widest text-xs font-medium text-gray-400 mb-1"); 254 | // border color is overriden here in tailwind.config.json 255 | let _classnames = 256 | tw!("h-full border-2 border-mana-53 border-opacity-60 rounded-lg overflow-hidden"); 257 | } 258 | } 259 | 260 | fn _happy_paths_list() { 261 | fn main() { 262 | let _classnames = tw![ 263 | "group-data-[selected=Right]:w-[30px]", 264 | "group-aria-[main-page=false]/main:hidden", 265 | "group-data-[main-page=false]/main:hidden", 266 | "*:overflow-scroll", 267 | "bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-red-50", 268 | "bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-purple bg-red-50 bg-tahiti-800 border-s-tahiti-800", 269 | "md:text-red-50 text-slate-50 text-purple text-tahiti-500", 270 | "py-sm md:py-md tablet:py-sm lg:py-lg xl:py-xl", 271 | "group", 272 | "text-sm font-medium text-slate-300 group-hover:text-white", 273 | "text-sm font-medium text-slate-500 group-hover:text-slate-300", 274 | "hover:-translate-y-0.5 transition motion-reduce:hover:translate-y-0 motion-reduce:transition-none", 275 | "motion-safe:hover:-translate-x-0.5 motion-safe:transition" 276 | ]; 277 | } 278 | } 279 | 280 | #[test] 281 | fn test() { 282 | let classes = tw!("group-data-[selected=Right]:w-[30px]"); 283 | assert_eq!(classes, "group-data-[selected=Right]:w-[30px]"); 284 | 285 | let classes = tw!("group-aria-[main-page=false]/main:hidden"); 286 | assert_eq!(classes, "group-aria-[main-page=false]/main:hidden"); 287 | 288 | let classes = tw!("group-data-[main-page=false]/main:hidden"); 289 | assert_eq!(classes, "group-data-[main-page=false]/main:hidden"); 290 | 291 | let _classnames = "group-data-[selected=Right]:w-[30px] bg-sky-700"; 292 | assert_eq!( 293 | _classnames, 294 | "group-data-[selected=Right]:w-[30px] bg-sky-700" 295 | ); 296 | 297 | let _classnames = "btn block group-data-[selected=Right]:w-[30px] bg-sky-700"; 298 | assert_eq!( 299 | _classnames, 300 | "btn block group-data-[selected=Right]:w-[30px] bg-sky-700" 301 | ); 302 | 303 | let classlist = tw!["group-data-[selected=Right]:w-[30px]", "group-aria-[main-page=false]/main:hidden", "group-data-[main-page=false]/main:hidden", "*:overflow-scroll", "bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-red-50", "bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-purple bg-red-50 bg-tahiti-800 border-s-tahiti-800", "md:text-red-50 text-slate-50 text-purple text-tahiti-500", "py-sm md:py-md tablet:py-sm lg:py-lg xl:py-xl", "group", "text-sm font-medium text-slate-300 group-hover:text-white", "text-sm font-medium text-slate-500 group-hover:text-slate-300", "hover:-translate-y-0.5 transition motion-reduce:hover:translate-y-0 motion-reduce:transition-none", "motion-safe:hover:-translate-x-0.5 motion-safe:transition"]; 304 | assert_eq!(classlist, "group-data-[selected=Right]:w-[30px] group-aria-[main-page=false]/main:hidden group-data-[main-page=false]/main:hidden *:overflow-scroll bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-red-50 bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-purple bg-red-50 bg-tahiti-800 border-s-tahiti-800 md:text-red-50 text-slate-50 text-purple text-tahiti-500 py-sm md:py-md tablet:py-sm lg:py-lg xl:py-xl group text-sm font-medium text-slate-300 group-hover:text-white text-sm font-medium text-slate-500 group-hover:text-slate-300 hover:-translate-y-0.5 transition motion-reduce:hover:translate-y-0 motion-reduce:transition-none motion-safe:hover:-translate-x-0.5 motion-safe:transition"); 305 | 306 | let _classnames = tw1!["group-data-[selected=Right]:w-[30px]"]; 307 | assert_eq!(_classnames, "group-data-[selected=Right]:w-[30px]"); 308 | 309 | let _classnames = tw1!("group-data-[selected=Right]:w-[30px]"); 310 | assert_eq!(_classnames, "group-data-[selected=Right]:w-[30px]"); 311 | 312 | let classlist = tws1![ 313 | "group-data-[selected=Right]:w-[30px]", 314 | "group-aria-[main-page=false]/main:hidden", 315 | "group-data-[main-page=false]/main:hidden", 316 | "*:overflow-scroll", 317 | "bg-taxvhiti", 318 | "bg-tahiti-500", 319 | "bg-tahiti", 320 | "bg-midnight", 321 | "bg-red-50", 322 | "bg-taxvhiti", 323 | "bg-tahiti-500", 324 | "bg-tahiti", 325 | "bg-midnight", 326 | "bg-purple", 327 | "bg-red-50", 328 | "bg-tahiti-800", 329 | "border-s-tahiti-800", 330 | "md:text-red-50", 331 | "text-slate-50", 332 | "text-purple", 333 | "text-tahiti-500", 334 | "py-sm", 335 | "md:py-md", 336 | "tablet:py-sm", 337 | "lg:py-lg", 338 | "xl:py-xl", 339 | "group", 340 | "text-sm", 341 | "font-medium", 342 | "text-slate-300", 343 | "group-hover:text-white", 344 | "text-sm", 345 | "font-medium", 346 | "text-slate-500", 347 | "group-hover:text-slate-300", 348 | "hover:-translate-y-0.5", 349 | "transition", 350 | "motion-reduce:hover:translate-y-0", 351 | "motion-reduce:transition-none", 352 | "motion-safe:hover:-translate-x-0.5", 353 | "motion-safe:transition" 354 | ]; 355 | 356 | assert_eq!( 357 | classlist, 358 | [ 359 | "group-data-[selected=Right]:w-[30px]", 360 | "group-aria-[main-page=false]/main:hidden", 361 | "group-data-[main-page=false]/main:hidden", 362 | "*:overflow-scroll", 363 | "bg-taxvhiti", 364 | "bg-tahiti-500", 365 | "bg-tahiti", 366 | "bg-midnight", 367 | "bg-red-50", 368 | "bg-taxvhiti", 369 | "bg-tahiti-500", 370 | "bg-tahiti", 371 | "bg-midnight", 372 | "bg-purple", 373 | "bg-red-50", 374 | "bg-tahiti-800", 375 | "border-s-tahiti-800", 376 | "md:text-red-50", 377 | "text-slate-50", 378 | "text-purple", 379 | "text-tahiti-500", 380 | "py-sm", 381 | "md:py-md", 382 | "tablet:py-sm", 383 | "lg:py-lg", 384 | "xl:py-xl", 385 | "group", 386 | "text-sm", 387 | "font-medium", 388 | "text-slate-300", 389 | "group-hover:text-white", 390 | "text-sm", 391 | "font-medium", 392 | "text-slate-500", 393 | "group-hover:text-slate-300", 394 | "hover:-translate-y-0.5", 395 | "transition", 396 | "motion-reduce:hover:translate-y-0", 397 | "motion-reduce:transition-none", 398 | "motion-safe:hover:-translate-x-0.5", 399 | "motion-safe:transition" 400 | ] 401 | ); 402 | 403 | let _classnames = tw!["group-data-[selected=Right]:w-[30px]", "bg-sky-700"]; 404 | assert_eq!( 405 | _classnames, 406 | "group-data-[selected=Right]:w-[30px] bg-sky-700" 407 | ); 408 | 409 | let _classnames = tws!["group-data-[selected=Right]:w-[30px]", "bg-sky-700"]; 410 | assert_eq!( 411 | _classnames, 412 | ["group-data-[selected=Right]:w-[30px]", "bg-sky-700"] 413 | ); 414 | 415 | let _classnames = tws!("group-data-[selected=Right]:w-[30px]", "bg-sky-700"); 416 | assert_eq!( 417 | _classnames, 418 | ["group-data-[selected=Right]:w-[30px]", "bg-sky-700"] 419 | ); 420 | 421 | let _classnames = tws!("group-data-[selected=Right]:w-[30px]", "bg-sky-700"); 422 | assert_eq!( 423 | _classnames, 424 | ["group-data-[selected=Right]:w-[30px]", "bg-sky-700"] 425 | ); 426 | 427 | let _classnames = tws! {"group-data-[selected=Right]:w-[30px]", "bg-sky-700" }; 428 | assert_eq!( 429 | _classnames, 430 | ["group-data-[selected=Right]:w-[30px]", "bg-sky-700"] 431 | ); 432 | 433 | let _classnames = tws1! {"group-data-[selected=Right]:w-[30px]", "bg-sky-700" }; 434 | assert_eq!( 435 | _classnames, 436 | ["group-data-[selected=Right]:w-[30px]", "bg-sky-700"] 437 | ); 438 | } 439 | -------------------------------------------------------------------------------- /tailwind/src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::no_effect)] 2 | /* 3 | * Author: Oyelowo Oyedayo 4 | * Email: oyelowo.oss@gmail.com 5 | * Copyright (c) 2023 Oyelowo Oyedayo 6 | * Licensed under the MIT license 7 | */ 8 | use twust::tw; 9 | 10 | fn main() { 11 | let _ = tw!("btn btn"); 12 | 13 | let test = tw!( 14 | r#"[mask-type:alpha] [mask-type:alpha] before:content-['rerer erer re rr r \re reFestivus'] 15 | after:content-['I am a content'] after:content-['I am a content'] after:content-['I am a content'] 16 | active:hover:text-[#bada55] active:hover:text-[#fa5] text-[#bada55] hover:aria-checked:text-[22px] 17 | text-[22.34e434cm] 18 | before:content-['hello\_world'] 19 | grid grid-cols-[fit-content(theme(spacing.32))] 20 | bg-[--my-color] 21 | text-[var(--my-var)] 22 | text-[length:var(--my-var)] 23 | text-[color:var(--my-var)] 24 | [--scroll-offset:56px] lg:[--scroll-offset:44px] 25 | btn bg-[url('/img/down-arrow.svg')] ring-white/10 bg-black/25 bg-black/[80%] bg-black/[100] bg-black/[0.75] active:hover:collapse-arrow 26 | 27 | [mask-image:linear-gradient(180deg,white,rgba(255,255,255,0))] 28 | 29 | pt-8 text-base font-semibold leading-7 30 | 31 | bg-[rgb(0,0,3)] absolute inset-0 bg-center 32 | 33 | -mt-4 34 | 35 | lg:[&:nth-child(3)]:hover:underline 36 | group-[:nth-of-type(3)_&]:block 37 | [&_p]:mt-4 38 | 39 | flex [@supports(display:grid)]:grid 40 | flex active:hover:[@supports(display:grid)]:grid 41 | 42 | [@media(any-hover:hover){&:hover}]:opacity-100 43 | 44 | hidden group-[.is-published]:block 45 | group-[:nth-of-type(3)_&]:block 46 | peer-[.is-dirty]:peer-required:block hidden 47 | hidden peer-[:nth-of-type(3)_&]:block 48 | 49 | group/edit invisible hover:bg-slate-200 group-hover/item:visible 50 | 51 | peer-checked/published:text-sky-500 52 | 53 | after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 54 | 55 | before:content-[''] before:block 56 | content-[>] 57 | content-[<] 58 | 59 | bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur 60 | 61 | aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] 62 | 63 | 64 | group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 65 | 66 | data-[size=large]:p-8 67 | 68 | open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg 69 | 70 | lg:[&:nth-child(3)]:hover:underline 71 | 72 | min-[320rem]:text-center max-[600px]:bg-sky-300 73 | 74 | top-[117px] lg:top-[344px] 75 | 76 | bg-[#bada55] text-[22px] before:content-['Festivus'] 77 | 78 | 79 | grid grid-cols-[fit-content(theme(spacing.32))] 80 | 81 | bg-[--my-color] 82 | 83 | [mask-type:luminance] hover:[mask-type:alpha] 84 | 85 | [--scroll-offset:56px] lg:[--scroll-offset:44px] 86 | 87 | lg:[&:nth-child(3)]:hover:underline 88 | bg-[url('/what_a_rush.png')] 89 | before:content-['hello\_world'] 90 | text-[22px] 91 | text-[#bada55] 92 | text-[var(--my-var)] 93 | text-[length:var(--my-var)] 94 | text-[color:var(--my-var)] 95 | 96 | 97 | p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4 98 | 99 | 100 | w-[calc(100%_-_theme("spacing[1.5]))"] 101 | shadow-[inset_0_-3em_3em_rgba(0,_0,_0,_0.1),_0_0_0_2px_rgb(255,_255,_255),_0.3em_0.3em_1em_rgba(0,_0,_0,_0.3)] 102 | 103 | 104 | "# 105 | ); 106 | // 'content-[>]', 107 | // // ^ 108 | // 'content-[<]', 109 | // // ^ 110 | // 111 | // // With functions and math expressions 112 | // 'px-[calc(100%-1rem)]', 113 | // 'px-[theme(spacing.1)]', 114 | // 'px-[theme(spacing[1.5])]', 115 | // 116 | // // With spaces (replaced by `_`) 117 | // 'bg-[rgb(255_0_0)]', 118 | // 119 | // // Examples with combinations 120 | // 121 | 122 | // let test = 123 | // tw!("peer[.is-dirty]:peer-required:block hidden hidden peer-[:nth-of-type(3)_&]:block"); 124 | println!("TEXT - {}", test); 125 | let _ = tw!("btn collapse-arrow"); 126 | let _ = tw!("bg-gray-600 bg-sky-700 bg-midnight underline"); 127 | let _ = tw!("bg-gray-600 aria-checked:bg-sky-700 aria-asc:bg-midnight data-checked:underline"); 128 | let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-red-50"); 129 | let _classnames = tw!("bg-taxvhiti bg-tahiti-500 bg-tahiti bg-midnight bg-purple bg-red-50 bg-tahiti-800 border-s-tahiti-800"); 130 | let _classnames = tw!("md:text-red-50 text-slate-50 text-purple text-tahiti-500"); 131 | let _classnames = tw!("py-sm md:py-md tablet:py-sm lg:py-lg xl:py-xl"); 132 | let _classnames = tw!("group"); 133 | let _classnames = tw!("text-sm font-medium text-slate-300 group-hover:text-white"); 134 | let _classnames = tw!("text-sm font-medium text-slate-500 group-hover:text-slate-300"); 135 | let _classnames = tw!("hover:-translate-y-0.5 transition motion-reduce:hover:translate-y-0 motion-reduce:transition-none"); 136 | let _classnames = tw!("motion-safe:hover:-translate-x-0.5 motion-safe:transition"); 137 | 138 | let _classnames = 139 | tw!("group/edit block invisible md:hover:bg-slate-200 group-hover/item:visible"); 140 | let _classnames = tw!("group-[:nth-of-type(3)_&]:block group-hover/edit:text-gray-700 group-[:nth-of-type(3)_&]:block"); 141 | 142 | let _classnames = tw!("tracking-widest text-xs title-font font-medium text-gray-400 mb-1"); 143 | 144 | let _classnames = 145 | tw!("bg-gray-600 aria-checked:bg-sky-700 aria-asc:bg-midnight data-checked:underline"); 146 | let _classnames = tw!("scroll-m-14 flex supports-grid:grid supports-[display:grid]:grid"); 147 | let _classnames = tw!("scroll-m-sm group-aria-[sort=ascending]:rotate-0"); 148 | let _classnames = tw!("scroll-mx-sm"); 149 | let _classnames = tw!("scroll-mx-md"); 150 | let _classnames = tw!("scroll-my-md"); 151 | let _classnames = tw!("px-sm pt-sm pb-sm pr-sm pl-sm"); 152 | let _classnames = tw!("px-md pt-md pb-md pr-md pl-md"); 153 | let _classnames = tw!("scroll-m-14 scroll-mx-14"); 154 | let _classnames = tw!("m-4 p-4 p-4"); 155 | let _classnames = tw!("-m-[4px] p-4 p-4"); 156 | let _classnames = tw!("-m-4 p-4 p-4"); 157 | let _classnames = tw!("lg:[&:nth-child(3)]:hover:underline"); 158 | let _classnames = // tw!("[0]"); 159 | // tw!("[color:red]/dark"); 160 | // tw!("![feature(slice_as_chunks)]"); 161 | // tw!("!-[feature(slice_as_chunks)]"); 162 | tw!("[@supports(display:grid)]:grid"); 163 | let _classnames = tw!("[@media(any-hover:hover){&:hover}]:opacity-100"); 164 | let _classnames = tw!("underline-offset-[3px]"); 165 | 166 | let _classnames = tw!("[&_p]:mt-4"); 167 | // tw!("-[&_p]:mt-4"); 168 | let _classnames = tw!("lg:[&:nth-child(3)]:hover:underline"); 169 | let _classnames = tw!("outline-blue-500/50"); 170 | let _classnames = tw!("text-blue-600/[.07]"); 171 | 172 | // tw!("[something]"); 173 | let _classnames = tw!("px-[-45px]"); 174 | let _classnames = tw!("px-[-45cm]"); 175 | let _classnames = tw!("px-[-45rem]"); 176 | let _classnames = tw!("px-[-45em]"); 177 | let _classnames = tw!("px-[-45%]"); 178 | let _classnames = tw!("px-[-45in]"); 179 | let _classnames = tw!("px-[-45vh]"); 180 | let _classnames = tw!("px-[-45vw]"); 181 | let _classnames = tw!("px-[-45vmin]"); 182 | let _classnames = tw!("px-[-45vmax]"); 183 | let _classnames = tw!("px-[-45mm]"); 184 | let _classnames = tw!("px-[-45pc]"); 185 | let _classnames = tw!("px-[0]"); 186 | let _classnames = tw!("px-[45px]"); 187 | let _classnames = tw!("px-[45cm]"); 188 | let _classnames = tw!("px-[45rem]"); 189 | let _classnames = tw!("px-[45em]"); 190 | let _classnames = tw!("px-[45%]"); 191 | let _classnames = tw!("px-[45in]"); 192 | let _classnames = tw!("px-[45vh]"); 193 | let _classnames = tw!("px-[45vw]"); 194 | let _classnames = tw!("px-[45vmin]"); 195 | let _classnames = tw!("px-[45vmax]"); 196 | let _classnames = tw!("px-[45mm]"); 197 | let _classnames = tw!("px-[45pc]"); 198 | let _classnames = tw!("py-[0]"); 199 | let _classnames = tw!("-px-[45pc]"); 200 | let _classnames = tw!("hover:[mask-type:alpha]"); 201 | let _classnames = tw!( 202 | "m-4 last:first:invalid:last:first:p-4 last:m-4 pb-[calc(100%-34px)] pb-[23px] [mask-type:luminance] 203 | [mask-type:luminance] hover:[mask-type:alpha] lg:[--scroll-offset:44px] oyelowo oyedayo break-after-avoid" 204 | ); 205 | let _classnames = tw!("p-4 md:w-1/3"); 206 | 207 | let _classnames = tw!("opacity-50 md:opacity-100 hover:opacity-100"); 208 | let _classnames = tw!("tracking-widest text-xs font-medium text-gray-400 mb-1"); 209 | // border color is overriden here in tailwind.config.json 210 | let _classnames = 211 | tw!("h-full border-2 border-mana-53 border-opacity-60 rounded-lg overflow-hidden"); 212 | } 213 | -------------------------------------------------------------------------------- /tailwind/tailwind.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "corePlugins": {}, 3 | "allowedLists": { 4 | "classes": ["oyelowo", "oyedayo", "codebreather", "title-font"], 5 | "modifiers": [] 6 | }, 7 | "theme": { 8 | "screens": {}, 9 | "colors": {}, 10 | "spacing": {}, 11 | "backgroundPosition": {}, 12 | "backgroundSize": {}, 13 | "borderColor": { 14 | "mana": { 15 | "DEFAULT": "#cffafe", 16 | "53": "#cffafe", 17 | "200": "#a5f3fc", 18 | "300": "#67e8f9", 19 | "400": "#22d3ee", 20 | "500": "#06b6d4", 21 | "600": "#0891b2", 22 | "700": "#0e7490", 23 | "800": "#155e75", 24 | "900": "#164e63" 25 | } 26 | }, 27 | "borderWidth": {}, 28 | "borderRadius": {}, 29 | "width": {}, 30 | "height": {}, 31 | "minWidth": {}, 32 | "minHeight": {}, 33 | "maxWidth": {}, 34 | "maxHeight": {}, 35 | "padding": {}, 36 | "margin": {}, 37 | "negativeMargin": {}, 38 | "shadows": {}, 39 | "zIndex": {}, 40 | "opacity": {}, 41 | "fill": {}, 42 | "stroke": {}, 43 | "breakAfter": {}, 44 | "extend": { 45 | "screens": { 46 | "tablet": "640px" 47 | }, 48 | "aria": { 49 | "asc": "sort=\"ascending\"", 50 | "desc": "sort=\"descending\"" 51 | }, 52 | "data": { 53 | "checked": "ui~=\"checked\"" 54 | }, 55 | "supports": { 56 | "grid": "display: grid" 57 | }, 58 | "colors": { 59 | "transparent": "transparent", 60 | "current": "currentColor", 61 | "white": "#ffffff", 62 | "purple": "#3f3cbb", 63 | "midnight": "#121063", 64 | "metal": "#565584", 65 | "taxvhiti": "#3ab7bf", 66 | "silver": "#ecebff", 67 | "bubble-gum": "#ff77e9", 68 | "bermuda": "#78dcca", 69 | "tahiti": { 70 | "DEFAULT": "#cffafe", 71 | "100": "#cffafe", 72 | "200": "#a5f3fc", 73 | "300": "#67e8f9", 74 | "400": "#22d3ee", 75 | "500": "#06b6d4", 76 | "600": "#0891b2", 77 | "700": "#0e7490", 78 | "800": "#155e75", 79 | "900": "#164e63" 80 | } 81 | }, 82 | "spacing": {}, 83 | "backgroundPosition": {}, 84 | "backgroundSize": {}, 85 | "borderColor": {}, 86 | "borderWidth": {}, 87 | "borderRadius": {}, 88 | "width": {}, 89 | "height": {}, 90 | "minWidth": {}, 91 | "minHeight": {}, 92 | "maxWidth": {}, 93 | "maxHeight": {}, 94 | "padding": { 95 | "DEFAULT": "1rem", 96 | "sm": "1rem", 97 | "md": "1rem", 98 | "lg": "1rem", 99 | "xl": "1rem", 100 | "2xl": "1rem" 101 | }, 102 | "margin": {}, 103 | "scrollMargin": { 104 | "DEFAULT": "0px", 105 | "sm": "0px", 106 | "md": "0px", 107 | "lg": "0px", 108 | "xl": "0px", 109 | "2xl": "0px" 110 | }, 111 | "negativeMargin": {}, 112 | "shadows": {}, 113 | "zIndex": {}, 114 | "opacity": {}, 115 | "fill": {}, 116 | "stroke": {}, 117 | "breakAfter": {} 118 | } 119 | }, 120 | "variants": {}, 121 | "plugins": {} 122 | } 123 | -------------------------------------------------------------------------------- /tailwind/tests/tailwind.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /twust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "twust" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | authors = { workspace = true } 6 | documentation = { workspace = true } 7 | description = { workspace = true } 8 | license = { workspace = true } 9 | repository = { workspace = true } 10 | readme = { workspace = true } 11 | keywords = { workspace = true } 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | twust_macro = { workspace = true } 17 | 18 | [features] 19 | daisyui = ["twust_macro/daisyui"] 20 | 21 | 22 | -------------------------------------------------------------------------------- /twust/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Oyelowo Oyedayo 3 | * Email: oyelowo.oss@gmail.com 4 | * Copyright (c) 2025 Oyelowo Oyedayo 5 | * Licensed under the MIT license 6 | */ 7 | 8 | // #[cfg(feature = "daisyui")] 9 | pub use twust_macro::{twust_many_classes, twust_one_class}; 10 | /// Typechecks Tailwind CSS class names at compile time. 11 | /// 12 | /// ## Features: 13 | /// - Supports **single string literals** (`tw!("class1 class2")`). 14 | /// - Supports **multiple string arguments** (`tw!["class1", "class2"]`). 15 | /// 16 | /// ## Example Usage 17 | /// 18 | /// ```rust 19 | /// use twust::tw; 20 | /// 21 | /// // Single class string 22 | /// let single_class = tw!("scroll-m-14 flex supports-grid:grid supports-[display:grid]:grid"); 23 | /// 24 | /// // Multiple class strings 25 | /// let multiple_classes = tw!["scroll-m-14", "flex", "supports-grid:grid", "supports-[display:grid]:grid"]; 26 | /// 27 | /// assert_eq!(single_class, "scroll-m-14 flex supports-grid:grid supports-[display:grid]:grid"); 28 | /// assert_eq!(multiple_classes, "scroll-m-14 flex supports-grid:grid supports-[display:grid]:grid"); 29 | /// ``` 30 | /// 31 | /// ## Notes: 32 | /// - The macro supports **both** `tw!(...)` and `tw![...]` syntax. 33 | /// - It ensures at compile time that only **valid Tailwind class names** are used. 34 | /// - The macro automatically **removes trailing spaces** to ensure consistent output. 35 | #[macro_export] 36 | macro_rules! tw { 37 | ($single:literal) => { 38 | $crate::twust_many_classes!($single) 39 | }; 40 | 41 | ($first:literal $(, $rest:literal)*) => { 42 | concat!($crate::twust_many_classes!($first), $(" ", $crate::twust_many_classes!($rest)),*) 43 | }; 44 | } 45 | 46 | /// Typechecks a **single** Tailwind CSS class at compile time. 47 | /// 48 | /// ## Features: 49 | /// - **Accepts only one class at a time** (`tw1!("class-name")`). 50 | /// - Ensures that the class is **valid at compile time**. 51 | /// 52 | /// ## Example Usage 53 | /// 54 | /// ```rust 55 | /// use twust::tw1; 56 | /// 57 | /// let class = tw1!("bg-red-500"); 58 | /// 59 | /// assert_eq!(class, "bg-red-500"); 60 | /// ``` 61 | /// 62 | /// ## Notes: 63 | /// - Unlike `tw!`, `tw1!` **does not allow multiple classes**. 64 | /// - Useful when enforcing single-class validation. 65 | #[macro_export] 66 | macro_rules! tw1 { 67 | ($class:literal) => { 68 | $crate::twust_one_class!($class) 69 | }; 70 | } 71 | 72 | /// Typechecks Tailwind CSS class names at compile time and returns an **array** of class strings. 73 | /// 74 | /// ## Features: 75 | /// - Supports **multiple string arguments** (`tws!["class1", "class2"]`). 76 | /// - Outputs a **Rust array of strings** (`["class1", "class2"]`). 77 | /// 78 | /// ## Example Usage 79 | /// 80 | /// ```rust 81 | /// use twust::tws; 82 | /// 83 | /// let class_list = tws!["scroll-m-14", "flex", "supports-grid:grid", "supports-[display:grid]:grid"]; 84 | /// 85 | /// assert_eq!(class_list, ["scroll-m-14", "flex", "supports-grid:grid", "supports-[display:grid]:grid"]); 86 | /// ``` 87 | /// 88 | /// ## Notes: 89 | /// - Unlike `tw!`, which returns a **single concatenated string**, `tws!` returns a **Rust array** of class names. 90 | /// - Useful when working with frameworks or libraries that require separate class names instead of a single string. 91 | #[macro_export] 92 | macro_rules! tws { 93 | ($($class:literal),*) => { 94 | [$($crate::twust_many_classes!($class)),*] 95 | }; 96 | } 97 | 98 | /// Typechecks multiple **single-class** Tailwind CSS names at compile time and returns an **array**. 99 | /// 100 | /// ## Features: 101 | /// - **Ensures each item is a single valid class**. 102 | /// - Returns a **Rust array** (`["class1", "class2"]`). 103 | /// 104 | /// ## Example Usage 105 | /// 106 | /// ```rust 107 | /// use twust::tws1; 108 | /// 109 | /// let class_list = tws1!["bg-red-500", "text-lg", "p-4"]; 110 | /// 111 | /// assert_eq!(class_list, ["bg-red-500", "text-lg", "p-4"]); 112 | /// ``` 113 | /// 114 | /// ## Notes: 115 | /// - Unlike `tws!`, which allows compound classes like `"text-lg bg-red-500"`, `tws1!` **only allows one class per entry**. 116 | /// - Useful for stricter **class validation** when working with UI frameworks. 117 | #[macro_export] 118 | macro_rules! tws1 { 119 | ($($class:literal),*) => { 120 | [$($crate::twust_one_class!($class)),*] 121 | }; 122 | } 123 | -------------------------------------------------------------------------------- /twust_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "twust_macro" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | authors = { workspace = true } 6 | documentation = { workspace = true } 7 | license = { workspace = true } 8 | repository = { workspace = true } 9 | description = { workspace = true } 10 | readme = { workspace = true } 11 | keywords = { workspace = true } 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [features] 16 | daisyui = [] 17 | 18 | [dependencies] 19 | syn = { workspace = true } 20 | proc-macro2 = { workspace = true } 21 | quote = { workspace = true } 22 | nom = { workspace = true } 23 | serde = { workspace = true } 24 | serde_json = { workspace = true } 25 | 26 | 27 | [lib] 28 | proc-macro = true 29 | -------------------------------------------------------------------------------- /twust_macro/src/config/macros.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Oyelowo Oyedayo 3 | * Email: oyelowo.oss@gmail.com 4 | * Copyright (c) 2023 Oyelowo Oyedayo 5 | * Licensed under the MIT license 6 | */ 7 | use crate::tailwind::tailwind_config::{ColorValue, Key}; 8 | use std::collections::HashMap; 9 | 10 | pub(crate) fn extract_keys_from_colors( 11 | specific_colors: &Option>, 12 | inherited_colors: &Option>, 13 | ) -> Vec { 14 | let mut keys = Vec::new(); 15 | let mut extract_color_keys = |colors: &Option>| { 16 | if let Some(colors_map) = colors { 17 | for (key, value) in colors_map.iter() { 18 | match value { 19 | // e.g for, bg-red => red 20 | ColorValue::Simple(_) => keys.push(key.to_string()), 21 | ColorValue::Shades(shades) => { 22 | for shade_key in shades.keys() { 23 | if shade_key == "DEFAULT" { 24 | keys.push(key.to_string()); 25 | } else { 26 | // e.g for bg-green-500 => green-500 27 | keys.push(format!("{key}-{shade_key}")); 28 | } 29 | } 30 | } 31 | } 32 | } 33 | }; 34 | }; 35 | extract_color_keys(specific_colors); 36 | extract_color_keys(inherited_colors); 37 | keys 38 | } 39 | 40 | // Define Color Fields implementations 41 | macro_rules! define_tailwind_color_field { 42 | ({name: $name:ident, prefix: $prefix:expr, field_name: $default_field:ident, variants: $variants:expr}) => { 43 | pub(crate) struct $name; 44 | 45 | impl TailwindField for $name { 46 | fn get_prefix(&self) -> &'static str { 47 | $prefix 48 | } 49 | 50 | fn get_variants(&self) -> Vec<&'static str> { 51 | $variants.to_vec() 52 | } 53 | 54 | fn get_default(&self, config: &TailwindConfig) -> Vec<&str> { 55 | if let Some(ref core_plugins) = config.core_plugins { 56 | if let Some(enabled) = core_plugins.$default_field { 57 | if !enabled { 58 | return vec![]; 59 | } 60 | } 61 | }; 62 | TAILWIND_CSS.$default_field.to_vec() 63 | } 64 | 65 | fn get_override(&self, config: &TailwindConfig) -> Vec { 66 | if let Some(ref core_plugins) = config.core_plugins { 67 | if let Some(enabled) = core_plugins.$default_field { 68 | if !enabled { 69 | return vec![]; 70 | } 71 | } 72 | }; 73 | 74 | $crate::config::macros::extract_keys_from_colors( 75 | &config.theme.overrides.$default_field, 76 | &config.theme.overrides.colors, 77 | ) 78 | } 79 | 80 | fn get_extend(&self, config: &TailwindConfig) -> Vec { 81 | if let Some(ref core_plugins) = config.core_plugins { 82 | if let Some(enabled) = core_plugins.$default_field { 83 | if !enabled { 84 | return vec![]; 85 | } 86 | } 87 | }; 88 | 89 | $crate::config::macros::extract_keys_from_colors( 90 | &config.theme.extend.$default_field, 91 | &config.theme.extend.colors, 92 | ) 93 | } 94 | 95 | fn handle_special_cases(&self, _config: &TailwindConfig) -> Vec { 96 | vec![] 97 | } 98 | } 99 | }; 100 | } 101 | 102 | pub(crate) use define_tailwind_color_field; 103 | 104 | macro_rules! define_tailwind_field { 105 | ({name : $name:ident, prefix: $prefix:expr, inherited: $inherited:ident, field_name: $field_name:ident, variants: $variants:expr}) => { 106 | pub(crate) struct $name; 107 | 108 | impl TailwindField for $name { 109 | fn get_prefix(&self) -> &'static str { 110 | $prefix 111 | } 112 | 113 | fn get_variants(&self) -> Vec<&'static str> { 114 | $variants.to_vec() 115 | } 116 | 117 | fn get_default(&self, config: &TailwindConfig) -> Vec<&str> { 118 | if let Some(ref core_plugins) = config.core_plugins { 119 | if let Some(enabled) = core_plugins.$field_name { 120 | if !enabled { 121 | return vec![]; 122 | } 123 | } 124 | }; 125 | 126 | TAILWIND_CSS.$field_name.to_vec() 127 | } 128 | 129 | fn get_override(&self, config: &TailwindConfig) -> Vec { 130 | if let Some(ref core_plugins) = config.core_plugins { 131 | if let Some(enabled) = core_plugins.$field_name { 132 | if !enabled { 133 | return vec![]; 134 | } 135 | } 136 | }; 137 | 138 | $crate::config::macros::extract_keys( 139 | &config.theme.overrides.$field_name, 140 | &config.theme.overrides.$inherited, 141 | ) 142 | } 143 | 144 | fn get_extend(&self, config: &TailwindConfig) -> Vec { 145 | if let Some(ref core_plugins) = config.core_plugins { 146 | if let Some(enabled) = core_plugins.$field_name { 147 | if !enabled { 148 | return vec![]; 149 | } 150 | } 151 | }; 152 | 153 | $crate::config::macros::extract_keys( 154 | &config.theme.extend.$field_name, 155 | &config.theme.extend.$inherited, 156 | ) 157 | } 158 | 159 | fn handle_special_cases(&self, _config: &TailwindConfig) -> Vec { 160 | vec![] 161 | } 162 | } 163 | }; 164 | } 165 | pub(crate) use define_tailwind_field; 166 | 167 | pub(crate) fn extract_keys( 168 | specific_config: &Option>, 169 | inherited_config: &Option>, 170 | ) -> Vec { 171 | let mut keys = Vec::new(); 172 | 173 | if let Some(confing) = specific_config { 174 | for key in confing.keys() { 175 | keys.push(key.to_string()); 176 | } 177 | } 178 | 179 | if let Some(config) = inherited_config { 180 | for key in config.keys() { 181 | keys.push(key.to_string()); 182 | } 183 | } 184 | 185 | keys 186 | } 187 | -------------------------------------------------------------------------------- /twust_macro/src/config/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Oyelowo Oyedayo 3 | * Email: oyelowo.oss@gmail.com 4 | * Copyright (c) 2023 Oyelowo Oyedayo 5 | * Licensed under the MIT license 6 | */ 7 | mod classes; 8 | mod macros; 9 | pub mod modifiers; 10 | pub mod noconfig; 11 | // use crate::plugins::daisyui::DAISY_UI_CLASSES; 12 | // use crate::plugins::DAISY_UI_CLASSES; 13 | use crate::plugins::daisyui; 14 | use crate::tailwind::tailwind_config::TailwindConfig; 15 | use std::fs; 16 | 17 | use self::classes::*; 18 | 19 | pub trait TailwindField { 20 | fn get_prefix(&self) -> &'static str; 21 | fn get_variants(&self) -> Vec<&'static str>; 22 | fn get_default(&self, config: &TailwindConfig) -> Vec<&str>; 23 | fn get_override(&self, config: &TailwindConfig) -> Vec; 24 | fn get_extend(&self, config: &TailwindConfig) -> Vec; 25 | fn handle_special_cases(&self, config: &TailwindConfig) -> Vec; 26 | } 27 | 28 | // Put it all together 29 | fn generate_classes_for_keys(field: &dyn TailwindField, keys: &[String]) -> Vec { 30 | let mut classes = Vec::new(); 31 | let variants = field.get_variants(); 32 | let prefix = field.get_prefix(); 33 | 34 | for key in keys.iter() { 35 | // e.g bg-red 36 | classes.push(format!("{prefix}-{key}")); 37 | for variant in variants.iter() { 38 | // e.g border-t-red, border-r-red-500, 39 | classes.push(format!("{prefix}{variant}-{key}")); 40 | } 41 | } 42 | 43 | classes 44 | } 45 | 46 | pub fn add_classes_for_field( 47 | field: &dyn TailwindField, 48 | config: &TailwindConfig, 49 | classes: &mut Vec, 50 | ) { 51 | let overrides = field.get_override(config); 52 | if !overrides.is_empty() { 53 | classes.extend(generate_classes_for_keys(field, &overrides)); 54 | } else { 55 | let default = field.get_default(config); 56 | classes.extend(default.iter().map(|x| x.to_string())); 57 | } 58 | let extend = field.get_extend(config); 59 | classes.extend(generate_classes_for_keys(field, &extend)); 60 | classes.extend(field.handle_special_cases(config)); 61 | } 62 | 63 | pub(crate) fn read_tailwind_config() -> Result { 64 | let current_dir = std::env::current_dir()?; 65 | 66 | // Construct the path to tailwind.config.json relative to the current directory 67 | // typically, top-level directory. 68 | let config_path = current_dir.join("tailwind.config.json"); 69 | 70 | if !config_path.exists() { 71 | return Err(std::io::Error::new( 72 | std::io::ErrorKind::NotFound, 73 | format!( 74 | "tailwind.config.json was not found in the top-level directory - \n{config_path:?}. Make sure it exists." 75 | ), 76 | )); 77 | } 78 | 79 | let content = fs::read_to_string(config_path)?; 80 | let config: TailwindConfig = serde_json::from_str(&content)?; 81 | Ok(config) 82 | } 83 | 84 | pub fn get_classes(config: &TailwindConfig) -> Vec { 85 | // Check that config overrides keys are not one of this list, as those cannot be set 86 | // by the user. 87 | 88 | let mut classes = Vec::new(); 89 | let utilities: [Box; 177] = [ 90 | Box::new(AspectRatio), 91 | Box::new(Container), 92 | Box::new(Columns), 93 | Box::new(BreakAfter), 94 | Box::new(BreakBefore), 95 | Box::new(BreakInside), 96 | Box::new(BoxDecorationBreak), 97 | Box::new(BoxSizing), 98 | Box::new(Display), 99 | Box::new(Float), 100 | Box::new(Clear), 101 | Box::new(Isolation), 102 | Box::new(ObjectFit), 103 | Box::new(ObjectPosition), 104 | Box::new(Overflow), 105 | Box::new(OverscrollBehavior), 106 | Box::new(Position), 107 | Box::new(Top), 108 | Box::new(Right), 109 | Box::new(Bottom), 110 | Box::new(Left), 111 | Box::new(Start), 112 | Box::new(End), 113 | Box::new(Inset), 114 | Box::new(Visibility), 115 | Box::new(ZIndex), 116 | Box::new(FlexBasis), 117 | Box::new(FlexDirection), 118 | Box::new(FlexWrap), 119 | Box::new(Flex), 120 | Box::new(FlexGrow), 121 | Box::new(FlexShrink), 122 | Box::new(Grow), 123 | Box::new(Shrink), 124 | Box::new(Order), 125 | Box::new(GridTemplateColumns), 126 | Box::new(GridColumn), 127 | Box::new(GridColumnStart), 128 | Box::new(GridColumnEnd), 129 | Box::new(GridTemplateRows), 130 | Box::new(GridRow), 131 | Box::new(GridRowStart), 132 | Box::new(GridRowEnd), 133 | Box::new(GridAutoFlow), 134 | Box::new(GridAutoColumns), 135 | Box::new(GridAutoRows), 136 | Box::new(Gap), 137 | Box::new(JustifyContent), 138 | Box::new(JustifyItems), 139 | Box::new(JustifySelf), 140 | Box::new(AlignContent), 141 | Box::new(AlignItems), 142 | Box::new(AlignSelf), 143 | Box::new(PlaceContent), 144 | Box::new(PlaceItems), 145 | Box::new(PlaceSelf), 146 | Box::new(PlaceholderColor), 147 | Box::new(PlaceholderOpacity), 148 | Box::new(Padding), 149 | Box::new(Margin), 150 | Box::new(SpaceBetween), 151 | Box::new(Width), 152 | Box::new(MinWidth), 153 | Box::new(MaxWidth), 154 | Box::new(Height), 155 | Box::new(MinHeight), 156 | Box::new(MaxHeight), 157 | Box::new(FontFamily), 158 | Box::new(FontSize), 159 | Box::new(FontSmoothing), 160 | Box::new(FontStyle), 161 | Box::new(FontWeight), 162 | Box::new(FontVariantNumeric), 163 | Box::new(LetterSpacing), 164 | Box::new(LineClamp), 165 | Box::new(LineHeight), 166 | Box::new(ListStyleImage), 167 | Box::new(ListStylePosition), 168 | Box::new(ListStyleType), 169 | Box::new(TextAlign), 170 | Box::new(TextColor), 171 | Box::new(TextDecoration), 172 | Box::new(TextDecorationColor), 173 | Box::new(TextDecorationStyle), 174 | Box::new(TextDecorationThickness), 175 | Box::new(TextUnderlineOffset), 176 | Box::new(TextTransform), 177 | Box::new(TextOverflow), 178 | Box::new(TextIndent), 179 | Box::new(VerticalAlign), 180 | Box::new(Whitespace), 181 | Box::new(WordBreak), 182 | Box::new(Hyphens), 183 | Box::new(Content), 184 | Box::new(BackgroundAttachment), 185 | Box::new(BackgroundClip), 186 | Box::new(BackgroundColor), 187 | Box::new(BackgroundOrigin), 188 | Box::new(BackgroundPosition), 189 | Box::new(BackgroundRepeat), 190 | Box::new(BackgroundSize), 191 | Box::new(BackgroundImage), 192 | Box::new(GradientColorStopsFrom), 193 | Box::new(GradientColorStopsVia), 194 | Box::new(GradientColorStopsTo), 195 | Box::new(BorderRadius), 196 | Box::new(BorderWidth), 197 | Box::new(BorderColor), 198 | Box::new(BorderStyle), 199 | Box::new(BorderOpacity), 200 | Box::new(DivideWidth), 201 | Box::new(DivideColor), 202 | Box::new(DivideStyle), 203 | Box::new(OutlineWidth), 204 | Box::new(OutlineColor), 205 | Box::new(OutlineStyle), 206 | Box::new(OutlineOffset), 207 | Box::new(RingWidth), 208 | Box::new(RingColor), 209 | Box::new(RingOffsetWidth), 210 | Box::new(RingOffsetColor), 211 | Box::new(BoxShadow), 212 | Box::new(BoxShadowColor), 213 | Box::new(Opacity), 214 | Box::new(MixBlendMode), 215 | Box::new(BackgroundBlendMode), 216 | Box::new(Blur), 217 | Box::new(Brightness), 218 | Box::new(Contrast), 219 | Box::new(DropShadow), 220 | Box::new(Grayscale), 221 | Box::new(HueRotate), 222 | Box::new(Invert), 223 | Box::new(Saturate), 224 | Box::new(Sepia), 225 | Box::new(BackdropBlur), 226 | Box::new(BackdropBrightness), 227 | Box::new(BackdropContrast), 228 | Box::new(BackdropGrayscale), 229 | Box::new(BackdropHueRotate), 230 | Box::new(BackdropInvert), 231 | Box::new(BackdropOpacity), 232 | Box::new(BackdropSaturate), 233 | Box::new(BackdropSepia), 234 | Box::new(BorderCollapse), 235 | Box::new(BorderSpacing), 236 | Box::new(TableLayout), 237 | Box::new(CaptionSide), 238 | Box::new(TransitionProperty), 239 | Box::new(TransitionDuration), 240 | Box::new(TransitionTimingFunction), 241 | Box::new(TransitionDelay), 242 | Box::new(Animation), 243 | Box::new(Scale), 244 | Box::new(Rotate), 245 | Box::new(Translate), 246 | Box::new(Skew), 247 | Box::new(TransformOrigin), 248 | Box::new(AccentColor), 249 | Box::new(Appearance), 250 | Box::new(Cursor), 251 | Box::new(CaretColor), 252 | Box::new(PointerEvents), 253 | Box::new(Resize), 254 | Box::new(ScrollBehavior), 255 | Box::new(ScrollMargin), 256 | Box::new(ScrollPadding), 257 | Box::new(ScrollSnapAlign), 258 | Box::new(ScrollSnapStop), 259 | Box::new(ScrollSnapType), 260 | Box::new(TouchAction), 261 | Box::new(UserSelect), 262 | Box::new(WillChange), 263 | Box::new(FillColor), 264 | Box::new(StrokeColor), 265 | Box::new(StrokeWidth), 266 | Box::new(ScreenReaders), 267 | ]; 268 | 269 | for utility in utilities { 270 | add_classes_for_field(utility.as_ref(), config, &mut classes); 271 | } 272 | 273 | let allowed_extra_classes = config 274 | .allowed_lists 275 | .as_ref() 276 | .and_then(|x| x.classes.to_owned()) 277 | .unwrap_or_default(); 278 | 279 | classes.extend(allowed_extra_classes); 280 | classes.push("group".to_string()); 281 | 282 | classes.extend( 283 | daisyui::get_daisy_v3_classes() 284 | .iter() 285 | .map(ToString::to_string), 286 | ); 287 | 288 | classes 289 | } 290 | -------------------------------------------------------------------------------- /twust_macro/src/config/modifiers.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Oyelowo Oyedayo 3 | * Email: oyelowo.oss@gmail.com 4 | * Copyright (c) 2023 Oyelowo Oyedayo 5 | * Licensed under the MIT license 6 | */ 7 | 8 | use crate::tailwind::{modifiers::ARIA_DEFAULT, tailwind_config::TailwindConfig}; 9 | 10 | use super::TailwindField; 11 | 12 | macro_rules! define_tailwind_modifier { 13 | ({name: $struct_name:ident, field_name: $field_name:ident, prefix: $prefix:expr, default_values: $default_values:expr }) => { 14 | pub struct $struct_name; 15 | 16 | impl TailwindField for $struct_name { 17 | fn get_prefix(&self) -> &'static str { 18 | $prefix 19 | } 20 | 21 | fn get_variants(&self) -> Vec<&'static str> { 22 | vec![] 23 | } 24 | 25 | fn get_default(&self, _config: &TailwindConfig) -> Vec<&'static str> { 26 | $default_values.to_vec() 27 | } 28 | 29 | fn get_override(&self, config: &TailwindConfig) -> Vec { 30 | config 31 | .theme 32 | .overrides 33 | .$field_name 34 | .clone() 35 | .unwrap_or_default() 36 | .into_keys() 37 | .collect() 38 | } 39 | 40 | fn get_extend(&self, config: &TailwindConfig) -> Vec { 41 | config 42 | .theme 43 | .extend 44 | .$field_name 45 | .clone() 46 | .unwrap_or_default() 47 | .into_keys() 48 | .collect() 49 | } 50 | 51 | fn handle_special_cases(&self, _config: &TailwindConfig) -> Vec { 52 | vec![] 53 | } 54 | } 55 | }; 56 | } 57 | 58 | define_tailwind_modifier!({ 59 | name: Aria, 60 | field_name: aria, 61 | prefix: "aria", 62 | default_values: ARIA_DEFAULT 63 | }); 64 | 65 | define_tailwind_modifier!({ 66 | name: Supports, 67 | field_name: supports, 68 | prefix: "supports", 69 | default_values: [] 70 | }); 71 | 72 | define_tailwind_modifier!({ 73 | name: Data, 74 | field_name: data, 75 | prefix: "data", 76 | default_values: [] 77 | }); 78 | -------------------------------------------------------------------------------- /twust_macro/src/config/noconfig.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Oyelowo Oyedayo 3 | * Email: oyelowo.oss@gmail.com 4 | * Copyright (c) 2023 Oyelowo Oyedayo 5 | * Licensed under the MIT license 6 | */ 7 | pub const UNCONFIGURABLE: [&str; 61] = [ 8 | "container", 9 | "columns", 10 | "break_after", 11 | "break_before", 12 | "break_inside", 13 | "box_decoration_break", 14 | "box_sizing", 15 | "float", 16 | "clear", 17 | "isolation", 18 | "object_fit", 19 | "overflow", 20 | "overscroll_behavior", 21 | "position", 22 | "visibility", 23 | "flex_direction", 24 | "flex_wrap", 25 | "grid_auto_flow", 26 | "justify_content", 27 | "justify_items", 28 | "justify_self", 29 | "align_content", 30 | "align_items", 31 | "align_self", 32 | "place_content", 33 | "place_items", 34 | "place_self", 35 | "font_smoothing", 36 | "font_variant_numeric", 37 | "list_style_position", 38 | "text_align", 39 | "text_decoration", 40 | "text_decoration_style", 41 | "text_transform", 42 | "text_overflow", 43 | "vertical_align", 44 | "whitespace", 45 | "word_break", 46 | "hyphens", 47 | "background_attachment", 48 | "background_clip", 49 | "background_origin", 50 | "background_repeat", 51 | "border_style", 52 | "divide_style", 53 | "outline_style", 54 | "mix_blend_mode", 55 | "background_blend_mode", 56 | "border_collapse", 57 | "table_layout", 58 | "caption_side", 59 | "appearance", 60 | "pointer_events", 61 | "resize", 62 | "scroll_behavior", 63 | "scroll_snap_align", 64 | "scroll_snap_stop", 65 | "scroll_snap_type", 66 | "touch_action", 67 | "user_select", 68 | "screen_readers", 69 | ]; 70 | -------------------------------------------------------------------------------- /twust_macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Oyelowo Oyedayo 3 | * Email: oyelowo.oss@gmail.com 4 | * Copyright (c) 2023 Oyelowo Oyedayo 5 | * Licensed under the MIT license 6 | */ 7 | 8 | use nom::{ 9 | branch::alt, 10 | bytes::complete::{tag, take_until, take_while1}, 11 | character::complete::{digit1, multispace0, multispace1}, 12 | combinator::{all_consuming, not, opt, recognize}, 13 | multi::separated_list0, 14 | number, 15 | sequence::{preceded, tuple}, 16 | IResult, 17 | }; 18 | use std::collections::HashSet; 19 | use syn::{parse_macro_input, LitStr}; 20 | mod config; 21 | mod plugins; 22 | mod tailwind; 23 | use tailwind::{ 24 | colorful::COLORFUL_BASECLASSES, lengthy::LENGTHY, modifiers::get_modifiers, 25 | tailwind_config::CustomisableClasses, valid_baseclass_names::VALID_BASECLASS_NAMES, 26 | }; 27 | 28 | use config::{get_classes, noconfig::UNCONFIGURABLE, read_tailwind_config}; 29 | use proc_macro::TokenStream; 30 | use tailwind::signable::SIGNABLES; 31 | 32 | fn setup(input: &LitStr) -> Result<(Vec, Vec), TokenStream> { 33 | let config = &(match read_tailwind_config() { 34 | Ok(config) => config, 35 | Err(e) => { 36 | return Err(syn::Error::new_spanned( 37 | input, 38 | format!("Error reading Tailwind config: {}", e), 39 | ) 40 | .to_compile_error() 41 | .into()); 42 | } 43 | }); 44 | let modifiers = get_modifiers(config); 45 | let valid_class_names = get_classes(config); 46 | let is_unconfigurable = |classes: &CustomisableClasses, action_type_str: &str| { 47 | serde_json::to_value(classes) 48 | .expect("Unable to convert to value") 49 | .as_object() 50 | .expect("Unable to convert to object") 51 | .iter() 52 | .any(|(key, value)| { 53 | if UNCONFIGURABLE.contains(&key.as_str()) && !value.is_null() { 54 | panic!("You cannot {action_type_str} the key: {key} in tailwind.config.json",); 55 | } 56 | false 57 | }) 58 | }; 59 | is_unconfigurable(&config.theme.overrides, "override"); 60 | is_unconfigurable(&config.theme.extend, "extend"); 61 | Ok((modifiers, valid_class_names)) 62 | } 63 | 64 | fn get_classes_straight() -> HashSet { 65 | HashSet::from_iter(get_classes( 66 | &read_tailwind_config().expect("Problem getting classes"), 67 | )) 68 | } 69 | 70 | fn is_valid_classname(class_name: &str) -> bool { 71 | get_classes_straight().contains(class_name) 72 | } 73 | 74 | fn is_valid_modifier(modifier: &str) -> bool { 75 | let modifiers: HashSet = HashSet::from_iter(get_modifiers( 76 | &read_tailwind_config().expect("Problem getting modifiers"), 77 | )); 78 | modifiers.contains(modifier) 79 | } 80 | 81 | fn parse_predefined_tw_classname(input: &str) -> IResult<&str, ()> { 82 | let (input, class_name) = recognize(|i| { 83 | // Considering a Tailwind class consists of alphanumeric, dashes, and slash 84 | nom::bytes::complete::is_a( 85 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-./", 86 | )(i) 87 | })(input)?; 88 | 89 | let is_signable = SIGNABLES.iter().any(|s| { 90 | class_name 91 | .strip_prefix('-') 92 | .unwrap_or(class_name) 93 | .starts_with(s) 94 | }); 95 | 96 | if is_signable && is_valid_classname(class_name.strip_prefix('-').unwrap_or(class_name)) 97 | || !is_signable && is_valid_classname(class_name) 98 | { 99 | Ok((input, ())) 100 | } else { 101 | Err(nom::Err::Error(nom::error::Error::new( 102 | input, 103 | nom::error::ErrorKind::Tag, 104 | ))) 105 | } 106 | } 107 | 108 | fn is_ident_char(c: char) -> bool { 109 | c.is_alphanumeric() || c == '_' || c == '-' 110 | } 111 | 112 | fn is_lengthy_classname(class_name: &str) -> bool { 113 | LENGTHY.contains(&class_name.strip_prefix('-').unwrap_or(class_name)) 114 | } 115 | 116 | // Custom number parser that handles optional decimals and signs, and scientific notation 117 | fn float_strict(input: &str) -> IResult<&str, f64> { 118 | let (input, number) = recognize(tuple(( 119 | opt(alt((tag("-"), tag("+")))), 120 | digit1, 121 | opt(preceded(tag("."), digit1)), 122 | opt(tuple(( 123 | alt((tag("e"), tag("E"))), 124 | opt(alt((tag("-"), tag("+")))), 125 | digit1, 126 | ))), 127 | )))(input)?; 128 | 129 | let float_val: f64 = number.parse().unwrap(); 130 | Ok((input, float_val)) 131 | } 132 | 133 | fn parse_length_unit(input: &str) -> IResult<&str, String> { 134 | let (input, number) = float_strict(input)?; 135 | let (input, unit) = { 136 | // px|em|rem|%|cm|mm|in|pt|pc|vh|vw|vmin|vmax 137 | alt(( 138 | tag("px"), 139 | tag("em"), 140 | tag("rem"), 141 | tag("%"), 142 | tag("cm"), 143 | tag("mm"), 144 | tag("in"), 145 | tag("pt"), 146 | tag("pc"), 147 | tag("vh"), 148 | tag("vw"), 149 | tag("vmin"), 150 | tag("vmax"), 151 | // TODO: Should i allow unitless values? Would need something like this in caller 152 | // location if so: 153 | // let (input, _) = alt((parse_length_unit, parse_number))(input)?; 154 | tag(""), 155 | )) 156 | }(input)?; 157 | Ok((input, format!("{}{}", number, unit))) 158 | } 159 | 160 | // text-[22px] 161 | fn lengthy_arbitrary_classname(input: &str) -> IResult<&str, ()> { 162 | let (input, class_name) = take_until("-[")(input)?; 163 | let (input, _) = if is_lengthy_classname(class_name) { 164 | Ok((input, ())) 165 | } else { 166 | Err(nom::Err::Error(nom::error::Error::new( 167 | input, 168 | nom::error::ErrorKind::Tag, 169 | ))) 170 | }?; 171 | 172 | // arbitrary value 173 | let (input, _) = tag("-")(input)?; 174 | let (input, _) = tag("[")(input)?; 175 | // is number 176 | let (input, _) = parse_length_unit(input)?; 177 | let (input, _) = tag("]")(input)?; 178 | Ok((input, ())) 179 | } 180 | 181 | // #bada55 182 | fn parse_hex_color(input: &str) -> IResult<&str, String> { 183 | let (input, _) = tag("#")(input)?; 184 | let (input, color) = take_while1(|c: char| c.is_ascii_hexdigit())(input)?; 185 | let (input, _) = if color.chars().count() == 3 || color.chars().count() == 6 { 186 | Ok((input, ())) 187 | } else { 188 | Err(nom::Err::Error(nom::error::Error::new( 189 | input, 190 | nom::error::ErrorKind::Tag, 191 | ))) 192 | }?; 193 | let color = format!("#{}", color); 194 | Ok((input, color)) 195 | } 196 | 197 | fn parse_u8(input: &str) -> IResult<&str, u8> { 198 | let (input, num) = number::complete::double(input)?; 199 | let input = match num as u32 { 200 | 0..=255 => input, 201 | _ => { 202 | return Err(nom::Err::Error(nom::error::Error::new( 203 | input, 204 | nom::error::ErrorKind::Tag, 205 | ))) 206 | } 207 | }; 208 | Ok((input, num as u8)) 209 | } 210 | 211 | // rgb(255, 255, 255) rgb(255_255_255) 212 | fn parse_rgb_color(input: &str) -> IResult<&str, String> { 213 | let (input, _) = tag("rgb(")(input)?; 214 | let (input, r) = parse_u8(input)?; 215 | let (input, _) = alt((tag(","), tag("_")))(input)?; 216 | let (input, g) = parse_u8(input)?; 217 | let (input, _) = alt((tag(","), tag("_")))(input)?; 218 | let (input, b) = parse_u8(input)?; 219 | let (input, _) = tag(")")(input)?; 220 | let color = format!("rgb({}, {}, {})", r, g, b); 221 | Ok((input, color)) 222 | } 223 | 224 | // rgba(255, 255, 255, 0.5) rgba(255_255_255_0.5) 225 | fn parse_rgba_color(input: &str) -> IResult<&str, String> { 226 | let (input, _) = tag("rgba(")(input)?; 227 | let (input, r) = parse_u8(input)?; 228 | let (input, _) = alt((tag(","), tag("_")))(input)?; 229 | let (input, g) = parse_u8(input)?; 230 | let (input, _) = alt((tag(","), tag("_")))(input)?; 231 | let (input, b) = parse_u8(input)?; 232 | let (input, _) = alt((tag(","), tag("_")))(input)?; 233 | let (input, a) = number::complete::double(input)?; 234 | let (input, _) = tag(")")(input)?; 235 | let color = format!("rgba({}, {}, {}, {})", r, g, b, a); 236 | Ok((input, color)) 237 | } 238 | 239 | fn is_colorful_baseclass(class_name: &str) -> bool { 240 | COLORFUL_BASECLASSES.contains(&class_name) 241 | } 242 | 243 | // text-[#bada55] 244 | fn colorful_arbitrary_baseclass(input: &str) -> IResult<&str, ()> { 245 | let (input, class_name) = take_until("-[")(input)?; 246 | let (input, _) = if is_colorful_baseclass(class_name) { 247 | Ok((input, ())) 248 | } else { 249 | Err(nom::Err::Error(nom::error::Error::new( 250 | input, 251 | nom::error::ErrorKind::Tag, 252 | ))) 253 | }?; 254 | 255 | // arbitrary value 256 | let (input, _) = tag("-")(input)?; 257 | let (input, _) = tag("[")(input)?; 258 | let (input, _) = alt((parse_hex_color, parse_rgb_color, parse_rgba_color))(input)?; 259 | let (input, _) = tag("]")(input)?; 260 | Ok((input, ())) 261 | } 262 | 263 | // e.g: [mask-type:alpha] 264 | fn kv_pair_classname(input: &str) -> IResult<&str, ()> { 265 | let (input, _) = tag("[")(input)?; 266 | let (input, _) = take_while1(is_ident_char)(input)?; 267 | let (input, _) = tag(":")(input)?; 268 | let (input, _) = take_until("]")(input)?; 269 | let (input, _) = tag("]")(input)?; 270 | Ok((input, ())) 271 | } 272 | 273 | // before:content-['Festivus'] 274 | fn arbitrary_content(input: &str) -> IResult<&str, ()> { 275 | let (input, _) = tag("content-['")(input)?; 276 | let (input, _) = take_until("']")(input)?; 277 | let (input, _) = tag("']")(input)?; 278 | Ok((input, ())) 279 | } 280 | 281 | // content-[>] content-[<] 282 | fn arbitrary_with_arrow(input: &str) -> IResult<&str, ()> { 283 | let (input, _) = take_while1(is_ident_char)(input)?; 284 | let (input, _) = tag("[")(input)?; 285 | let (input, _) = alt((tag(">"), tag("<")))(input)?; 286 | let (input, _) = take_until("]")(input)?; 287 | let (input, _) = tag("]")(input)?; 288 | Ok((input, ())) 289 | } 290 | 291 | // bg-black/25 292 | fn predefined_colorful_opacity(input: &str) -> IResult<&str, ()> { 293 | let input = if COLORFUL_BASECLASSES 294 | .iter() 295 | .any(|cb| input.trim().starts_with(cb)) 296 | { 297 | input 298 | } else { 299 | return Err(nom::Err::Error(nom::error::Error::new( 300 | input, 301 | nom::error::ErrorKind::Tag, 302 | ))); 303 | }; 304 | let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; 305 | // let (input, _) = take_until("/")(input)?; 306 | let (input, _) = tag("/")(input)?; 307 | 308 | let (input, num) = number::complete::double(input)?; 309 | let input = match num as u8 { 310 | 0..=100 => input, 311 | _ => { 312 | return Err(nom::Err::Error(nom::error::Error::new( 313 | input, 314 | nom::error::ErrorKind::Tag, 315 | ))) 316 | } 317 | }; 318 | 319 | Ok((input, ())) 320 | } 321 | 322 | // bg-black/[27] bg-black/[27%] 323 | fn arbitrary_opacity(input: &str) -> IResult<&str, ()> { 324 | let input = if COLORFUL_BASECLASSES 325 | .iter() 326 | .any(|cb| input.trim().starts_with(cb)) 327 | { 328 | input 329 | } else { 330 | return Err(nom::Err::Error(nom::error::Error::new( 331 | input, 332 | nom::error::ErrorKind::Tag, 333 | ))); 334 | }; 335 | let (input, _) = take_while1(|char| is_ident_char(char) && char != '/')(input)?; 336 | let (input, _) = tag("/")(input)?; 337 | let (input, _) = tag("[")(input)?; 338 | // 0-100 integer 339 | let (input, num) = number::complete::double(input)?; 340 | let input = match num as u8 { 341 | 0..=100 => input, 342 | _ => { 343 | return Err(nom::Err::Error(nom::error::Error::new( 344 | input, 345 | nom::error::ErrorKind::Tag, 346 | ))) 347 | } 348 | }; 349 | let (input, _) = opt(tag("%"))(input)?; 350 | let (input, _) = tag("]")(input)?; 351 | Ok((input, ())) 352 | } 353 | 354 | // bg-[url('/img/down-arrow.svg')] 355 | fn bg_arbitrary_url(input: &str) -> IResult<&str, ()> { 356 | // prefixed by baseclass 357 | let input = if COLORFUL_BASECLASSES 358 | .iter() 359 | .any(|cb| input.trim().starts_with(cb)) 360 | { 361 | input 362 | } else { 363 | return Err(nom::Err::Error(nom::error::Error::new( 364 | input, 365 | nom::error::ErrorKind::Tag, 366 | ))); 367 | }; 368 | let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; 369 | let (input, _) = tag("[")(input)?; 370 | let (input, _) = tag("url('")(input)?; 371 | let (input, _) = take_until("')")(input)?; 372 | let (input, _) = tag("')")(input)?; 373 | let (input, _) = tag("]")(input)?; 374 | Ok((input, ())) 375 | } 376 | 377 | // grid-cols-[fit-content(theme(spacing.32))] 378 | fn arbitrary_css_value(input: &str) -> IResult<&str, ()> { 379 | // is prefixed by valid base class 380 | // take until -[ 381 | let (input, base_class) = take_until("-[")(input)?; 382 | let input = if VALID_BASECLASS_NAMES 383 | .iter() 384 | .any(|cb| base_class.trim().eq(*cb)) 385 | { 386 | input 387 | } else { 388 | return Err(nom::Err::Error(nom::error::Error::new( 389 | base_class, 390 | nom::error::ErrorKind::Tag, 391 | ))); 392 | }; 393 | let (input, _) = tag("-[")(input)?; 394 | let (input, _) = not(alt(( 395 | tag("--"), 396 | tag("var(--"), 397 | // :var(-- 398 | )))(input)?; 399 | let (input, _) = take_while1(|char| is_ident_char(char) && char != '(')(input)?; 400 | let (input, _) = tag("(")(input)?; 401 | let (input, _) = take_until(")]")(input)?; 402 | 403 | // allow anything inthe brackets 404 | let (input, _) = take_until("]")(input)?; 405 | let (input, _) = tag("]")(input)?; 406 | Ok((input, ())) 407 | } 408 | 409 | // bg-[--my-color] 410 | fn arbitrary_css_var(input: &str) -> IResult<&str, ()> { 411 | // is prefixed by valid base class 412 | let input = if VALID_BASECLASS_NAMES 413 | .iter() 414 | .any(|cb| input.trim().starts_with(cb)) 415 | { 416 | input 417 | } else { 418 | return Err(nom::Err::Error(nom::error::Error::new( 419 | input, 420 | nom::error::ErrorKind::Tag, 421 | ))); 422 | }; 423 | let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; 424 | let (input, _) = tag("[")(input)?; 425 | let (input, _) = tag("--")(input)?; 426 | let (input, _) = take_while1(|char| is_ident_char(char) && char != ']')(input)?; 427 | let (input, _) = tag("]")(input)?; 428 | Ok((input, ())) 429 | } 430 | // text-[var(--my-var)] 431 | fn arbitrary_css_var2(input: &str) -> IResult<&str, ()> { 432 | // is prefixed by valid base class 433 | let input = if VALID_BASECLASS_NAMES 434 | .iter() 435 | .any(|cb| input.trim().starts_with(cb)) 436 | { 437 | input 438 | } else { 439 | return Err(nom::Err::Error(nom::error::Error::new( 440 | input, 441 | nom::error::ErrorKind::Tag, 442 | ))); 443 | }; 444 | let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; 445 | let (input, _) = tag("[")(input)?; 446 | let (input, _) = tag("var(--")(input)?; 447 | let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; 448 | let (input, _) = tag(")]")(input)?; 449 | Ok((input, ())) 450 | } 451 | 452 | // text-[length:var(--my-var)] 453 | fn arbitrary_css_var3(input: &str) -> IResult<&str, ()> { 454 | // is prefixed by valid base class 455 | let input = if VALID_BASECLASS_NAMES 456 | .iter() 457 | .any(|cb| input.trim().starts_with(cb)) 458 | { 459 | input 460 | } else { 461 | return Err(nom::Err::Error(nom::error::Error::new( 462 | input, 463 | nom::error::ErrorKind::Tag, 464 | ))); 465 | }; 466 | let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; 467 | let (input, _) = tag("[")(input)?; 468 | let (input, _) = take_while1(|char| is_ident_char(char) && char != ':')(input)?; 469 | let (input, _) = tag(":")(input)?; 470 | let (input, _) = tag("var(--")(input)?; 471 | let (input, _) = take_while1(|char| is_ident_char(char) && char != ')')(input)?; 472 | let (input, _) = tag(")]")(input)?; 473 | Ok((input, ())) 474 | } 475 | 476 | // group/edit 477 | fn arbitrary_group_classname(input: &str) -> IResult<&str, ()> { 478 | let (input, _) = alt((tag("group"),))(input)?; 479 | let (input, _) = tag("/")(input)?; 480 | let (input, _) = take_while1(is_ident_char)(input)?; 481 | Ok((input, ())) 482 | } 483 | 484 | fn parse_single_tw_classname(input: &str) -> IResult<&str, ()> { 485 | alt(( 486 | // bg-[url('/what_a_rush.png')] 487 | bg_arbitrary_url, 488 | // bg-black/25 489 | predefined_colorful_opacity, 490 | // group/edit 491 | arbitrary_group_classname, 492 | // bg-black/[27] 493 | arbitrary_opacity, 494 | // btn 495 | parse_predefined_tw_classname, 496 | // [mask-type:luminance] [mask-type:alpha] 497 | kv_pair_classname, 498 | // text-[22px] 499 | lengthy_arbitrary_classname, 500 | // text-[#bada55] 501 | colorful_arbitrary_baseclass, 502 | // before:content-['Festivus'] 503 | arbitrary_content, 504 | // content-[>] content-[<] 505 | arbitrary_with_arrow, 506 | // bg-[--my-color] 507 | arbitrary_css_var, 508 | // text-[var(--my-var)] 509 | arbitrary_css_var2, 510 | // text-[length:var(--my-var)] 511 | arbitrary_css_var3, 512 | // grid-cols-[fit-content(theme(spacing.32))] 513 | arbitrary_css_value, 514 | ))(input) 515 | } 516 | 517 | // hover:underline 518 | fn predefined_modifier(input: &str) -> IResult<&str, ()> { 519 | let (input, modifier) = recognize(|i| { 520 | // Assuming a Tailwind class consists of alphanumeric, dashes, and colons 521 | nom::bytes::complete::is_a( 522 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-", 523 | )(i) 524 | })(input)?; 525 | 526 | if is_valid_modifier(modifier) { 527 | Ok((input, ())) 528 | } else { 529 | Err(nom::Err::Error(nom::error::Error::new( 530 | input, 531 | nom::error::ErrorKind::Tag, 532 | ))) 533 | } 534 | } 535 | 536 | // predefined special modifiers e.g peer-checked:p-4 group-hover:visible 537 | fn predefined_special_modifier(input: &str) -> IResult<&str, ()> { 538 | let (input, _) = alt(( 539 | // peer-checked:p-4 540 | tuple((tag("peer-"), predefined_modifier)), 541 | // group-hover:visible 542 | tuple((tag("group-"), predefined_modifier)), 543 | ))(input)?; 544 | Ok((input, ())) 545 | } 546 | 547 | // [&:nth-child(3)]:underline 548 | // [&_p]:mt-4 549 | fn arbitrary_front_selector_modifier(input: &str) -> IResult<&str, ()> { 550 | let (input, _) = tag("[&")(input)?; 551 | let (input, _) = take_until("]")(input)?; 552 | let (input, _) = tag("]")(input)?; 553 | Ok((input, ())) 554 | } 555 | 556 | // group-[:nth-of-type(3)_&]:block 557 | fn arbitrary_back_selector_modifier(input: &str) -> IResult<&str, ()> { 558 | let (input, _) = take_while1(|char| is_ident_char(char) && char != '[')(input)?; 559 | let (input, _) = tag("-[")(input)?; 560 | let (input, _) = take_until("&]")(input)?; 561 | let (input, _) = tag("&]")(input)?; 562 | Ok((input, ())) 563 | } 564 | 565 | // [@supports(display:grid)]:grid 566 | fn arbitrary_at_supports_rule_modifier(input: &str) -> IResult<&str, ()> { 567 | let (input, _) = tag("[@supports(")(input)?; 568 | let (input, _) = take_until(")")(input)?; 569 | let (input, _) = tag(")]")(input)?; 570 | Ok((input, ())) 571 | } 572 | 573 | // [@media(any-hover:hover){&:hover}]:opacity-100 574 | fn arbitrary_at_media_rule_modifier(input: &str) -> IResult<&str, ()> { 575 | // starts with [@media and ends with ] 576 | let (input, _) = tag("[@media(")(input)?; 577 | let (input, _) = take_until("]")(input)?; 578 | let (input, _) = tag("]")(input)?; 579 | Ok((input, ())) 580 | } 581 | 582 | // group/edit invisible hover:bg-slate-200 group-hover/item:visible 583 | fn group_peer_modifier(input: &str) -> IResult<&str, ()> { 584 | let (input, _) = alt(( 585 | tuple((tag("group-"), predefined_modifier)), 586 | // https://tailwindcss.com/docs/hover-focus-and-other-states#differentiating-peers 587 | // peer-checked/published:text-sky-500 588 | tuple((tag("peer-"), predefined_modifier)), 589 | ))(input)?; 590 | let (input, _) = tag("/")(input)?; 591 | let (input, _) = take_while1(is_ident_char)(input)?; 592 | Ok((input, ())) 593 | } 594 | 595 | // hidden group-[.is-published]:block 596 | // group-[:nth-of-type(3)_&]:block 597 | // peer-[.is-dirty]:peer-required:block hidden 598 | // hidden peer-[:nth-of-type(3)_&]:block 599 | fn group_modifier_selector(input: &str) -> IResult<&str, ()> { 600 | let (input, _) = alt((tag("group"), tag("peer")))(input)?; 601 | let (input, _) = tag("-[")(input)?; 602 | let (input, _) = take_until("]")(input)?; 603 | let (input, _) = tag("]")(input)?; 604 | Ok((input, ())) 605 | } 606 | 607 | // supports-[backdrop-filter] 608 | fn supports_arbitrary(input: &str) -> IResult<&str, ()> { 609 | let (input, _) = tag("supports-[")(input)?; 610 | let (input, _) = take_until("]")(input)?; 611 | let (input, _) = tag("]")(input)?; 612 | Ok((input, ())) 613 | } 614 | 615 | // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] 616 | // aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] 617 | // group-data-[selected=Right]:w-[30px] 618 | // group-aria-[main-page=false]/main:hidden / group-data-[main-page=false]/main:hidden 619 | fn aria_or_data_arbitrary(input: &str) -> IResult<&str, ()> { 620 | let (input, _) = opt(tag("group-"))(input)?; 621 | let (input, _) = alt((tag("aria-["), tag("data-[")))(input)?; 622 | let (input, _) = take_while1(is_ident_char)(input)?; 623 | let (input, _) = tag("=")(input)?; 624 | let (input, _) = take_while1(is_ident_char)(input)?; 625 | let (input, _) = tag("]")(input)?; 626 | let (input, _) = opt(tuple((tag("/"), take_while1(is_ident_char))))(input)?; 627 | Ok((input, ())) 628 | } 629 | 630 | // data-[size=large]:p-8 631 | fn data_arbitrary(input: &str) -> IResult<&str, ()> { 632 | let (input, _) = tag("data-[")(input)?; 633 | let (input, _) = take_while1(is_ident_char)(input)?; 634 | let (input, _) = tag("=")(input)?; 635 | let (input, _) = take_while1(is_ident_char)(input)?; 636 | let (input, _) = tag("]")(input)?; 637 | Ok((input, ())) 638 | } 639 | 640 | // min-[320px]:text-center max-[600px]:bg-sky-300 641 | fn min_max_arbitrary_modifier(input: &str) -> IResult<&str, ()> { 642 | let (input, _) = alt((tag("min-"), tag("max-")))(input)?; 643 | let (input, _) = tag("[")(input)?; 644 | let (input, _) = parse_length_unit(input)?; 645 | let (input, _) = tag("]")(input)?; 646 | Ok((input, ())) 647 | } 648 | 649 | // *:overflow-scroll 650 | fn wildcard_modifier(input: &str) -> IResult<&str, ()> { 651 | let (input, _) = tag("*")(input)?; 652 | Ok((input, ())) 653 | } 654 | 655 | fn modifier(input: &str) -> IResult<&str, ()> { 656 | alt(( 657 | group_modifier_selector, 658 | group_peer_modifier, 659 | predefined_special_modifier, 660 | arbitrary_front_selector_modifier, 661 | arbitrary_back_selector_modifier, 662 | arbitrary_at_supports_rule_modifier, 663 | arbitrary_at_media_rule_modifier, 664 | predefined_modifier, 665 | supports_arbitrary, 666 | aria_or_data_arbitrary, 667 | data_arbitrary, 668 | min_max_arbitrary_modifier, 669 | wildcard_modifier, 670 | ))(input) 671 | } 672 | 673 | fn modifiers_chained(input: &str) -> IResult<&str, ()> { 674 | let (input, _modifiers) = separated_list0(tag(":"), modifier)(input)?; 675 | Ok((input, ())) 676 | } 677 | 678 | fn parse_tw_full_classname(input: &str) -> IResult<&str, Vec<&str>> { 679 | let (input, _class_names) = tuple(( 680 | opt(tuple((modifiers_chained, tag(":")))), 681 | parse_single_tw_classname, 682 | ))(input)?; 683 | 684 | Ok((input, vec![])) 685 | } 686 | 687 | // Edge cases 688 | // [&:nth-child(3)]:underline 689 | // lg:[&:nth-child(3)]:hover:underline 690 | // [&_p]:mt-4 691 | // flex [@supports(display:grid)]:grid 692 | // [@media(any-hover:hover){&:hover}]:opacity-100 693 | // group/edit invisible hover:bg-slate-200 group-hover/item:visible 694 | // hidden group-[.is-published]:block 695 | // group-[:nth-of-type(3)_&]:block 696 | // peer-checked/published:text-sky-500 697 | // peer-[.is-dirty]:peer-required:block hidden 698 | // hidden peer-[:nth-of-type(3)_&]:block 699 | // after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700 700 | // before:content-[''] before:block 701 | // bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur 702 | // aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')] 703 | // group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180 704 | // data-[size=large]:p-8 705 | // open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg 706 | // lg:[&:nth-child(3)]:hover:underline 707 | // min-[320px]:text-center max-[600px]:bg-sky-300 708 | // top-[117px] lg:top-[344px] 709 | // bg-[#bada55] text-[22px] before:content-['Festivus'] 710 | // grid grid-cols-[fit-content(theme(spacing.32))] 711 | // bg-[--my-color] 712 | // [mask-type:luminance] hover:[mask-type:alpha] 713 | // [--scroll-offset:56px] lg:[--scroll-offset:44px] 714 | // lg:[&:nth-child(3)]:hover:underline 715 | // bg-[url('/what_a_rush.png')] 716 | // before:content-['hello\_world'] 717 | // text-[22px] 718 | // text-[#bada55] 719 | // text-[var(--my-var)] 720 | // text-[length:var(--my-var)] 721 | // text-[color:var(--my-var)] 722 | fn parse_class_names(input: &str) -> IResult<&str, Vec<&str>> { 723 | let (input, _) = multispace0(input)?; 724 | let (input, _class_names) = separated_list0(multispace1, parse_tw_full_classname)(input)?; 725 | let (input, _) = multispace0(input)?; 726 | 727 | Ok((input, vec![])) 728 | } 729 | 730 | fn parse_top(input: &str) -> IResult<&str, Vec<&str>> { 731 | all_consuming(parse_class_names)(input) 732 | } 733 | 734 | fn parse_single_top(input: &str) -> IResult<&str, Vec<&str>> { 735 | all_consuming(parse_tw_full_classname)(input) 736 | } 737 | 738 | #[proc_macro] 739 | pub fn twust_many_classes(raw_input: TokenStream) -> TokenStream { 740 | let r_input = raw_input.clone(); 741 | let input_original = parse_macro_input!(r_input as LitStr); 742 | let (_modifiers, _valid_class_names) = match setup(&input_original) { 743 | Ok(value) => value, 744 | Err(value) => { 745 | return syn::Error::new_spanned(input_original, value) 746 | .to_compile_error() 747 | .into() 748 | } 749 | }; 750 | let full_classnames = input_original.value(); 751 | 752 | let (_input, _class_names) = match parse_top(&full_classnames) { 753 | Ok(value) => value, 754 | Err(value) => { 755 | return syn::Error::new_spanned(input_original, value) 756 | .to_compile_error() 757 | .into() 758 | } 759 | }; 760 | 761 | quote::quote! { 762 | #input_original 763 | } 764 | .into() 765 | } 766 | 767 | #[proc_macro] 768 | pub fn twust_one_class(raw_input: TokenStream) -> TokenStream { 769 | let r_input = raw_input.clone(); 770 | let input_original = parse_macro_input!(r_input as LitStr); 771 | let (_modifiers, _valid_class_names) = match setup(&input_original) { 772 | Ok(value) => value, 773 | Err(value) => { 774 | return syn::Error::new_spanned(input_original, value) 775 | .to_compile_error() 776 | .into() 777 | } 778 | }; 779 | let full_classnames = input_original.value(); 780 | 781 | let (_input, _class_names) = match parse_single_top(&full_classnames) { 782 | Ok(value) => value, 783 | Err(value) => { 784 | return syn::Error::new_spanned(input_original, value) 785 | .to_compile_error() 786 | .into() 787 | } 788 | }; 789 | 790 | quote::quote! { 791 | #input_original 792 | } 793 | .into() 794 | } 795 | 796 | // // Requires featues = full. Dont need it, can just use macrorules 797 | // #[proc_macro] 798 | // pub fn tws(raw_input: TokenStream) -> TokenStream { 799 | // let input = parse_macro_input!(raw_input as Expr); 800 | // 801 | // let mut all_classnames = Vec::new(); 802 | // 803 | // match input { 804 | // Expr::Array(array) => { 805 | // for expr in array.elems.iter() { 806 | // if let Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit_str), .. }) = expr { 807 | // all_classnames.push(lit_str.value()); 808 | // } else { 809 | // return syn::Error::new_spanned(expr, "Expected string literals in the array") 810 | // .to_compile_error() 811 | // .into(); 812 | // } 813 | // } 814 | // } 815 | // Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit_str), .. }) => { 816 | // all_classnames.push(lit_str.value()); 817 | // } 818 | // _ => { 819 | // return syn::Error::new_spanned(input, "Expected a string literal or an array of string literals") 820 | // .to_compile_error() 821 | // .into(); 822 | // } 823 | // } 824 | // 825 | // let concatenated = all_classnames.join(" "); 826 | // 827 | // quote::quote! { 828 | // #concatenated 829 | // } 830 | // .into() 831 | // } 832 | -------------------------------------------------------------------------------- /twust_macro/src/plugins/daisyui.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "daisyui")] 2 | pub fn get_daisy_v3_classes() -> Vec<&'static str> { 3 | DAISY_UI_CLASSES.to_vec() 4 | } 5 | 6 | #[cfg(not(feature = "daisyui"))] 7 | pub fn get_daisy_v3_classes() -> Vec<&'static str> { 8 | Vec::new() 9 | } 10 | 11 | #[cfg(feature = "daisyui")] 12 | pub const DAISY_UI_CLASSES: [&str; 420] = [ 13 | // Collapse 14 | "collapse", 15 | "collapse-title", 16 | "collapse-content", 17 | "collapse-arrow", 18 | "collapse-plus", 19 | "collapse-open", 20 | "collapse-close", 21 | // Alert 22 | "alert", 23 | "alert-info", 24 | "alert-success", 25 | "alert-warning", 26 | "alert-error", 27 | //artboard 28 | "artboard", 29 | "artboard-demo", 30 | "artboard-horizontal", 31 | // phones 32 | "phone-1", 33 | "phone-2", 34 | "phone-3", 35 | "phone-4", 36 | "phone-5", 37 | "phone-6", 38 | // Avatar 39 | "avatar", 40 | "avatar-group", 41 | "online", 42 | "offline", 43 | "placeholder", 44 | // Badge 45 | "badge", 46 | "badge-neutral", 47 | "badge-primary", 48 | "badge-secondary", 49 | "badge-accent", 50 | "badge-ghost", 51 | "badge-info", 52 | "badge-success", 53 | "badge-warning", 54 | "badge-error", 55 | "badge-outline", 56 | "badge-lg", 57 | "badge-md", 58 | "badge-sm", 59 | "badge-xs", 60 | // Bottom Navigation 61 | "btm-nav", 62 | "active", 63 | "disabled", 64 | "btm-nav-xs", 65 | "btm-nav-sm", 66 | "btm-nav-md", 67 | "btm-nav-lg", 68 | "breadcrumbs", 69 | // Button 70 | "btn", 71 | "btn-neutral", 72 | "btn-primary", 73 | "btn-secondary", 74 | "btn-accent", 75 | "btn-info", 76 | "btn-success", 77 | "btn-warning", 78 | "btn-error", 79 | "btn-ghost", 80 | "btn-link", 81 | "btn-outline", 82 | "btn-active", 83 | "btn-disabled", 84 | "glass", 85 | "no-animation", 86 | "btn-lg", 87 | "btn-md", 88 | "btn-sm", 89 | "btn-xs", 90 | "btn-wide", 91 | "btn-block", 92 | "btn-circle", 93 | "btn-square", 94 | "btn-group", 95 | "btn-group-horizontal", 96 | "btn-group-vertical", 97 | // Card 98 | "card", 99 | "card-title", 100 | "card-body", 101 | "card-actions", 102 | "card-bordered", 103 | "image-full", 104 | "card-normal", 105 | "card-compact", 106 | "card-side", 107 | // Carousel 108 | "carousel", 109 | "carousel-item", 110 | "carousel-center", 111 | "carousel-end", 112 | "carousel-vertical", 113 | // Chat 114 | "chat", 115 | "chat-start", 116 | "chat-end", 117 | "chat-image", 118 | "chat-header", 119 | "chat-footer", 120 | "chat-bubble", 121 | "chat-bubble-primary", 122 | "chat-bubble-secondary", 123 | "chat-bubble-accent", 124 | "chat-bubble-info", 125 | "chat-bubble-success", 126 | "chat-bubble-warning", 127 | "chat-bubble-error", 128 | // Form Control (Checkbox) 129 | "form-control", 130 | "checkbox", 131 | "checkbox-primary", 132 | "checkbox-secondary", 133 | "checkbox-accent", 134 | "checkbox-success", 135 | "checkbox-warning", 136 | "checkbox-info", 137 | "checkbox-error", 138 | "checkbox-lg", 139 | "checkbox-md", 140 | "checkbox-sm", 141 | "checkbox-xs", 142 | // Collapse (repeated from above, not adding again) 143 | 144 | // Countdown 145 | "countdown", 146 | // Divider 147 | "divider", 148 | "divider-vertical", 149 | "divider-horizontal", 150 | // Drawer 151 | "drawer", 152 | "drawer-toggle", 153 | "drawer-content", 154 | "drawer-side", 155 | "drawer-overlay", 156 | "drawer-end", 157 | "drawer-open", 158 | // Dropdown 159 | "dropdown", 160 | "dropdown-content", 161 | "dropdown-end", 162 | "dropdown-top", 163 | "dropdown-bottom", 164 | "dropdown-left", 165 | "dropdown-right", 166 | "dropdown-hover", 167 | "dropdown-open", 168 | // Form Control (File Input) 169 | "label", 170 | "file-input", 171 | "file-input-bordered", 172 | "file-input-ghost", 173 | "file-input-primary", 174 | "file-input-secondary", 175 | "file-input-accent", 176 | "file-input-info", 177 | "file-input-success", 178 | "file-input-warning", 179 | "file-input-error", 180 | "file-input-lg", 181 | "file-input-md", 182 | "file-input-sm", 183 | "file-input-xs", 184 | // Footer 185 | "footer", 186 | "footer-title", 187 | "footer-center", 188 | // Hero 189 | "hero", 190 | "hero-content", 191 | "hero-overlay", 192 | // Indicator 193 | "indicator", 194 | "indicator-item", 195 | "indicator-start", 196 | "indicator-center", 197 | "indicator-end", 198 | "indicator-top", 199 | "indicator-middle", 200 | "indicator-bottom", 201 | // Form Control (Input) 202 | "input", 203 | "input-bordered", 204 | "input-ghost", 205 | "input-primary", 206 | "input-secondary", 207 | "input-accent", 208 | "input-info", 209 | "input-success", 210 | "input-warning", 211 | "input-error", 212 | "input-lg", 213 | "input-md", 214 | "input-sm", 215 | "input-xs", 216 | // Form Control (Input Group) 217 | "input-group", 218 | "input-group-lg", 219 | "input-group-md", 220 | "input-group-sm", 221 | "input-group-xs", 222 | "input-group-vertical", 223 | // Join 224 | "join", 225 | "join-item", 226 | "join-vertical", 227 | "join-horizontal", 228 | // Key 229 | "kbd", 230 | "kbd-lg", 231 | "kbd-md", 232 | "kbd-sm", 233 | "kbd-xs", 234 | // Link 235 | "link", 236 | "link-neutral", 237 | "link-primary", 238 | "link-secondary", 239 | "link-accent", 240 | "link-success", 241 | "link-info", 242 | "link-warning", 243 | "link-error", 244 | "link-hover", 245 | // Loader 246 | "loading", 247 | "loading-spinner", 248 | "loading-dots", 249 | "loading-ring", 250 | "loading-ball", 251 | "loading-bars", 252 | "loading-infinity", 253 | // Mask 254 | "mask", 255 | "mask-squircle", 256 | "mask-heart", 257 | "mask-hexagon", 258 | "mask-hexagon-2", 259 | "mask-decagon", 260 | "mask-pentagon", 261 | "mask-diamond", 262 | "mask-square", 263 | "mask-circle", 264 | "mask-parallelogram", 265 | "mask-parallelogram-2", 266 | "mask-parallelogram-3", 267 | "mask-parallelogram-4", 268 | "mask-star", 269 | "mask-star-2", 270 | "mask-triangle", 271 | "mask-triangle-2", 272 | "mask-triangle-3", 273 | "mask-triangle-4", 274 | "mask-half-1", 275 | "mask-half-2", 276 | // Menu 277 | "menu", 278 | "menu-title", 279 | "disabled", 280 | "active", 281 | "focus", 282 | "menu-dropdown-toggle", 283 | "menu-dropdown", 284 | "menu-dropdown-show", 285 | "menu-xs", 286 | "menu-sm", 287 | "menu-md", 288 | "menu-lg", 289 | "menu-vertical", 290 | "menu-horizontal", 291 | // Mockup 292 | "mockup-browser", 293 | "mockup-browser-toolbar", 294 | "mockup-code", 295 | "mockup-phone", 296 | "mockup-window", 297 | // Modal 298 | "modal", 299 | "modal-box", 300 | "modal-action", 301 | "modal-backdrop", 302 | "modal-toggle", 303 | "modal-open", 304 | "modal-top", 305 | "modal-bottom", 306 | "modal-middle", 307 | // Navbar 308 | "navbar", 309 | "navbar-start", 310 | "navbar-center", 311 | "navbar-end", 312 | "join", 313 | // Progress 314 | "progress", 315 | "progress-primary", 316 | "progress-secondary", 317 | "progress-accent", 318 | "progress-info", 319 | "progress-success", 320 | "progress-warning", 321 | "progress-error", 322 | "radial-progress", 323 | // Form Control (Radio) 324 | "form-control", 325 | // Radio 326 | "radio", 327 | "radio-primary", 328 | "radio-secondary", 329 | "radio-accent", 330 | "radio-success", 331 | "radio-warning", 332 | "radio-info", 333 | "radio-error", 334 | "radio-lg", 335 | "radio-md", 336 | "radio-sm", 337 | "radio-xs", 338 | // Range 339 | "range", 340 | "range-primary", 341 | "range-secondary", 342 | "range-accent", 343 | "range-success", 344 | "range-warning", 345 | "range-info", 346 | "range-error", 347 | "range-lg", 348 | "range-md", 349 | "range-sm", 350 | "range-xs", 351 | // Rating 352 | "rating", 353 | "rating-half", 354 | "rating-hidden", 355 | "rating-lg", 356 | "rating-md", 357 | "rating-sm", 358 | "rating-xs", 359 | "form-control", 360 | "label", 361 | // Select 362 | "select", 363 | "select-bordered", 364 | "select-ghost", 365 | "select-primary", 366 | "select-secondary", 367 | "select-accent", 368 | "select-info", 369 | "select-success", 370 | "select-warning", 371 | "select-error", 372 | "select-lg", 373 | "select-md", 374 | "select-sm", 375 | "select-xs", 376 | // Stack 377 | "stack", 378 | // Stat 379 | "stats", 380 | "stat", 381 | "stat-title", 382 | "stat-value", 383 | "stat-desc", 384 | "stat-figure", 385 | "stat-actions", 386 | "stats-horizontal", 387 | "stats-vertical", 388 | // Steps 389 | "steps", 390 | "step", 391 | "step-primary", 392 | "step-secondary", 393 | "step-accent", 394 | "step-info", 395 | "step-success", 396 | "step-warning", 397 | "step-error", 398 | "steps-vertical", 399 | "steps-horizontal", 400 | // Swap 401 | "swap", 402 | "swap-on", 403 | "swap-off", 404 | "swap-indeterminate", 405 | "swap-active", 406 | "swap-rotate", 407 | "swap-flip", 408 | // Tabs 409 | "tabs", 410 | "tabs-boxed", 411 | "tab", 412 | "tab-active", 413 | "tab-disabled", 414 | "tab-bordered", 415 | "tab-lifted", 416 | "tab-xs", 417 | "tab-sm", 418 | "tab-md", 419 | "tab-lg", 420 | // Table 421 | "table", 422 | "table-zebra", 423 | "table-pin-rows", 424 | "table-pin-cols", 425 | "table-xs", 426 | "table-sm", 427 | "table-md", 428 | "table-lg", 429 | "form-control", 430 | "label", 431 | // Textarea 432 | "textarea", 433 | "textarea-bordered", 434 | "textarea-ghost", 435 | "textarea-primary", 436 | "textarea-secondary", 437 | "textarea-accent", 438 | "textarea-info", 439 | "textarea-success", 440 | "textarea-warning", 441 | "textarea-error", 442 | "textarea-lg", 443 | "textarea-md", 444 | "textarea-sm", 445 | "textarea-xs", 446 | // Toast 447 | "toast", 448 | "toast-start", 449 | "toast-center", 450 | "toast-end", 451 | "toast-top", 452 | "toast-middle", 453 | "toast-bottom", 454 | "form-control", 455 | // Toggle 456 | "toggle", 457 | "toggle-primary", 458 | "toggle-secondary", 459 | "toggle-accent", 460 | "toggle-success", 461 | "toggle-warning", 462 | "toggle-info", 463 | "toggle-error", 464 | "toggle-lg", 465 | "toggle-md", 466 | "toggle-sm", 467 | "toggle-xs", 468 | // Tooltip 469 | "tooltip", 470 | "tooltip-open", 471 | "tooltip-top", 472 | "tooltip-bottom", 473 | "tooltip-left", 474 | "tooltip-right", 475 | "tooltip-primary", 476 | "tooltip-secondary", 477 | "tooltip-accent", 478 | "tooltip-info", 479 | "tooltip-success", 480 | "tooltip-warning", 481 | "tooltip-error", 482 | ]; 483 | -------------------------------------------------------------------------------- /twust_macro/src/plugins/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod daisyui; 2 | -------------------------------------------------------------------------------- /twust_macro/src/tailwind/colorful.rs: -------------------------------------------------------------------------------- 1 | pub const COLORFUL_BASECLASSES: [&str; 22] = [ 2 | "text", 3 | "bg", 4 | "border", 5 | "border-x", 6 | "border-y", 7 | "border-s", 8 | "border-e", 9 | "border-t", 10 | "border-r", 11 | "border-b", 12 | "border-l", 13 | "divide", 14 | "outline", 15 | "ring", 16 | "ring-offset", 17 | "shadow", 18 | "caret", 19 | "accent", 20 | "fill", 21 | "stroke", 22 | "placeholder", 23 | "decoration", 24 | ]; 25 | -------------------------------------------------------------------------------- /twust_macro/src/tailwind/lengthy.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Oyelowo Oyedayo 3 | * Email: oyelowo.oss@gmail.com 4 | * Copyright (c) 2023 Oyelowo Oyedayo 5 | * Licensed under the MIT license 6 | */ 7 | pub const LENGTHY: [&str; 50] = [ 8 | "p", 9 | "pt", 10 | "pr", 11 | "pb", 12 | "pl", 13 | "px", 14 | "py", 15 | "m", 16 | "mt", 17 | "mr", 18 | "mb", 19 | "ml", 20 | "mx", 21 | "my", 22 | "space-x", 23 | "space-y", 24 | "inset", 25 | "inset-y", 26 | "inset-x", 27 | "top", 28 | "right", 29 | "bottom", 30 | "left", 31 | "w", 32 | "h", 33 | "min-w", 34 | "max-w", 35 | "min-h", 36 | "max-h", 37 | "border", 38 | "border-t", 39 | "border-r", 40 | "border-b", 41 | "border-l", 42 | "text", 43 | "gap", 44 | "gap-x", 45 | "gap-y", 46 | "grid-cols", 47 | "grid-rows", 48 | "stroke", 49 | "ring", 50 | "translate-x", 51 | "translate-y", 52 | "scale-x", 53 | "scale-y", 54 | "rotate", 55 | "skew-x", 56 | "skew-y", 57 | "underline-offset", 58 | ]; 59 | -------------------------------------------------------------------------------- /twust_macro/src/tailwind/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Oyelowo Oyedayo 3 | * Email: oyelowo.oss@gmail.com 4 | * Copyright (c) 2023 Oyelowo Oyedayo 5 | * Licensed under the MIT license 6 | */ 7 | pub mod colorful; 8 | pub mod default_classnames; 9 | pub mod lengthy; 10 | pub mod modifiers; 11 | pub mod signable; 12 | pub mod tailwind_config; 13 | pub mod valid_baseclass_names; 14 | -------------------------------------------------------------------------------- /twust_macro/src/tailwind/modifiers.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Oyelowo Oyedayo 3 | * Email: oyelowo.oss@gmail.com 4 | * Copyright (c) 2023 Oyelowo Oyedayo 5 | * Licensed under the MIT license 6 | */ 7 | // Modifier CSS 8 | // hover &:hover 9 | // focus &:focus 10 | // focus-within &:focus-within 11 | // focus-visible &:focus-visible 12 | // active &:active 13 | // visited &:visited 14 | // target &:target 15 | // first &:first-child 16 | // last &:last-child 17 | // only &:only-child 18 | // odd &:nth-child(odd) 19 | // even &:nth-child(even) 20 | // first-of-type &:first-of-type 21 | // last-of-type &:last-of-type 22 | // only-of-type &:only-of-type 23 | // empty &:empty 24 | // disabled &:disabled 25 | // enabled &:enabled 26 | // checked &:checked 27 | // indeterminate &:indeterminate 28 | // default &:default 29 | // required &:required 30 | // valid &:valid 31 | // invalid &:invalid 32 | // in-range &:in-range 33 | // out-of-range &:out-of-range 34 | // placeholder-shown &:placeholder-shown 35 | // autofill &:autofill 36 | // read-only &:read-only 37 | // before &::before 38 | // after &::after 39 | // first-letter &::first-letter 40 | // first-line &::first-line 41 | // marker &::marker 42 | // selection &::selection 43 | // file &::file-selector-button 44 | // backdrop &::backdrop 45 | // placeholder &::placeholder 46 | // sm @media (min-width: 640px) 47 | // md @media (min-width: 768px) 48 | // lg @media (min-width: 1024px) 49 | // xl @media (min-width: 1280px) 50 | // 2xl @media (min-width: 1536px) 51 | // min-[…] @media (min-width: …) 52 | // max-sm @media not all and (min-width: 640px) 53 | // max-md @media not all and (min-width: 768px) 54 | // max-lg @media not all and (min-width: 1024px) 55 | // max-xl @media not all and (min-width: 1280px) 56 | // max-2xl @media not all and (min-width: 1536px) 57 | // max-[…] @media (max-width: …) 58 | // dark @media (prefers-color-scheme: dark) 59 | // portrait @media (orientation: portrait) 60 | // landscape @media (orientation: landscape) 61 | // motion-safe @media (prefers-reduced-motion: no-preference) 62 | // motion-reduce @media (prefers-reduced-motion: reduce) 63 | // contrast-more @media (prefers-contrast: more) 64 | // contrast-less @media (prefers-contrast: less) 65 | // print @media print 66 | // supports-[…] @supports (…) 67 | // aria-checked &[aria-checked=“true”] 68 | // aria-disabled &[aria-disabled=“true”] 69 | // aria-expanded &[aria-expanded=“true”] 70 | // aria-hidden &[aria-hidden=“true”] 71 | // aria-pressed &[aria-pressed=“true”] 72 | // aria-readonly &[aria-readonly=“true”] 73 | // aria-required &[aria-required=“true”] 74 | // aria-selected &[aria-selected=“true”] 75 | // aria-[…] &[aria-…] 76 | // data-[…] &[data-…] 77 | // rtl [dir=“rtl”] & 78 | // ltr [dir=“ltr”] & 79 | // open &[open] 80 | 81 | use crate::config::{add_classes_for_field, modifiers}; 82 | 83 | use super::tailwind_config::TailwindConfig; 84 | 85 | const MODIFIERS: [&str; 50] = [ 86 | "*", 87 | "hover", 88 | "focus", 89 | "focus-within", 90 | "focus-visible", 91 | "active", 92 | "visited", 93 | "target", 94 | "first", 95 | "last", 96 | "only", 97 | "odd", 98 | "even", 99 | "first-of-type", 100 | "last-of-type", 101 | "only-of-type", 102 | "empty", 103 | "disabled", 104 | "enabled", 105 | "checked", 106 | "indeterminate", 107 | "default", 108 | "required", 109 | "valid", 110 | "invalid", 111 | "in-range", 112 | "out-of-range", 113 | "placeholder-shown", 114 | "autofill", 115 | "read-only", 116 | "before", 117 | "after", 118 | "first-letter", 119 | "first-line", 120 | "marker", 121 | "selection", 122 | "file", 123 | "backdrop", 124 | "placeholder", 125 | "dark", 126 | "portrait", 127 | "landscape", 128 | "motion-safe", 129 | "motion-reduce", 130 | "contrast-more", 131 | "contrast-less", 132 | "print", 133 | // supports-[…] @supports (…) 134 | "ltr", 135 | "rtl", 136 | "open", 137 | // "aria-[…] ", 138 | // data-[…] &[data-…] 139 | // rtl [dir=“rtl”] & 140 | // ltr [dir=“ltr”] & 141 | // open &[open] 142 | ]; 143 | 144 | pub const ARIA_DEFAULT: [&str; 8] = [ 145 | "aria-checked", 146 | "aria-disabled", 147 | "aria-expanded", 148 | "aria-hidden", 149 | "aria-pressed", 150 | "aria-readonly", 151 | "aria-required", 152 | "aria-selected", 153 | ]; 154 | 155 | pub fn get_modifiers(config: &TailwindConfig) -> Vec { 156 | let mut modifiers = Vec::new(); 157 | modifiers.extend(MODIFIERS.iter().map(ToString::to_string)); 158 | let mut default_screens = vec![ 159 | "sm", "md", "lg", "xl", "2xl", // "min-[…]", 160 | "max-sm", "max-md", "max-lg", "max-xl", "max-2xl", 161 | // "max-[…]", 162 | ] 163 | .into_iter() 164 | .map(Into::into) 165 | .collect::>(); 166 | 167 | if let Some(ref screens) = config.theme.overrides.screens { 168 | if !screens.is_empty() { 169 | default_screens = screens.keys().map(ToString::to_string).collect(); 170 | } 171 | } 172 | 173 | if let Some(ref screens) = config.theme.extend.screens { 174 | if !screens.is_empty() { 175 | let screens = screens.keys().map(ToString::to_string).collect::>(); 176 | default_screens.extend(screens); 177 | } 178 | } 179 | 180 | let allowed_extra_modifiers = config 181 | .allowed_lists 182 | .as_ref() 183 | .and_then(|x| x.classes.to_owned()) 184 | .unwrap_or_default(); 185 | modifiers.extend(allowed_extra_modifiers); 186 | 187 | modifiers.extend(default_screens); 188 | 189 | add_classes_for_field(&modifiers::Aria, config, &mut modifiers); 190 | add_classes_for_field(&modifiers::Supports, config, &mut modifiers); 191 | add_classes_for_field(&modifiers::Data, config, &mut modifiers); 192 | 193 | modifiers.extend( 194 | modifiers 195 | .iter() 196 | .map(|x| format!("group-{x}")) 197 | .collect::>(), 198 | ); 199 | modifiers 200 | } 201 | -------------------------------------------------------------------------------- /twust_macro/src/tailwind/signable.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Oyelowo Oyedayo 3 | * Email: oyelowo.oss@gmail.com 4 | * Copyright (c) 2023 Oyelowo Oyedayo 5 | * Licensed under the MIT license 6 | */ 7 | pub const SIGNABLES: [&str; 40] = [ 8 | "m", 9 | "mt", 10 | "mr", 11 | "mb", 12 | "ml", 13 | "mx", 14 | "my", 15 | "mb", 16 | "ml", 17 | "mr", 18 | "mt", 19 | "mx", 20 | "my", 21 | "p", 22 | "pb", 23 | "pl", 24 | "pr", 25 | "pt", 26 | "px", 27 | "py", 28 | "space-x", 29 | "space-y", 30 | "translate-x", 31 | "translate-y", 32 | "inset", 33 | "inset-y", 34 | "inset-x", 35 | "top", 36 | "right", 37 | "bottom", 38 | "left", 39 | "gap", 40 | "gap-x", 41 | "gap-y", 42 | "divide-x", 43 | "divide-y", 44 | "grid-auto-rows", 45 | "grid-auto-columns", 46 | "z", 47 | "order", 48 | // "scroll-mx", 49 | // "scroll-my", 50 | // "scroll-m", 51 | ]; 52 | -------------------------------------------------------------------------------- /twust_macro/src/tailwind/tailwind_config.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Oyelowo Oyedayo 3 | * Email: oyelowo.oss@gmail.com 4 | * Copyright (c) 2023 Oyelowo Oyedayo 5 | * Licensed under the MIT license 6 | */ 7 | use serde::{Deserialize, Serialize}; 8 | use std::collections::HashMap; 9 | 10 | #[derive(Serialize, Deserialize, Default, Debug)] 11 | #[serde(rename_all = "camelCase", deny_unknown_fields)] 12 | pub struct TailwindConfig { 13 | pub theme: Theme, 14 | pub allowed_lists: Option, 15 | pub variants: Option, 16 | pub core_plugins: Option, 17 | pub plugins: Option, 18 | } 19 | 20 | #[derive(Serialize, Deserialize, Default, Debug)] 21 | #[serde(rename_all = "camelCase", deny_unknown_fields)] 22 | pub struct CorePlugins { 23 | pub screens: Option, 24 | pub colors: Option, 25 | pub spacing: Option, 26 | 27 | pub width: Option, 28 | pub height: Option, 29 | 30 | pub min_width: Option, 31 | 32 | pub min_height: Option, 33 | 34 | pub max_width: Option, 35 | 36 | pub max_height: Option, 37 | 38 | pub padding: Option, 39 | pub margin: Option, 40 | pub negative_margin: Option, 41 | 42 | pub shadows: Option, 43 | 44 | pub z_index: Option, 45 | pub opacity: Option, 46 | pub stroke: Option, 47 | 48 | pub accent_color: Option, 49 | pub accessibility: Option, 50 | 51 | pub align_content: Option, 52 | pub align_items: Option, 53 | pub align_self: Option, 54 | pub animation: Option, 55 | pub appearance: Option, 56 | pub aspect_ratio: Option, 57 | pub grayscale: Option, 58 | pub backdrop_blur: Option, 59 | pub backdrop_brightness: Option, 60 | pub backdrop_contrast: Option, 61 | pub backdrop_filter: Option, 62 | pub backdrop_grayscale: Option, 63 | pub backdrop_hue_rotate: Option, 64 | pub backdrop_invert: Option, 65 | pub backdrop_opacity: Option, 66 | pub backdrop_saturate: Option, 67 | pub backdrop_sepia: Option, 68 | pub background_attachment: Option, 69 | pub background_blend_mode: Option, 70 | pub background_clip: Option, 71 | pub background_color: Option, 72 | pub background_image: Option, 73 | pub background_opacity: Option, 74 | pub background_origin: Option, 75 | pub background_position: Option, 76 | pub background_repeat: Option, 77 | pub background_size: Option, 78 | pub blur: Option, 79 | pub border_collapse: Option, 80 | pub border_color: Option, 81 | pub border_opacity: Option, 82 | pub border_radius: Option, 83 | pub border_spacing: Option, 84 | pub border_style: Option, 85 | pub border_width: Option, 86 | pub box_decoration_break: Option, 87 | pub box_shadow: Option, 88 | pub box_shadow_color: Option, 89 | pub box_sizing: Option, 90 | pub break_after: Option, 91 | pub break_before: Option, 92 | pub break_inside: Option, 93 | pub brightness: Option, 94 | pub caption_side: Option, 95 | pub caret_color: Option, 96 | pub clear: Option, 97 | pub columns: Option, 98 | pub container: Option, 99 | pub content: Option, 100 | pub contrast: Option, 101 | pub cursor: Option, 102 | pub display: Option, 103 | pub divide_color: Option, 104 | pub divide_opacity: Option, 105 | pub divide_style: Option, 106 | pub divide_width: Option, 107 | pub drop_shadow: Option, 108 | pub fill: Option, 109 | pub filter: Option, 110 | pub flex: Option, 111 | pub flex_basis: Option, 112 | pub flex_direction: Option, 113 | pub flex_grow: Option, 114 | pub flex_shrink: Option, 115 | pub flex_wrap: Option, 116 | pub float: Option, 117 | pub font_family: Option, 118 | pub font_size: Option, 119 | pub font_smoothing: Option, 120 | pub font_style: Option, 121 | pub font_variant_numeric: Option, 122 | pub font_weight: Option, 123 | pub gap: Option, 124 | pub gradient_color_stops: Option, 125 | pub grid_auto_columns: Option, 126 | pub grid_auto_flow: Option, 127 | pub grid_auto_rows: Option, 128 | pub grid_column: Option, 129 | pub grid_column_end: Option, 130 | pub grid_column_start: Option, 131 | pub grid_row: Option, 132 | pub grid_row_end: Option, 133 | pub grid_row_start: Option, 134 | pub grid_template_columns: Option, 135 | pub grid_template_rows: Option, 136 | pub hue_rotate: Option, 137 | pub hyphens: Option, 138 | pub top: Option, 139 | pub right: Option, 140 | pub left: Option, 141 | pub bottom: Option, 142 | pub start: Option, 143 | pub end: Option, 144 | pub inset: Option, 145 | pub invert: Option, 146 | pub isolation: Option, 147 | pub justify_content: Option, 148 | pub justify_items: Option, 149 | pub justify_self: Option, 150 | pub letter_spacing: Option, 151 | pub line_clamp: Option, 152 | pub line_height: Option, 153 | pub list_style_image: Option, 154 | pub list_style_position: Option, 155 | pub list_style_type: Option, 156 | pub mix_blend_mode: Option, 157 | pub object_fit: Option, 158 | pub object_position: Option, 159 | pub order: Option, 160 | pub outline_color: Option, 161 | pub outline_offset: Option, 162 | pub outline_style: Option, 163 | pub outline_width: Option, 164 | pub overflow: Option, 165 | pub overscroll_behavior: Option, 166 | pub place_content: Option, 167 | pub place_items: Option, 168 | pub place_self: Option, 169 | pub placeholder_color: Option, 170 | pub placeholder_opacity: Option, 171 | pub pointer_events: Option, 172 | pub position: Option, 173 | pub preflight: Option, 174 | pub resize: Option, 175 | pub ring_color: Option, 176 | pub ring_offset_color: Option, 177 | pub ring_offset_width: Option, 178 | pub ring_opacity: Option, 179 | pub ring_width: Option, 180 | pub rotate: Option, 181 | pub saturate: Option, 182 | pub scale: Option, 183 | pub scroll_behavior: Option, 184 | pub scroll_margin: Option, 185 | pub scroll_padding: Option, 186 | pub scroll_snap_align: Option, 187 | pub scroll_snap_stop: Option, 188 | pub scroll_snap_type: Option, 189 | pub sepia: Option, 190 | pub skew: Option, 191 | pub space: Option, 192 | pub stroke_width: Option, 193 | pub table_layout: Option, 194 | pub text_align: Option, 195 | pub text_color: Option, 196 | pub text_decoration: Option, 197 | pub text_decoration_color: Option, 198 | pub text_decoration_style: Option, 199 | pub text_decoration_thickness: Option, 200 | pub text_indent: Option, 201 | pub text_opacity: Option, 202 | pub text_overflow: Option, 203 | pub text_transform: Option, 204 | pub text_underline_offset: Option, 205 | pub touch_action: Option, 206 | pub transform: Option, 207 | pub transform_origin: Option, 208 | 209 | pub transition_delay: Option, 210 | pub transition_duration: Option, 211 | pub transition_property: Option, 212 | pub transition_timing_function: Option, 213 | pub translate: Option, 214 | pub user_select: Option, 215 | pub vertical_align: Option, 216 | pub visibility: Option, 217 | pub whitespace: Option, 218 | pub will_change: Option, 219 | pub word_break: Option, 220 | pub screen_readers: Option, 221 | // pub extend: HashMap>, 222 | } 223 | 224 | #[derive(Serialize, Deserialize, Default, Debug)] 225 | #[serde(rename_all = "camelCase", deny_unknown_fields)] 226 | pub struct AllowedLists { 227 | pub classes: Option>, 228 | pub modifiers: Option>, 229 | } 230 | 231 | #[derive(Serialize, Deserialize, Default, Debug)] 232 | #[serde(rename_all = "camelCase", deny_unknown_fields)] 233 | pub struct Theme { 234 | #[serde(flatten)] 235 | pub overrides: CustomisableClasses, 236 | pub extend: CustomisableClasses, 237 | } 238 | 239 | // Represents a color which can either be a simple string or a nested structure. 240 | #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] 241 | #[serde(untagged)] 242 | pub enum ColorValue { 243 | Simple(String), 244 | Shades(HashMap), 245 | } 246 | 247 | // #[derive(Debug, Deserialize)] 248 | // struct Theme { 249 | // colors: Option>, 250 | // } 251 | 252 | #[derive(Debug, Serialize, Deserialize)] 253 | #[serde(untagged)] 254 | pub enum ScreenValue { 255 | Simple(String), 256 | Range(RangeBreakpoint), 257 | MultiRange(Vec), 258 | Raw(RawBreakpoint), 259 | } 260 | 261 | #[derive(Debug, Serialize, Deserialize)] 262 | pub struct RangeBreakpoint { 263 | #[serde(skip_serializing_if = "Option::is_none")] 264 | min: Option, 265 | #[serde(skip_serializing_if = "Option::is_none")] 266 | max: Option, 267 | } 268 | 269 | #[derive(Debug, Serialize, Deserialize)] 270 | pub struct RawBreakpoint { 271 | raw: String, 272 | } 273 | 274 | pub type Key = String; 275 | 276 | #[derive(Serialize, Deserialize, Default, Debug)] 277 | #[serde(rename_all = "camelCase", deny_unknown_fields)] 278 | pub struct CustomisableClasses { 279 | // pub screens: Option>, 280 | pub screens: Option>, 281 | pub colors: Option>, 282 | pub spacing: Option>, 283 | 284 | pub width: Option>, 285 | pub height: Option>, 286 | 287 | pub min_width: Option>, 288 | 289 | pub min_height: Option>, 290 | 291 | pub max_width: Option>, 292 | 293 | pub max_height: Option>, 294 | 295 | pub padding: Option>, 296 | pub margin: Option>, 297 | pub negative_margin: Option>, 298 | 299 | pub shadows: Option>, 300 | 301 | pub z_index: Option>, 302 | pub opacity: Option>, 303 | pub stroke: Option>, 304 | 305 | pub accent_color: Option>, 306 | pub accessibility: Option>, 307 | 308 | pub align_content: Option>, 309 | pub align_items: Option>, 310 | pub align_self: Option>, 311 | pub animation: Option>, 312 | pub appearance: Option>, 313 | pub aspect_ratio: Option>, 314 | pub grayscale: Option>, 315 | pub backdrop_blur: Option>, 316 | pub backdrop_brightness: Option>, 317 | pub backdrop_contrast: Option>, 318 | pub backdrop_filter: Option>, 319 | pub backdrop_grayscale: Option>, 320 | pub backdrop_hue_rotate: Option>, 321 | pub backdrop_invert: Option>, 322 | pub backdrop_opacity: Option>, 323 | pub backdrop_saturate: Option>, 324 | pub backdrop_sepia: Option>, 325 | pub background_attachment: Option>, 326 | pub background_blend_mode: Option>, 327 | pub background_clip: Option>, 328 | pub background_color: Option>, 329 | pub background_image: Option>, 330 | pub background_opacity: Option>, 331 | pub background_origin: Option>, 332 | pub background_position: Option>, 333 | pub background_repeat: Option>, 334 | pub background_size: Option>, 335 | pub blur: Option>, 336 | pub border_collapse: Option>, 337 | pub border_color: Option>, 338 | pub border_opacity: Option>, 339 | pub border_radius: Option>, 340 | pub border_spacing: Option>, 341 | pub border_style: Option>, 342 | pub border_width: Option>, 343 | pub box_decoration_break: Option>, 344 | pub box_shadow: Option>, 345 | pub box_shadow_color: Option>, 346 | pub box_sizing: Option>, 347 | pub break_after: Option>, 348 | pub break_before: Option>, 349 | pub break_inside: Option>, 350 | pub brightness: Option>, 351 | pub caption_side: Option>, 352 | pub caret_color: Option>, 353 | pub clear: Option>, 354 | pub columns: Option>, 355 | pub container: Option>, 356 | pub content: Option>, 357 | pub contrast: Option>, 358 | pub cursor: Option>, 359 | pub display: Option>, 360 | pub divide_color: Option>, 361 | pub divide_opacity: Option>, 362 | pub divide_style: Option>, 363 | pub divide_width: Option>, 364 | pub drop_shadow: Option>, 365 | pub fill: Option>, 366 | pub filter: Option>, 367 | pub flex: Option>, 368 | pub flex_basis: Option>, 369 | pub flex_direction: Option>, 370 | pub flex_grow: Option>, 371 | pub flex_shrink: Option>, 372 | pub flex_wrap: Option>, 373 | pub float: Option>, 374 | pub font_family: Option>, 375 | pub font_size: Option>, 376 | pub font_smoothing: Option>, 377 | pub font_style: Option>, 378 | pub font_variant_numeric: Option>, 379 | pub font_weight: Option>, 380 | pub gap: Option>, 381 | pub gradient_color_stops: Option>, 382 | pub grid_auto_columns: Option>, 383 | pub grid_auto_flow: Option>, 384 | pub grid_auto_rows: Option>, 385 | pub grid_column: Option>, 386 | pub grid_column_end: Option>, 387 | pub grid_column_start: Option>, 388 | pub grid_row: Option>, 389 | pub grid_row_end: Option>, 390 | pub grid_row_start: Option>, 391 | pub grid_template_columns: Option>, 392 | pub grid_template_rows: Option>, 393 | pub hue_rotate: Option>, 394 | pub hyphens: Option>, 395 | pub top: Option>, 396 | pub right: Option>, 397 | pub left: Option>, 398 | pub bottom: Option>, 399 | pub start: Option>, 400 | pub end: Option>, 401 | pub inset: Option>, 402 | pub invert: Option>, 403 | pub isolation: Option>, 404 | pub justify_content: Option>, 405 | pub justify_items: Option>, 406 | pub justify_self: Option>, 407 | pub letter_spacing: Option>, 408 | pub line_clamp: Option>, 409 | pub line_height: Option>, 410 | pub list_style_image: Option>, 411 | pub list_style_position: Option>, 412 | pub list_style_type: Option>, 413 | pub mix_blend_mode: Option>, 414 | pub object_fit: Option>, 415 | pub object_position: Option>, 416 | pub order: Option>, 417 | pub outline_color: Option>, 418 | pub outline_offset: Option>, 419 | pub outline_style: Option>, 420 | pub outline_width: Option>, 421 | pub overflow: Option>, 422 | pub overscroll_behavior: Option>, 423 | pub place_content: Option>, 424 | pub place_items: Option>, 425 | pub place_self: Option>, 426 | pub placeholder_color: Option>, 427 | pub placeholder_opacity: Option>, 428 | pub pointer_events: Option>, 429 | pub position: Option>, 430 | pub preflight: Option>, 431 | pub resize: Option>, 432 | pub ring_color: Option>, 433 | pub ring_offset_color: Option>, 434 | pub ring_offset_width: Option>, 435 | pub ring_opacity: Option>, 436 | pub ring_width: Option>, 437 | pub rotate: Option>, 438 | pub saturate: Option>, 439 | pub scale: Option>, 440 | pub scroll_behavior: Option>, 441 | pub scroll_margin: Option>, 442 | pub scroll_padding: Option>, 443 | pub scroll_snap_align: Option>, 444 | pub scroll_snap_stop: Option>, 445 | pub scroll_snap_type: Option>, 446 | pub sepia: Option>, 447 | pub skew: Option>, 448 | pub space: Option>, 449 | pub stroke_width: Option>, 450 | pub table_layout: Option>, 451 | pub text_align: Option>, 452 | pub text_color: Option>, 453 | pub text_decoration: Option>, 454 | pub text_decoration_color: Option>, 455 | pub text_decoration_style: Option>, 456 | pub text_decoration_thickness: Option>, 457 | pub text_indent: Option>, 458 | pub text_opacity: Option>, 459 | pub text_overflow: Option>, 460 | pub text_transform: Option>, 461 | pub text_underline_offset: Option>, 462 | pub touch_action: Option>, 463 | pub transform: Option>, 464 | pub transform_origin: Option>, 465 | 466 | pub transition_delay: Option>, 467 | pub transition_duration: Option>, 468 | pub transition_property: Option>, 469 | pub transition_timing_function: Option>, 470 | pub translate: Option>, 471 | pub user_select: Option>, 472 | pub vertical_align: Option>, 473 | pub visibility: Option>, 474 | pub whitespace: Option>, 475 | pub will_change: Option>, 476 | pub word_break: Option>, 477 | 478 | pub screen_readers: Option>, 479 | 480 | // Modifiers 481 | pub aria: Option>, 482 | pub supports: Option>, 483 | pub data: Option>, 484 | // pub extend: HashMap>, 485 | } 486 | 487 | // #[derive(Serialize, Deserialize, Debug)] 488 | // pub struct Extend { 489 | // pub screens: Option>, 490 | // // add other fields as needed 491 | // } 492 | 493 | #[derive(Serialize, Deserialize, Default, Debug)] 494 | pub struct Variants {} 495 | 496 | #[derive(Serialize, Deserialize, Default, Debug)] 497 | pub struct Plugins {} 498 | -------------------------------------------------------------------------------- /twust_macro/src/tailwind/valid_baseclass_names.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Oyelowo Oyedayo 3 | * Email: oyelowo.oss@gmail.com 4 | * Copyright (c) 2023 Oyelowo Oyedayo 5 | * Licensed under the MIT license 6 | */ 7 | pub const VALID_BASECLASS_NAMES: [&str; 111] = [ 8 | "stroke", 9 | "columns", 10 | "duration", 11 | "col", 12 | "backdrop-brightness", 13 | "p", 14 | "px", 15 | "py", 16 | "pt", 17 | "pr", 18 | "pb", 19 | "pl", 20 | "translate-y", 21 | "translate-x", 22 | "drop-shadow", 23 | "content", 24 | "outline", 25 | "delay", 26 | "ring-offset", 27 | "basis", 28 | "hue-rotate", 29 | "row", 30 | "origin", 31 | "bg", 32 | "ease", 33 | "animate", 34 | "grayscale", 35 | "auto-rows", 36 | "max-w", 37 | "rotate", 38 | "rotate", 39 | "grid-cols", 40 | "grid-rows", 41 | "blur", 42 | "max-h", 43 | "ring", 44 | "shadow", 45 | "shadow", 46 | "backdrop-grayscale", 47 | "flex", 48 | "shrink", 49 | "skew-x", 50 | "skew-y", 51 | "contrast", 52 | "backdrop-contrast", 53 | "text", 54 | "font", 55 | "list", 56 | "bg", 57 | "order", 58 | "border-t", 59 | "space-x", 60 | "space-y", 61 | "stroke", 62 | "w", 63 | "divide", 64 | "sepia", 65 | "transition", 66 | "backdrop-sepia", 67 | "bg", 68 | "backdrop-blur", 69 | "list-image", 70 | "outline-offset", 71 | "scale", 72 | "auto-cols", 73 | "backdrop-opacity", 74 | "leading", 75 | "saturate", 76 | "invert", 77 | "opacity", 78 | "align", 79 | "outline", 80 | "decoration", 81 | "border-spacing", 82 | "font", 83 | "indent", 84 | "ring-offset", 85 | "decoration", 86 | "z", 87 | "will-change", 88 | "min-h", 89 | "grid-rows", 90 | "ring", 91 | "text", 92 | "m", 93 | "mx", 94 | "my", 95 | "mt", 96 | "mr", 97 | "mb", 98 | "ml", 99 | "bg", 100 | "tracking", 101 | "underline-offset", 102 | "h", 103 | "scroll-m", 104 | "scroll-p", 105 | "backdrop-invert", 106 | "min-w", 107 | "rounded", 108 | "grow", 109 | "border", 110 | "line-clamp", 111 | "divide-x", 112 | "brightness", 113 | "gap", 114 | "top", 115 | "fill", 116 | "from", 117 | "backdrop-saturate", 118 | "backdrop-hue-rotate", 119 | ]; 120 | --------------------------------------------------------------------------------