├── .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 |
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