├── .circleci └── config.yml ├── .github └── FUNDING.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example └── reagent-demo │ ├── .gitignore │ ├── README.md │ ├── deps.edn │ ├── package-lock.json │ ├── package.json │ ├── public │ └── index.html │ ├── shadow-cljs.edn │ └── src │ └── acme │ └── frontend │ ├── annotated.cljc │ ├── app.cljc │ ├── comprehensive.cljc │ ├── my_css.cljc │ └── my_grammar.cljc └── lib ├── girouette ├── .gitignore ├── bin │ └── kaocha ├── deps.edn ├── package-lock.json ├── package.json ├── pom.xml ├── src │ └── girouette │ │ ├── core.cljc │ │ ├── garden │ │ └── util.cljc │ │ ├── grammar │ │ └── hiccup_tag.cljc │ │ ├── tw │ │ ├── accessibility.cljc │ │ ├── animation.cljc │ │ ├── background.cljc │ │ ├── border.cljc │ │ ├── box_alignment.cljc │ │ ├── color.cljc │ │ ├── common.cljc │ │ ├── core.cljc │ │ ├── default_api.cljc │ │ ├── effect.cljc │ │ ├── filter.cljc │ │ ├── flexbox.cljc │ │ ├── grid.cljc │ │ ├── interactivity.cljc │ │ ├── layout.cljc │ │ ├── preflight.cljc │ │ ├── sizing.cljc │ │ ├── spacing.cljc │ │ ├── svg.cljc │ │ ├── table.cljc │ │ ├── transform.cljc │ │ └── typography.cljc │ │ ├── util.cljc │ │ └── version.cljc ├── test │ └── girouette │ │ ├── garden │ │ └── util_test.cljc │ │ ├── grammar │ │ └── hiccup_tag_test.cljc │ │ ├── tw │ │ ├── accessibility_test.cljc │ │ ├── animation_test.cljc │ │ ├── background_test.cljc │ │ ├── border_test.cljc │ │ ├── box_alignment_test.cljc │ │ ├── color_test.cljc │ │ ├── common_test.cljc │ │ ├── core_test.cljc │ │ ├── effect_test.cljc │ │ ├── filter_test.cljc │ │ ├── flexbox_test.cljc │ │ ├── grid_test.cljc │ │ ├── interactivity_test.cljc │ │ ├── layout_test.cljc │ │ ├── sizing_test.cljc │ │ ├── spacing_test.cljc │ │ ├── svg_test.cljc │ │ ├── table_test.cljc │ │ ├── transform_test.cljc │ │ └── typography_test.cljc │ │ └── version_test.cljc └── tests.edn └── processor ├── .gitignore ├── deps.edn ├── pom.xml └── src └── girouette ├── processor.clj └── processor └── env.clj /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/clojure:tools-deps-1.10.1.763-node 6 | working_directory: ~/girouette/lib/girouette 7 | steps: 8 | - checkout: 9 | path: ~/girouette 10 | - restore_cache: 11 | keys: 12 | - 'clj-v1-{{ checksum "deps.edn" }}-{{ checksum "package-lock.json" }}' 13 | - 'clj-v1' 14 | - run: npm ci 15 | - run: mkdir -p test-results 16 | - run: bin/kaocha --plugin kaocha.plugin/junit-xml --junit-xml-file test-results/kaocha/results.xml 17 | - store_test_results: 18 | path: test-results 19 | - save_cache: 20 | key: 'clj-v1-{{checksum "deps.edn"}}-{{ checksum "package-lock.json" }}' 21 | paths: 22 | - ~/.m2 23 | - ~/.cljs/.aot_cache 24 | - ~/node_modules 25 | - ~/.gitlibs 26 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: vincentcantin # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Unreleased 2 | 3 | ## Fixed 4 | 5 | - "Text decoration" now uses the property "text-decoration-line" instead of "text-decoration". 6 | 7 | ## Added 8 | 9 | - Class "overline". 10 | 11 | ## v0.0.10 12 | 13 | ## Fixed 14 | 15 | - (PR #93) Fixed an inversion left-right in the `divide-x-..` classes. Thanks to `andrei-zhidkov` for the fix. 16 | - (PR #94) Fixes the format of the CSS colors in `box-shadow`. Thanks to `zolazhou` for the fix. 17 | 18 | ## v0.0.9 19 | 20 | ## Fixed 21 | 22 | - (PR #92) Fixed the CSS Selectors broken in `v0.0.8`. Thanks to `zolazhou`. 23 | 24 | ## v0.0.8 25 | 26 | ## Added 27 | 28 | - (PR #83) Compatibility with Tailwind `v3.0.23`, except for the Tailwind-styled "arbitrary values" specified between `[` and `]`. 29 | Thanks to `jamesnvc` for this large contribution. 30 | - Added Tailwind v3's "colors" and "extended colors". 31 | - Added fields `:since-version` and `:removed-in-version` on Girouette components. 32 | - Added a function to filter components based on a version value. Users can 33 | select components for the version they want, e.g. `[:tw 2]` or `[:tw 3]`, 34 | making Girouette (hope)fully backward compatible. 35 | - (Issue #91) Added a more recent v3.0.24 version of Preflight (v3.0.24), selectable via the settings of the Girouette processor. 36 | 37 | ## Fixed 38 | 39 | - (PR #85) Implemented `--gi-divide-*-reverse`. 40 | Thanks to `flyingmachine` for the bug report and fix. 41 | - Cljdoc is analyzing Girouette correctly again. Thanks to `ribelo` and `lread` for their help. 42 | 43 | ## Changed 44 | 45 | - (PR #86) Swap hawk for beholder. It results in a faster response of the CSS processor tool while in the watch mode. 46 | Thanks to `dpassen` for the contribution. 47 | - Updated the example project to use the Girouette API setup compatible with Tailwind v3. 48 | 49 | ## Breaking changes 50 | 51 | Some symbols have been renamed to better reflect the multiple versions supported by Girouette: 52 | - `girouette.tw.color/default-color-map` -> `girouette.tw.color/tw-v2-colors` 53 | - `girouette.tw.default-api/default-components` -> `girouette.tw.default-api/all-tw-components` 54 | - `girouette.tw.default-api/class-name->garden` -> `girouette.tw.default-api/tw-v2-class-name->garden` 55 | - `girouette.tw.typography/default-font-family-map` -> `girouette.tw.typography/tw-v2-font-family-map` 56 | - `girouette.tw.preflight/preflight` -> `girouette.tw.preflight/preflight-v2_0_3` 57 | 58 | The processor's params have changed (see the [example project](https://github.com/green-coder/girouette/blob/fd0f7cbb017ea5a989c5ce01149c67896aaca977/example/reagent-demo/deps.edn)): 59 | - `garden-fn` is no longer optional, you need to provide a qualified symbol. 60 | - `preflight?` was replaced by `base-css-rules`, it takes a vector of qualified symbols. 61 | 62 | ## v0.0.7 63 | 64 | ### Fixed 65 | 66 | Thanks to `jamesnvc` for the following fixes: 67 | - (PR #76) Escape octothorpe character in class name. 68 | - (PR #77) Fix `.invisible` class. 69 | - (PR #80) Fix the rule `:max-width`. 70 | - Fix bug in the processor tool when parsing CLJS files containing character literals. 71 | 72 | - Fixed and simplified the function `girouette.util/rule-comparator`. 73 | 74 | ## v0.0.6 75 | 76 | ### Changed 77 | 78 | - (PR #74) Changed the garden data output for components like `space-x-2` to make it easier to be processed by libraries like Ornament. 79 | The change won't affect most end users as the new garden data has the same effect, CSS-wise. 80 | Big thank to `Vynlar` for this cross-project contribution. 81 | 82 | ## v0.0.5 83 | 84 | ### Added 85 | 86 | - `girouette.garden.util/rule-comparator` can be used for ordering the garden rules which are output by `class-name->garden`. 87 | Related to issue #71 and PR #66. 88 | 89 | ### Fixed 90 | 91 | - (issue #72) Added missing cases for the `max-width` rule. Thanks to `joe-loco` for the bug report. 92 | - (issue #71) The media queries are now coming after the non-media queries in the style file. Thanks to `joe-loco` for the bug report. 93 | - (PR #73) Fixed the line height on text-5xl ~ text-9xl. Thanks to `jeremS` for the PR. 94 | 95 | ## v0.0.4 96 | 97 | ### Added 98 | 99 | - SCSS's @apply equivalent (issue #35 and PR #64) 100 | - Partial ordering between different rules (commit e0e9ab34d2bdefb7b7289ab15e06a3e243dc230c) 101 | - The return value of `class-name->garden` now has some metadata: 102 | - It's useful during the development of the Girouette components. 103 | - The metadata also contains the information about how the garden rules should be ordered in a CSS file. 104 | - Added `:dry-run?` flag in the inputs of the processor. 105 | 106 | ### Fixed 107 | 108 | - Fixed the state variant "first", "last", "odd" and "even" (PR #62) 109 | - Fixed a typo in Preflight (PR #63) 110 | - Fixed typo in :font-variant-numeric rule (PR #68) 111 | - Fixed typo in :divide-width rule 112 | - Fixed minor bugs in the processor (issues #60 and #61) 113 | 114 | ## v0.0.3 115 | 116 | ### Added 117 | 118 | - Added support for HSL colors. 119 | - Made the colors easy to customize. Updated the demo. 120 | - Made the font-family easy to customize. 121 | - Extended the rule `:line-height` to work with any number. 122 | - `make-api` does no longer need to have specified colors or font families to work. 123 | 124 | ### Fixed 125 | 126 | - (issue #55) The "display" rule is now working correctly when used with a variant like "sm:". 127 | Thanks to @jacobobryant for reporting it. 128 | 129 | ## v0.0.2 130 | 131 | ### Added 132 | 133 | - Preflight CSS from Tailwind was converted into Garden, to be used as a base by the processor tool. 134 | - "font-size-..." Girouette component. 135 | - "line-height-..." Girouette component. 136 | 137 | ### Changed 138 | 139 | - Source code clean up for Cljdoc. 140 | 141 | ### Fixed 142 | 143 | - "min-width" and "max-width" were not implemented correctly. 144 | - "font-weight" was not implemented correctly. 145 | 146 | ## v0.0.1 147 | 148 | Initial release 149 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Vincent Cantin and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to use, 6 | copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Girouette [![CircleCI](https://circleci.com/gh/green-coder/girouette.svg?style=svg)](https://circleci.com/gh/green-coder/girouette) 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/girouette.svg)](https://clojars.org/girouette) 4 | [![Cljdoc badge](https://cljdoc.org/badge/girouette/girouette)](https://cljdoc.org/d/girouette/girouette/CURRENT) 5 | [![Project chat](https://img.shields.io/badge/slack-join_chat-brightgreen.svg)](https://clojurians.slack.com/archives/C01J8H2VD97) 6 | [![Clojars download_badge](https://img.shields.io/clojars/dt/girouette?color=opal)](https://clojars.org/girouette) 7 | 8 | 9 | [![Une girouette](https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/Girouette_Bateau_Yeu.jpg/360px-Girouette_Bateau_Yeu.jpg)](https://commons.wikimedia.org/wiki/File:Girouette_Bateau_Yeu.jpg) 10 | 11 | > Dès que le vent soufflera, je repartira.
12 | > Dès que les vents tourneront, nous nous en allerons. 13 | 14 | Girouette is a grammar-based, generative approach to CSS. 15 | It translates a classname into a Garden data structure representing CSS. 16 | 17 | ```clojure 18 | (class-name->garden "w-10") 19 | ; => [".w-10" {:width "2.5rem"}] 20 | ``` 21 | 22 | Girouette also makes it easy to use your own grammar rules to generate anything 23 | you may dream of. 24 | 25 | ## Introduction 26 | 27 | CSS libraries like Tailwind and Tachyons provide quantities of predefined class names, 28 | hoping to cover most of the needs of their users. But because of combinatory explosion, 29 | they cannot provide all the class names users will ever need, in which case the users will 30 | have to hack their way with config files and/or handwritten CSS. 31 | When releasing for production, the unused CSS classes are removed from the CSS files using 32 | tools like PurgeCSS. 33 | 34 | Girouette is taking the opposite approach where we first look at which class names are used, 35 | and from their names and a grammar associated with some generation rules, the corresponding 36 | CSS content is created. 37 | 38 | This "on demand" generative approach allows to have any combination of parameters in a CSS 39 | class name, while opening the door to the most creative grammars which a human would want 40 | to use to communicate its intent. 41 | 42 | (*UPDATE:* a couple of months after Girouette was released, the author of Tailwind implemented 43 | in its version 2 and 3 an "on demand" feature very similar to Girouette) 44 | 45 | ### Documentation & Resources 46 | 47 | Girouette currently has components which makes it compatible (with a few caveats) with: 48 | - [Tailwind v2.0.2](https://v2.tailwindcss.com/docs) 49 | - [Tailwind v3.0.23](https://tailwindcss.com/docs) 50 | 51 | Presentation @ the Bay Area Clojure Meetup: 52 | - [The slides](https://app.pitch.com/app/presentation/a760be33-4a5b-4e73-bd25-07387cd197dc/7282e9fa-8789-43bc-8b2d-eaec38711d98) 53 | - [Video recording](https://www.youtube.com/watch?v=Tnv6SvZM6tg) 54 | 55 | The project has example projects in `example/`: 56 | - A simple [demo project using Reagent](example/reagent-demo). 57 | 58 | ## How it works 59 | 60 | `Girouette` is using the awesome [`Instaparse`](https://github.com/Engelberg/instaparse) 61 | library for parsing the class names, and is converting them into the 62 | [`Garden`](https://github.com/noprompt/garden) format. 63 | 64 | Its API mainly consists in the function `class-name->garden` which is pretty explicit. 65 | 66 | ```clojure 67 | (class-name->garden "w-42%") 68 | ;=> [".w-42\\%" {:width "42%"}] 69 | ``` 70 | 71 | You can use `Girouette processor tool` to extract the CSS class names from 72 | your source code and generate the CSS in real time as you develop. 73 | 74 | See the [demo project](example/reagent-demo) for more information. 75 | 76 | 77 | ## Advantages of this approach 78 | 79 | With the right Girouette components in place, any parameters can be combined 80 | in class names without leaving your usual REPL workflow. 81 | 82 | ### Large range on numbers 83 | 84 | No need to stop what you are doing and to modify some config files just because 85 | `mx-13` is not supported by default while `mx-12` is. 86 | 87 | Any color can be represented directly in class names, 88 | like `rgba-f59d` or `rgba-ff5599dd`. 89 | 90 | ### Limitless class name descriptiveness 91 | 92 | It is possible to create grammars which support very long class names. 93 | 94 | ```clojure 95 | ;; Example of class name: 96 | "bg-gradient-to-right-red-orange-yellow-green-blue-purple" 97 | 98 | ;; Instaparse rule: 99 | "bg-gradient = <'bg-gradient-to-'> gradient-direction (<'-'> color)+ 100 | gradient-direction = 'top' | 'right' | 'bottom' | 'left' | angle 101 | color = ... 102 | " 103 | ``` 104 | 105 | ## Link to other CSS projects 106 | 107 | ### In Clojure 108 | 109 | A few other alternatives are available. 110 | 111 | - [Tailwind Garden](https://github.com/wilkerlucio/tailwind-garden) 112 | - [macrocss](https://github.com/HealthSamurai/macrocss) 113 | - [tailwind-hiccup](https://github.com/rgm/tailwind-hiccup) 114 | - [tailwind-clj](https://github.com/mrmcc3/tailwind-clj) 115 | 116 | ### In JS 117 | 118 | ### Atomizer 119 | 120 | [Atomizer](https://acss.io/) is an older project which is also interpreting CSS class names. 121 | 122 | ### WindiCSS 123 | 124 | Independently and in parallel of Girouette's development, [WindiCSS](https://github.com/windicss/windicss) 125 | was developed with similar ideas. Please check it out, specially if you are developing directly in the JS environment. 126 | 127 | ## Who is using Girouette 128 | 129 | - The [Ornament](https://github.com/lambdaisland/ornament) library, by [Lambda Island / Gaiwan](https://gaiwan.co/): 130 | A very elegant way to craft and integrate CSS rules inside your Clojure(script) apps. 131 | 132 | (To add your project to this list, just edit this file and open a pull request) 133 | 134 | ## Contribute 135 | 136 | Contributions are very welcome, just make sure that the contributions are your own, 137 | and add proper credits in the commit messages if it is not the case. 138 | 139 | ## License terms 140 | 141 | This project is distributed under the `MIT License`, which is available at 142 | https://opensource.org/licenses/MIT 143 | 144 | Copyright (c) Vincent Cantin and contributors. 145 | -------------------------------------------------------------------------------- /example/reagent-demo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/js 3 | public/style/girouette.css 4 | 5 | /target 6 | /checkouts 7 | /src/gen 8 | 9 | pom.xml 10 | pom.xml.asc 11 | *.iml 12 | *.jar 13 | *.log 14 | .shadow-cljs 15 | .clj-kondo 16 | .idea 17 | .nrepl-* 18 | .DS_Store 19 | .cpcache 20 | 21 | *~ 22 | [#]*[#] 23 | .\#* 24 | -------------------------------------------------------------------------------- /example/reagent-demo/README.md: -------------------------------------------------------------------------------- 1 | # Reagent demo 2 | 3 | This demonstrates one of the way Girouette can be used in a 4 | Single Page Application (SPA) using Reagent. 5 | 6 | The same process should work with many other front end frameworks. 7 | 8 | ## How it works 9 | 10 | We use the macro `girouette.core/css` to annotate the CSS class names in the source code 11 | and differentiate them from any other text the source code might contain. 12 | 13 | The Girouette Processor tool is then parsing the source code in the background, finds the 14 | CSS class names, and generates the content of `public/style/girouette.css` based on 15 | the CSS class names found. 16 | 17 | ## Launching the webapp 18 | 19 | Load modules used by Shadow-CLJS (you only need to do that once): 20 | ```shell 21 | npm i 22 | ``` 23 | 24 | Then launch the compiler in watch mode: 25 | ```shell 26 | shadow-cljs watch frontend 27 | ``` 28 | 29 | In parallel, launch Girouette's CSS processor in watch mode: 30 | ```shell 31 | clojure -X:girouette-processor 32 | ``` 33 | 34 | Browse your webapp by clicking on the link displayed by the compiler 35 | after completion of the compilation. 36 | 37 | At that point, the front end Clojurescript code and the CSS will be 38 | automatically reloaded in the browser if you change the source code. 39 | 40 | ## Alternative way to use Girouette 41 | 42 | `Girouette` can be run directly inside your frontend application, where 43 | the generation of the CSS and injection in the browser's styles would be 44 | done entirely on the front end without the need to reload the CSS. 45 | 46 | This approach does not have a demo yet, feel free to contribute and add one. 47 | -------------------------------------------------------------------------------- /example/reagent-demo/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | 3 | :deps {org.clojure/clojurescript {:mvn/version "1.11.60"} 4 | thheller/shadow-cljs {:mvn/version "2.19.5"} 5 | reagent/reagent {:mvn/version "1.1.1"} 6 | ;girouette/girouette {:local/root "../../lib/girouette"} 7 | girouette/girouette {:mvn/version "0.0.10"}} 8 | 9 | :aliases {; clojure -X:girouette-processor 10 | :girouette-processor {;:extra-deps {girouette/processor {:local/root "../../lib/processor"}} 11 | :extra-deps {girouette/processor {:mvn/version "0.0.8"}} 12 | :ns-default girouette.processor 13 | :exec-fn process 14 | :exec-args {:css {:output-file "public/style/girouette.css"} 15 | :base-css-rules [;girouette.tw.preflight/preflight-v2_0_3 16 | girouette.tw.preflight/preflight-v3_0_24 17 | acme.frontend.my-css/my-base-css-rules] 18 | :garden-fn acme.frontend.my-grammar/class-name->garden 19 | :apply-classes acme.frontend.my-css/composed-classes 20 | :watch? true 21 | #_#_:dry-run? true}} 22 | 23 | ; clojure -M:outdated --upgrade 24 | :outdated {:extra-deps {com.github.liquidz/antq {:mvn/version "1.8.847"}} 25 | :main-opts ["-m" "antq.core"]}}} 26 | -------------------------------------------------------------------------------- /example/reagent-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reagent-demo", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "shadow-cljs": "2.19.5" 7 | }, 8 | "dependencies": { 9 | "@radix-ui/react-icons": "^1.1.1", 10 | "react": "17.0.1", 11 | "react-dom": "17.0.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/reagent-demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Acme frontend 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/reagent-demo/shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | ;; shadow-cljs configuration 2 | {:deps true 3 | 4 | :dev-http 5 | {8080 "public"} 6 | 7 | :builds 8 | {:frontend 9 | {:target :browser 10 | :modules {:main {:init-fn acme.frontend.app/init}}}}} 11 | -------------------------------------------------------------------------------- /example/reagent-demo/src/acme/frontend/annotated.cljc: -------------------------------------------------------------------------------- 1 | (ns acme.frontend.annotated 2 | (:require [girouette.core :refer [css]])) 3 | 4 | ;; ----------------------------------------------- 5 | ;; Use {:retrieve :annotated} to collect those 6 | ;; ----------------------------------------------- 7 | 8 | (css "text-green-300") 9 | 10 | ;; This might be supported by the Girouette processor in a next version. 11 | #_ (css (str "text-" "red" "-300")) 12 | 13 | (defn annotated-example [] 14 | [:h1 {:class (css "flex")} 15 | [:div {:class (css "flex-10")} "hello"] 16 | [:div {:class (css "flex-20")} "the"] 17 | [:div {:class (css "flex-90/3")} "world"]]) 18 | -------------------------------------------------------------------------------- /example/reagent-demo/src/acme/frontend/app.cljc: -------------------------------------------------------------------------------- 1 | (ns acme.frontend.app 2 | (:require [reagent.dom :as rdom] 3 | ["@radix-ui/react-icons" :as Icons])) 4 | 5 | (defn simple-example [] 6 | [:main 7 | [:div.bg-white.dark:bg-gray-800.p-10 8 | [:h1.text-3xl.text-gray-600.dark:text-gray-100 "Dark Mode Test"]] 9 | 10 | ;; Demonstrates the use of arbitrary values in flex layouts 11 | [:h1.flex.space-x-2 12 | [:div.flex-1.p-4.text-center.rounded-lg.bg-red-200 "hello"] 13 | [:div.flex-2.p-4.text-center.rounded-lg.bg-green-200 "the"] 14 | [:div.p-4.text-center.rounded-lg.bg-blue-200 {:class "flex-9/3"} "world"]] 15 | 16 | ;; Demonstrates the use of a custom Girouette component which provides the CSS class "rainbow-text" 17 | [:div.rainbow-text.text-center.font-size-10vw 18 | "Everybody needs a rainbow in their life"] 19 | 20 | ;; Demonstrates the use of custom colors (e.g. cat-white, cat-orange, cat-black) 21 | [:div.flex 22 | [:div.flex-1.bg-cat-white] 23 | [:div.flex-1.bg-cat-orange] 24 | [:div.flex-1.bg-cat-black] 25 | [:div.flex-1.p-4.text-center.text-4xl.text-cat-black.bg-cat-white "Miaw!!!"] 26 | [:div.flex-1.p-4.text-center.text-4xl.text-cat-white.bg-cat-orange "Miaw!!!"] 27 | [:div.flex-1.p-4.text-center.text-4xl.text-cat-orange.bg-cat-black "Miaw!!!"]] 28 | 29 | [:div.flex.my-10 30 | ;; Demonstrates a fix on the `divide-x` class. 31 | [:div.mx-auto.p-6.bg-gray-100 32 | [:div.flex.flex-row.bg-white.divide-x-10.divide-red-400-50 33 | [:div.p-3 "item 1"] 34 | [:div.p-3 "item 2"] 35 | [:div.p-3 {:hidden true} "item 3"] 36 | [:div.p-3 "item 4"]]] 37 | 38 | ;; Demonstrates the `divide-x-reverse` class. 39 | [:div.mx-auto.p-6.bg-gray-100 40 | [:div.flex.flex-row-reverse.bg-white.divide-x-10.divide-x-reverse.divide-red-400-50 41 | [:div.p-3 "reverse item 1"] 42 | [:div.p-3 "reverse item 2"] 43 | [:div.p-3 {:hidden true} "reverse item 3"] 44 | [:div.p-3 "reverse item 4"]]]] 45 | 46 | [:div.flex.my-10 47 | ;; Demonstrates a fix on the `divide-y` class. 48 | [:div.mx-auto.p-6.bg-gray-100 49 | [:div.flex.flex-col.bg-white.divide-y-10.divide-red-400-50 50 | [:div.p-3 "item 1"] 51 | [:div.p-3 "item 2"] 52 | [:div.p-3 {:hidden true} "item 3"] 53 | [:div.p-3 "item 4"]]] 54 | 55 | ;; Demonstrates the `divide-y-reverse` class. 56 | [:div.mx-auto.p-6.bg-gray-100 57 | [:div.flex.flex-col-reverse.bg-white.divide-y-10.divide-y-reverse.divide-red-400-50 58 | [:div.p-3 "reverse item 1"] 59 | [:div.p-3 "reverse item 2"] 60 | [:div.p-3 {:hidden true} "reverse item 3"] 61 | [:div.p-3 "reverse item 4"]]]] 62 | 63 | ;; Demonstrates the shadow colors 64 | [:div.m-4.p-4.grid.grid-cols-3.gap-4.justify-items-center.text-lg.border-1.rounded-lg 65 | [:p.font-medium.text-blueGray-500.font-mono.text-center.mb-3.dark:text-blueGray-400 "shadow-cyan-500/50"] 66 | [:p.font-medium.text-blueGray-500.font-mono.text-center.mb-3.dark:text-blueGray-400 "shadow-blue-500/50"] 67 | [:p.font-medium.text-blueGray-500.font-mono.text-center.mb-3.dark:text-blueGray-400 "shadow-indigo-500/50"] 68 | [:button.py-2.px-3.mb-4.bg-cyan-500.text-white.font-semibold.rounded-md.shadow-lg.shadow-cyan-500-50.focus:outline-none "Clojure rules"] 69 | [:button.py-2.px-3.mb-4.bg-blue-500.text-white.font-semibold.rounded-md.shadow-lg.shadow-blue-500-50.focus:outline-none "Clojure rules"] 70 | [:button.py-2.px-3.mb-4.bg-indigo-500.text-white.font-semibold.rounded-md.shadow-lg.shadow-indigo-500-50.focus:outline-none "Clojure rules"]] 71 | 72 | ;; Demonstrate the use of a native component. 73 | ;; The Girouette processor does not crash with a recent version of CLJS. 74 | [:p 75 | "Displays a native JS React component:" 76 | [:> Icons/CheckIcon]]]) 77 | 78 | (defn render [] 79 | (rdom/render [simple-example] (js/document.getElementById "app"))) 80 | 81 | (defn init [] 82 | (println "(init)") 83 | (render)) 84 | 85 | (defn ^:dev/before-load stop [] 86 | (println "(stop)")) 87 | 88 | (defn ^:dev/after-load start [] 89 | (println "(start)") 90 | (render)) 91 | -------------------------------------------------------------------------------- /example/reagent-demo/src/acme/frontend/comprehensive.cljc: -------------------------------------------------------------------------------- 1 | (ns acme.frontend.comprehensive) 2 | 3 | ;; --------------------------------------------------- 4 | ;; Use {:retrieve :comprehensive} to collect those 5 | ;; (it's the default retrieval method) 6 | ;; --------------------------------------------------- 7 | 8 | (defn compact-example [] 9 | [:h1.flex 10 | [:div.flex-1 "hello"] 11 | [:div.flex-2 "the"] 12 | [:div.flex-3 "world"]]) 13 | -------------------------------------------------------------------------------- /example/reagent-demo/src/acme/frontend/my_css.cljc: -------------------------------------------------------------------------------- 1 | (ns acme.frontend.my-css) 2 | 3 | ;; This CSS rules is appended to preflight by the Girouette processor. 4 | (def my-base-css-rules 5 | [;; Make the font bigger 6 | [:html {:font-size "20px"}] 7 | 8 | ;; More CSS rules can be added here 9 | ,]) 10 | 11 | 12 | ;; This symbol is referenced in `deps.edn` 13 | (def composed-classes 14 | {"btn" ["p-2" "rounded"] 15 | "larger-btn" ["p-6" "rounded-xl"]}) 16 | -------------------------------------------------------------------------------- /example/reagent-demo/src/acme/frontend/my_grammar.cljc: -------------------------------------------------------------------------------- 1 | (ns acme.frontend.my-grammar 2 | (:require 3 | [girouette.version :as version] 4 | [girouette.tw.core :as gtw] 5 | [girouette.tw.common :as common] 6 | [girouette.tw.color :as color] 7 | [girouette.tw.layout :as layout] 8 | [girouette.tw.flexbox :as flexbox] 9 | [girouette.tw.grid :as grid] 10 | [girouette.tw.box-alignment :as box-alignment] 11 | [girouette.tw.spacing :as spacing] 12 | [girouette.tw.sizing :as sizing] 13 | [girouette.tw.typography :as typography] 14 | [girouette.tw.background :as background] 15 | [girouette.tw.border :as border] 16 | [girouette.tw.effect :as effect] 17 | ; [girouette.tw.table :as table] 18 | ; [girouette.tw.animation :as animation] 19 | ; [girouette.tw.transform :as transform] 20 | ; [girouette.tw.interactivity :as interactivity] 21 | ; [girouette.tw.svg :as svg] 22 | ; [girouette.tw.accessibility :as accessibility] 23 | ,)) 24 | 25 | 26 | (def my-custom-components 27 | [{:id :rainbow-text 28 | :rules " 29 | rainbow-text = <'rainbow-text'> 30 | " 31 | :garden (fn [_] 32 | {:background-image "linear-gradient(to left, violet, indigo, blue, green, yellow, orange, red)" 33 | :background-clip "text" 34 | ;:-webkit-background-clip "text" 35 | :color "transparent"})}]);})}]) 36 | 37 | 38 | (def my-chosen-components 39 | (-> [common/components 40 | layout/components 41 | flexbox/components 42 | grid/components 43 | box-alignment/components 44 | spacing/components 45 | sizing/components 46 | typography/components 47 | background/components 48 | border/components 49 | effect/components 50 | ;table/components 51 | ;animation/components 52 | ;transform/components 53 | ;interactivity/components 54 | ;svg/components 55 | ;accessibility/components 56 | ,] 57 | (version/filter-components-by-version [:tw 3]) 58 | (into my-custom-components))) 59 | 60 | 61 | ;; Adds colors to the existing default ones. 62 | (def my-color-map 63 | (assoc color/tw-v3-unified-colors-extended 64 | "cat-white" "eeeeee" 65 | "cat-orange" "e58c56" 66 | "cat-black" "333333")) 67 | 68 | 69 | ;; This example shows how to Girouette on a custom grammar. 70 | ;; Here, we use only a subset of the Girouette components, and we add your own. 71 | (def class-name->garden 72 | (-> my-chosen-components 73 | (gtw/make-api {:color-map my-color-map 74 | :font-family-map typography/tw-v2-font-family-map}) 75 | :class-name->garden)) 76 | -------------------------------------------------------------------------------- /lib/girouette/.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache 2 | .idea/ 3 | .nrepl-port 4 | *.jar 5 | *.iml 6 | .cljs_node_repl 7 | .clj-kondo 8 | node_modules 9 | out 10 | 11 | *~ 12 | [#]*[#] 13 | .\#* 14 | -------------------------------------------------------------------------------- /lib/girouette/bin/kaocha: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | clojure -M:test -m kaocha.runner "$@" 3 | -------------------------------------------------------------------------------- /lib/girouette/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :deps {instaparse/instaparse {:mvn/version "1.4.12"} 3 | garden/garden {:mvn/version "1.3.10"}} 4 | :aliases {:dev {:extra-deps {org.clojure/clojure {:mvn/version "1.11.1"} 5 | org.clojure/clojurescript {:mvn/version "1.11.60"}}} 6 | :test {:extra-paths ["test"] 7 | :extra-deps {lambdaisland/kaocha {:mvn/version "1.68.1059"} 8 | lambdaisland/kaocha-cljs {:mvn/version "1.0.107"} 9 | lambdaisland/kaocha-junit-xml {:mvn/version "0.0.76"} 10 | org.clojure/test.check {:mvn/version "1.1.1"}}} 11 | 12 | ; clojure -M:outdated --upgrade 13 | :outdated {:extra-deps {com.github.liquidz/antq {:mvn/version "1.8.847"}} 14 | :main-opts ["-m" "antq.core"]} 15 | 16 | :depstar {:replace-deps {com.github.seancorfield/depstar {:mvn/version "2.1.303"}} 17 | :exec-fn hf.depstar/jar 18 | :exec-args {:sync-pom true 19 | :group-id "girouette" 20 | :artifact-id "girouette" 21 | :version "0.0.10" 22 | :jar "girouette.jar"}}}} 23 | ;; Memo for deploying a new release: 24 | ;; - Change the version above, then build the jar: 25 | ;; clojure -X:depstar 26 | ;; - add a tag "v0.x.y" to the latest commit and push to repo 27 | ;; - deploy: 28 | ;; mvn deploy:deploy-file -Dfile=girouette.jar -DpomFile=pom.xml -DrepositoryId=clojars -Durl=https://clojars.org/repo/ 29 | -------------------------------------------------------------------------------- /lib/girouette/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "girouette", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "devDependencies": { 8 | "isomorphic-ws": "^4.0.1", 9 | "ws": "^7.0.1" 10 | } 11 | }, 12 | "node_modules/isomorphic-ws": { 13 | "version": "4.0.1", 14 | "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", 15 | "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", 16 | "dev": true 17 | }, 18 | "node_modules/ws": { 19 | "version": "7.4.2", 20 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", 21 | "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==", 22 | "dev": true, 23 | "engines": { 24 | "node": ">=8.3.0" 25 | } 26 | } 27 | }, 28 | "dependencies": { 29 | "isomorphic-ws": { 30 | "version": "4.0.1", 31 | "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", 32 | "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", 33 | "dev": true 34 | }, 35 | "ws": { 36 | "version": "7.4.2", 37 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", 38 | "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==", 39 | "dev": true 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/girouette/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "isomorphic-ws": "^4.0.1", 4 | "ws": "^7.0.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /lib/girouette/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | jar 5 | girouette 6 | girouette 7 | 0.0.10 8 | girouette 9 | 10 | https://github.com/green-coder/girouette 11 | scm:git:git://github.com/green-coder/girouette.git 12 | scm:git:ssh://git@github.com/green-coder/girouette.git 13 | master 14 | 15 | 16 | 17 | org.clojure 18 | clojure 19 | 1.10.3 20 | 21 | 22 | instaparse 23 | instaparse 24 | 1.4.12 25 | 26 | 27 | garden 28 | garden 29 | 1.3.10 30 | 31 | 32 | 33 | src 34 | 35 | 36 | 37 | clojars 38 | https://repo.clojars.org/ 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/core.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.core 2 | #?(:cljs (:require-macros girouette.core))) 3 | 4 | (defmacro css 5 | "This macro should be used as a way to tag the expressions where 6 | all the strings should be interpreted as css class names." 7 | [expr] 8 | expr) 9 | 10 | #_ (defmacro not-css 11 | "This macro should be used as a way to tag the expressions where 12 | none of the strings and keywords should be interpreted as css class names. 13 | 14 | It may be used inside an expression annotated by the `css` macro above." 15 | [expr] 16 | expr) 17 | 18 | #_ (defmacro hiccup 19 | "This macro should be used as a way to tag the expressions which are 20 | in the Hiccup format, hinting code processors at how to find css class names." 21 | [expr] 22 | expr) 23 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/garden/util.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.garden.util 2 | (:require [clojure.spec.alpha :as s] 3 | [clojure.walk :as walk] 4 | #?(:clj [garden.types] 5 | :cljs [garden.types :refer [CSSAtRule]]) 6 | [girouette.util :as util]) 7 | #?(:clj (:import (garden.types CSSAtRule)))) 8 | 9 | (declare merge-rules) 10 | 11 | (defn- merge-similar-rules [[rule-type x y] values] 12 | (case rule-type 13 | :simple [x (apply merge values)] 14 | :pseudo-class [x [y (apply merge values)]] 15 | :media (assoc-in x [:value :rules] (merge-rules (apply concat values))) 16 | :unknown values)) 17 | 18 | (defn- rule-info [rule] 19 | (condp s/valid? rule 20 | (s/tuple string? map?) 21 | {:ident [:simple (first rule)] 22 | :value (second rule)} 23 | 24 | (s/tuple string? (s/tuple keyword? map?)) 25 | {:ident [:pseudo-class (first rule) (first (second rule))] 26 | :value (second (second rule))} 27 | 28 | (s/and (s/keys :req-un [::identifier ::value]) 29 | #(= :media (:identifier %))) 30 | {:ident [:media (update rule :value dissoc :rules)] 31 | :value (get-in rule [:value :rules])} 32 | 33 | {:ident [:unknown rule] 34 | :value rule})) 35 | 36 | (defn merge-rules 37 | "Combine garden rules that have the same selectors." 38 | [rules] 39 | (->> rules 40 | (map rule-info) 41 | (util/group-by :ident :value) 42 | (map (fn [[ident values]] 43 | (merge-similar-rules ident values))))) 44 | 45 | (defn apply-class-rules 46 | "Returns a collection of garden rules defining `target-class-name` as an aggregation of `gi-garden-rules`. 47 | `target-class-name` is the dotted CSS class name which we want to define. 48 | `gi-garden-rules` is an ordered collection of garden rules generated by Girouette. 49 | `gi-class-names` is an ordered collection of the CSS dotted class-names defined in `gi-garden-rules`." 50 | [target-class-name gi-garden-rules gi-class-names] 51 | (->> (map (fn [rule rule-class-name] 52 | (walk/postwalk (fn [x] 53 | (if (= rule-class-name x) 54 | target-class-name 55 | x)) 56 | rule)) 57 | gi-garden-rules 58 | gi-class-names) 59 | merge-rules)) 60 | 61 | (defn rule-comparator 62 | "Compares the Garden rules provided by Girouette, 63 | so they can be ordered correctly in a style file." 64 | [rule1 rule2] 65 | (let [is-media-query1 (and (instance? CSSAtRule rule1) 66 | (= (:identifier rule1) :media)) 67 | is-media-query2 (and (instance? CSSAtRule rule2) 68 | (= (:identifier rule2) :media))] 69 | (compare [is-media-query1 (-> rule1 meta :girouette/component :ordering-level)] 70 | [is-media-query2 (-> rule2 meta :girouette/component :ordering-level)]))) 71 | 72 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/grammar/hiccup_tag.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.grammar.hiccup-tag 2 | (:require [instaparse.core :as insta])) 3 | 4 | (def hiccup-tag-grammar " 5 | hiccup-tag = html-tag (<'#'> id | (<'.'> class-name))* 6 | html-tag = segment 7 | id = segment 8 | class-name = segment 9 | = #'[^\\.#]+' 10 | ") 11 | 12 | (def hiccup-tag-parser 13 | (insta/parser hiccup-tag-grammar)) 14 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/accessibility.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.accessibility) 2 | 3 | (def components 4 | [{:id :screen-reader 5 | :since-version [:tw 2] 6 | :rules " 7 | screen-reader = 'sr-only' | 'not-sr-only' 8 | " 9 | :garden (fn [{[type] :component-data}] 10 | (case type 11 | "sr-only" {:position "absolute" 12 | :width "1px" 13 | :height "1px" 14 | :padding 0 15 | :margin "-1px" 16 | :overflow "hidden" 17 | :clip "rect(0,0,0,0)" 18 | :white-space "nowrap" 19 | :border-width 0} 20 | "not-sr-only" {:position "static" 21 | :width "auto" 22 | :height "auto" 23 | :padding 0 24 | :margin 0 25 | :overflow "visible" 26 | :clip "auto" 27 | :white-space "normal"}))}]) 28 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/animation.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.animation 2 | (:require [garden.stylesheet :as gs] 3 | [girouette.tw.common :refer [value-unit->css]])) 4 | 5 | (def components 6 | [{:id :transition-property 7 | :since-version [:tw 2] 8 | :rules " 9 | transition-property = <'transition'> (<'-'> ('none' | 'all' | 'colors' | 10 | 'opacity' | 'shadow' | 'transform'))? 11 | " 12 | :garden (fn [{[type] :component-data}] 13 | (if (= type "none") 14 | {:transition-property "none"} 15 | {:transition-property ({"all" "all" 16 | nil "background-color,border-color,color,fill,stroke,opacity,box-shadow,transform" 17 | "colors" "background-color,border-color,color,fill,stroke" 18 | "opacity" "opacity" 19 | "shadow" "box-shadow" 20 | "transform" "transform"} type) 21 | :transition-timing-function "cubic-bezier(0.4,0,0.2,1)" 22 | :transition-duration "150ms"}))} 23 | 24 | 25 | {:id :transition-duration 26 | :since-version [:tw 2] 27 | :rules " 28 | transition-duration = <'duration-'> (number | time) 29 | " 30 | :garden (fn [{[duration] :component-data}] 31 | {:transition-duration (value-unit->css duration {:zero-unit "s" 32 | :number {:unit "ms"}})})} 33 | 34 | 35 | {:id :transition-timing-function 36 | :since-version [:tw 2] 37 | :rules " 38 | transition-timing-function = <'ease-'> ('linear' | 'in' | 'out' | 'in-out') 39 | " 40 | :garden (fn [{[type] :component-data}] 41 | {:transition-timing-function ({"linear" "linear" 42 | "in" "cubic-bezier(0.4,0,1,1)" 43 | "out" "cubic-bezier(0,0,0.2,1)" 44 | "in-out" "cubic-bezier(0.4,0,0.2,1)"} type)})} 45 | 46 | 47 | {:id :transition-delay 48 | :since-version [:tw 2] 49 | :rules " 50 | transition-delay = <'delay-'> (number | time) 51 | " 52 | :garden (fn [{[duration] :component-data}] 53 | {:transition-delay (value-unit->css duration {:zero-unit "s" 54 | :number {:unit "ms"}})})} 55 | 56 | 57 | {:id :animation 58 | :since-version [:tw 2] 59 | :rules " 60 | animation = <'animate-'> ('none' | 'spin' | 'ping' | 'pulse' | 'bounce') 61 | " 62 | ;; TODO: keyframes should be expressed outside of any prefix's scope 63 | ;; It is time to introduce grammar component's dependencies 64 | :garden (fn [{[type] :component-data}] 65 | (case type 66 | "none" 67 | {:animation "none"} 68 | 69 | "spin" 70 | [{:animation "spin 1s linear infinite"} 71 | (gs/at-keyframes 72 | "spin" 73 | [:from {:transform "rotate(0)"}] 74 | [:to {:transform "rotate(360deg)"}])] 75 | 76 | "ping" 77 | [{:animation "ping 1s cubic-bezier(0,0,0.2,1) infinite"} 78 | (gs/at-keyframes 79 | "ping" 80 | ["75%" "100%" {:transform "scale(2)" 81 | :opacity 0}])] 82 | 83 | "pulse" 84 | [{:animation "pulse 2s cubic-bezier(0.4,0,0.6,1) infinite"} 85 | (gs/at-keyframes 86 | "pulse" 87 | ["0%" "100%" {:opacity 1}] 88 | ["50%" {:opacity 0.5}])] 89 | 90 | "bounce" 91 | [{:animation "bounce 1s infinite"} 92 | (gs/at-keyframes 93 | "bounce" 94 | ["0%" "100%" {:transform "translateY(-25%)" 95 | :animation-timing-function "cubic-bezier(0.8,0,1,1)"}] 96 | ["50%" {:transform "translateY(0)" 97 | :animation-timing-function "cubic-bezier(0,0,0.2,1)"}])]))}]) 98 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/background.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.background 2 | (:require [clojure.string :as str] 3 | [girouette.tw.common :refer [value-unit->css div-100 div-4 mul-100]] 4 | [girouette.tw.color :refer [as-transparent color->css]])) 5 | 6 | (def components 7 | [{:id :background-attachment 8 | :since-version [:tw 2] 9 | :rules " 10 | background-attachment = <'bg-'> ('fixed' | 'local' | 'scroll') 11 | " 12 | :garden (fn [{[attachment-type] :component-data}] 13 | {:background-attachment attachment-type})} 14 | 15 | 16 | {:id :background-clip 17 | :since-version [:tw 2] 18 | :rules " 19 | background-clip = <'bg-clip-'> ('border' | 'padding' | 'content' | 'text') 20 | " 21 | :garden (fn [{[clip-type] :component-data}] 22 | {:background-clip ({"border" "border-box" 23 | "padding" "padding-box" 24 | "content" "content-box" 25 | "text" "text"} clip-type)})} 26 | 27 | 28 | {:id :background-color 29 | :since-version [:tw 2] 30 | :rules " 31 | background-color = <'bg-'> color 32 | " 33 | :garden (fn [{[color] :component-data 34 | read-color :read-color}] 35 | (let [color (read-color color)] 36 | (if (string? color) 37 | {:background-color color} 38 | (let [[r g b a] color] 39 | (if (some? a) 40 | {:background-color (color->css color)} 41 | {:--gi-bg-opacity 1 42 | :background-color (color->css [r g b "var(--gi-bg-opacity)"])}))))) 43 | :before-rules #{:background-opacity}} 44 | 45 | 46 | {:id :background-opacity 47 | :since-version [:tw 2] 48 | :rules " 49 | background-opacity = <'bg-opacity-'> number 50 | " 51 | :garden (fn [{[value] :component-data}] 52 | {:--gi-bg-opacity (value-unit->css value {:value-fn div-100})})} 53 | 54 | 55 | {:id :background-origin 56 | :since-version [:tw 3] 57 | :rules " 58 | background-origin = <'bg-origin-'> ('border' | 'padding' | 'content') 59 | " 60 | :garden (fn [{[value] :component-data}] 61 | {:background-origin (str value "-box")})} 62 | 63 | 64 | {:id :background-position 65 | :since-version [:tw 2] 66 | :rules " 67 | background-position = <'bg-'> ('top' | 'center' | 'bottom' | 68 | 'left-top' | 'left' | 'left-bottom' | 69 | 'right-top' | 'right' | 'right-bottom') 70 | " 71 | :garden (fn [{[position] :component-data}] 72 | {:background-position (str/escape position {\- \space})})} 73 | 74 | 75 | {:id :background-repeat 76 | :since-version [:tw 2] 77 | :rules " 78 | background-repeat = <'bg-'> ('repeat' | 'no-repeat' | 'repeat-x' | 'repeat-y' | 79 | <'repeat-'> 'round' | <'repeat-'> 'space') 80 | " 81 | :garden (fn [{[repeat-type] :component-data}] 82 | {:background-repeat repeat-type})} 83 | 84 | 85 | {:id :background-size 86 | :since-version [:tw 2] 87 | :rules " 88 | background-size = <'bg-'> ('auto' | 'cover' | 'contain' | 89 | <'size-'> background-size-length <'-'> background-size-length) 90 | = auto | number | length | length-unit | fraction | percentage 91 | " 92 | :garden (fn [{data :component-data 93 | :keys [unitless-length-conversion]}] 94 | (if (= (count data) 1) 95 | {:background-size (first data)} 96 | (let [[x y] data 97 | options {:zero-unit nil 98 | :number unitless-length-conversion 99 | :fraction {:unit "%" 100 | :value-fn mul-100}}] 101 | {:background-size [[(value-unit->css x options) 102 | (value-unit->css y options)]]})))} 103 | 104 | 105 | {:id :background-image 106 | :since-version [:tw 2] 107 | :rules " 108 | background-image = <'bg-'> 'none' | 109 | <'bg-gradient-to-'> ('tr' | 't' | 'tl' | 'l' | 'r' | 'bl' | 'b' | 'br') 110 | " 111 | :garden (fn [{[data] :component-data}] 112 | {:background-image 113 | (if (= data "none") 114 | "none" 115 | (let [direction (->> data 116 | (map {\t "top" 117 | \b "bottom" 118 | \l "left" 119 | \r "right"}) 120 | (str/join " "))] 121 | (str "linear-gradient(to " direction "," 122 | "var(--gi-gradient-stops))")))})} 123 | 124 | 125 | {:id :gradient-color-from 126 | :since-version [:tw 2] 127 | :rules " 128 | gradient-color-from = <'from-'> color 129 | " 130 | :garden (fn [{[color] :component-data 131 | read-color :read-color}] 132 | (let [color (read-color color) 133 | transp-color (as-transparent color)] 134 | {:--gi-gradient-from (color->css color) 135 | :--gi-gradient-stops (str "var(--gi-gradient-from)," 136 | "var(--gi-gradient-to," (color->css transp-color) ")")})) 137 | :before-rules #{:gradient-color-via}} 138 | 139 | 140 | {:id :gradient-color-to 141 | :since-version [:tw 2] 142 | :rules " 143 | gradient-color-to = <'to-'> color 144 | " 145 | :garden (fn [{[color] :component-data 146 | read-color :read-color}] 147 | {:--gi-gradient-to (color->css (read-color color))})} 148 | 149 | 150 | {:id :gradient-color-via 151 | :since-version [:tw 2] 152 | :rules " 153 | gradient-color-via = <'via-'> color 154 | " 155 | :garden (fn [{[color] :component-data 156 | read-color :read-color}] 157 | (let [color (read-color color) 158 | transp-color (as-transparent color)] 159 | {:--gi-gradient-stops (str "var(--gi-gradient-from)," 160 | (color->css color) "," 161 | "var(--gi-gradient-to," (color->css transp-color) ")")}))}]) 162 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/box_alignment.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.box-alignment) 2 | 3 | (def components 4 | [{:id :justify-content 5 | :since-version [:tw 2] 6 | :rules " 7 | justify-content = <'justify-'> ('start' | 'end' | 'center' | 'between' | 'around' | 'evenly') 8 | " 9 | :garden (fn [{[param] :component-data}] 10 | {:justify-content ({"start" "flex-start" 11 | "end" "flex-end" 12 | "center" "center" 13 | "between" "space-between" 14 | "around" "space-around" 15 | "evenly" "space-evenly"} param)})} 16 | 17 | 18 | {:id :justify-items 19 | :since-version [:tw 2] 20 | :rules " 21 | justify-items = <'justify-items-'> ('auto' | 'start' | 'end' | 'center' | 'stretch') 22 | " 23 | :garden (fn [{[param] :component-data}] 24 | {:justify-items param})} 25 | 26 | 27 | {:id :justify-self 28 | :since-version [:tw 2] 29 | :rules " 30 | justify-self = <'justify-self-'> ('auto' | 'start' | 'end' | 'center' | 'stretch') 31 | " 32 | :garden (fn [{[param] :component-data}] 33 | {:justify-self param})} 34 | 35 | 36 | {:id :align-content 37 | :since-version [:tw 2] 38 | :rules " 39 | align-content = <'content-'> ('start' | 'end' | 'center' | 'between' | 'around' | 'evenly') 40 | " 41 | :garden (fn [{[param] :component-data}] 42 | {:align-content ({"start" "flex-start" 43 | "end" "flex-end" 44 | "center" "center" 45 | "between" "space-between" 46 | "around" "space-around" 47 | "evenly" "space-evenly"} param)})} 48 | 49 | 50 | {:id :align-items 51 | :since-version [:tw 2] 52 | :rules " 53 | align-items = <'items-'> ('start' | 'end' | 'center' | 'baseline' | 'stretch') 54 | " 55 | :garden (fn [{[param] :component-data}] 56 | {:align-items ({"start" "flex-start" 57 | "end" "flex-end" 58 | "center" "center" 59 | "baseline" "baseline" 60 | "stretch" "stretch"} param)})} 61 | 62 | 63 | {:id :align-self 64 | :since-version [:tw 2] 65 | :rules " 66 | align-self = <'self-'> ('auto' | 'start' | 'end' | 'center' | 'stretch' | 'baseline') 67 | " 68 | :garden (fn [{[param] :component-data}] 69 | {:align-self ({"auto" "auto" 70 | "start" "flex-start" 71 | "end" "flex-end" 72 | "center" "center" 73 | "stretch" "stretch" 74 | "baseline" "baseline"} param)})} 75 | 76 | 77 | {:id :place-content 78 | :since-version [:tw 2] 79 | :rules " 80 | place-content = <'place-content-'> ('start' | 'end' | 'center' | 'between' | 'around' | 'evenly' | 'stretch') 81 | " 82 | :garden (fn [{[param] :component-data}] 83 | {:place-content ({"start" "start" 84 | "end" "end" 85 | "center" "center" 86 | "between" "space-between" 87 | "around" "space-around" 88 | "evenly" "space-evenly" 89 | "stretch" "stretch"} param)})} 90 | 91 | 92 | {:id :place-items 93 | :since-version [:tw 2] 94 | :rules " 95 | place-items = <'place-items-'> ('auto' | 'start' | 'end' | 'center' | 'stretch') 96 | " 97 | :garden (fn [{[param] :component-data}] 98 | {:place-items param})} 99 | 100 | 101 | {:id :place-self 102 | :since-version [:tw 2] 103 | :rules " 104 | place-self = <'place-self-'> ('auto' | 'start' | 'end' | 'center' | 'stretch') 105 | " 106 | :garden (fn [{[param] :component-data}] 107 | {:place-self param})}]) 108 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/common.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.common 2 | (:require [clojure.string :as str] 3 | [clojure.edn :as edn] 4 | [garden.selectors] 5 | [garden.stylesheet :as gs])) 6 | 7 | ;; This Instaparse grammar matches nothing. 8 | ;; It literally means "look ahead to see 'nop', then see 'no-way'". 9 | (def matches-nothing "&'nop' 'no-way'") 10 | 11 | (defn- state-variant->str [state-variant] 12 | (cond 13 | (string? state-variant) 14 | (str ":" 15 | ({"first" "first-child" 16 | "last" "last-child" 17 | "odd" "nth-child(odd)" 18 | "even" "nth-child(even)"} state-variant state-variant)) 19 | 20 | (and (coll? state-variant) 21 | (= :attribute-state-variant (first state-variant))) 22 | (str "[" (second state-variant) "]"))) 23 | 24 | 25 | (defn- target-variant->str [target-variant] 26 | (str "::" ({"file" "file-selector-button"} target-variant target-variant))) 27 | 28 | 29 | (defn- outer-state-variants 30 | [variant] 31 | (and (coll? variant) 32 | (#{:group-state-variant :peer-state-variant} (first variant)))) 33 | 34 | (defn dot [class-name] 35 | (str "." (str/replace class-name #"[^-a-zA-Z0-9_]" 36 | (fn [c] (str "\\" c))))) 37 | 38 | 39 | (def breakpoint->pixels 40 | {"sm" "640px" 41 | "md" "768px" 42 | "lg" "1024px" 43 | "xl" "1280px" 44 | "2xl" "1536px"}) 45 | 46 | 47 | (defn div-4 [x] (/ x 4)) 48 | (defn div-100 [x] (/ x 100)) 49 | (defn mul-4 [x] (* x 4)) 50 | (defn mul-100 [x] (* x 100)) 51 | (defn mul-255 [x] (* x 255)) 52 | (defn clamp-0-255 [x] (-> x (max 0) (min 255))) 53 | (defn ratio-str [[x y]] (str x " / " y)) 54 | 55 | 56 | (defn read-number 57 | "Converts the input into a number. 58 | Accepts the formats [:integer \"1\"], [:number \"1\"] or \"1\". 59 | This function might become private at some point, do not use if possible." 60 | [data] 61 | (let [number-str (cond-> data (vector? data) second)] 62 | (-> number-str 63 | (str/escape {\_ \.}) 64 | edn/read-string))) 65 | 66 | 67 | (defn number->double-or-int 68 | "Convert numeric value to a double, or an int if the value can be converted without a loss. 69 | Useful for getting rid of ratio numbers like 5/2." 70 | [value] 71 | (if (= (double (int value)) 72 | (double value)) 73 | (int value) 74 | (double value))) 75 | 76 | 77 | (defn value-unit->css 78 | ([data] 79 | (value-unit->css data {})) 80 | ;; The options also contain :unit, :zero-unit and :value-fn, at the root and 81 | ;; can also contain an override per data-type, e.g. {:fraction {:unit ...}} 82 | ([data {:keys [signus] :as options}] 83 | (case (first data) 84 | :auto "auto" 85 | :none "none" 86 | :full "full" 87 | :min-content "min-content" 88 | :max-content "max-content" 89 | :fit-content "fit-content" 90 | (let [[data-type arg1 arg2] data 91 | [value unit] (case data-type 92 | :integer [(read-number arg1) nil] 93 | :number [(read-number arg1) nil] 94 | :length [(read-number arg1) arg2] 95 | :length-unit [1 arg1] 96 | :angle [(read-number arg1) arg2] 97 | :time [(read-number arg1) arg2] 98 | :percentage [(read-number arg1) "%"] 99 | :fraction [(/ (read-number arg1) (read-number arg2)) nil] 100 | :ratio [[(read-number arg1) (read-number arg2)] nil] 101 | :full-100% [100 "%"] 102 | :screen-100vw [100 "vw"] 103 | :screen-100vh [100 "vh"]) 104 | value-fn (get-in options [data-type :value-fn] (:value-fn options identity)) 105 | value (value-fn value) 106 | unit (get-in options [data-type :unit] (:unit options unit)) 107 | zero-unit (get-in options [data-type :zero-unit] (:zero-unit options unit)) 108 | [value unit] (if (and (number? value) 109 | (zero? value)) 110 | [0 zero-unit] 111 | [value unit]) 112 | value (cond-> value (= signus "-") (* -1))] 113 | (cond-> value 114 | (number? value) number->double-or-int 115 | (some? unit) (str unit)))))) 116 | 117 | 118 | (defn inner-state-variants-transform [rule props] 119 | (reduce (fn [rule [variant-type variant-value]] 120 | [(keyword (str "&" 121 | (case variant-type 122 | :target-variant 123 | (target-variant->str variant-value) 124 | 125 | (:plain-state-variant :attribute-state-variant) 126 | (state-variant->str variant-value)))) 127 | rule]) 128 | rule 129 | (->> props :prefixes :state-variants reverse 130 | (remove outer-state-variants)))) 131 | 132 | 133 | (defn class-name-transform [rule props] 134 | [(dot (:class-name props)) rule]) 135 | 136 | 137 | (def between-children-selector 138 | "Selects every direct child of an element except the last. 139 | Commonly used to visually style 'between' a list of elements. 140 | 141 | For example: 142 | .space-y-2 uses this selector to add space between elements" 143 | (garden.selectors.CSSSelector. "&>:not([hidden])~:not([hidden])")) 144 | 145 | 146 | (defn outer-state-variants-transform [rule props] 147 | (reduce (fn [rule state-variant] 148 | (case (first state-variant) 149 | :group-state-variant 150 | [(str ".group" (state-variant->str (second state-variant))) 151 | rule] 152 | 153 | :peer-state-variant 154 | (into [(:selector 155 | (garden.selectors/- 156 | (str ".peer" (state-variant->str (second state-variant))) 157 | (first rule)))] 158 | (rest rule)))) 159 | rule 160 | (->> props :prefixes :state-variants reverse 161 | (filter outer-state-variants)))) 162 | 163 | 164 | (defn media-queries-transform [rule props] 165 | (let [prefixes (:prefixes props) 166 | min-width (-> prefixes :media-query-min-width breakpoint->pixels) 167 | color-scheme (-> prefixes :media-query-color-scheme) 168 | reduced-motion (-> prefixes :media-query-reduced-motion {"motion-safe" "no-preference" 169 | "motion-reduce" "reduced"}) 170 | orientation (-> prefixes :media-query-orientation) 171 | media-query (cond-> {} 172 | min-width (assoc :min-width min-width) 173 | color-scheme (assoc :prefers-color-scheme color-scheme) 174 | reduced-motion (assoc :prefers-reduced-motion reduced-motion) 175 | orientation (assoc :orientation orientation))] 176 | (if (seq media-query) 177 | (gs/at-media media-query rule) 178 | rule))) 179 | 180 | 181 | (def default-pipeline 182 | {:media-queries [media-queries-transform] 183 | :outer-state-variants [outer-state-variants-transform] 184 | :class-name [class-name-transform] 185 | :inner-state-variants [inner-state-variants-transform]}) 186 | 187 | 188 | (def common-rules " 189 | prefixes = (media-query <':'>)* (state-variant <':'>)* 190 | 191 | = media-query-min-width | 192 | media-query-color-scheme | 193 | media-query-reduced-motion | 194 | media-query-orientation 195 | media-query-min-width = 'sm' | 'md' | 'lg' | 'xl' | '2xl' 196 | media-query-color-scheme = 'light' | 'dark' 197 | media-query-reduced-motion = 'motion-safe' | 'motion-reduce' 198 | media-query-orientation = 'landscape' | 'portrait' 199 | 200 | attribute-state-variant = 'open' 201 | = 'hover' | 'focus' | 'disabled' | 'active' | 202 | 'focus-within' | 'focus-visible' | 203 | 'any-link' | 'link' | 'visited' | 'target' | 204 | 'blank' | 'required' | 'optional' | 'valid' | 'invalid' | 'placeholder-shown' | 'checked' | 205 | 'read-only' | 'read-write' | 206 | 'first' | 'last' | 'odd' | 'even' | 'first-of-type' | 'last-of-type' | 207 | 'root' | 'empty' | 208 | attribute-state-variant 209 | group-state-variant = <'group-'> state-variant-value 210 | peer-state-variant = <'peer-'> state-variant-value 211 | plain-state-variant = state-variant-value 212 | target-variant = 'file' | 'before' | 'after' | 'placeholder' 213 | state-variant = group-state-variant | peer-state-variant | plain-state-variant | target-variant 214 | 215 | signus = '-' | '+' 216 | direction = 't' | 'r' | 'b' | 'l' 217 | axis = 'x' | 'y' 218 | 219 | fraction = integer <'/'> integer 220 | ratio = integer <'/'> integer 221 | 222 | full-100% = 'full' 223 | screen-100vw = 'screen' 224 | screen-100vh = 'screen' 225 | min-content = 'min' 226 | max-content = 'max' 227 | fit-content = 'fit' 228 | auto = 'auto' 229 | none = 'none' 230 | full = 'full' 231 | 232 | (* source: https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units *) 233 | integer = #'\\d+' 234 | number = #'\\d+([._]\\d+)?' 235 | percentage = number <'%'> 236 | 237 | length = number (absolute-length-unit | relative-length-unit) 238 | length-unit = absolute-length-unit | relative-length-unit 239 | = 'cm' | 'mm' | 'in' | 'pc' | 'pt' | 'px' 240 | = 'em' | 'ex' | 'ch' | 'rem' | 'lh' | 'vw' | 'vh' | 'vmin' | 'vmax' 241 | 242 | angle = number angle-unit 243 | = 'deg' | 'grad' | 'rad' | 'turn' 244 | 245 | time = number time-unit 246 | = 's' | 'ms' 247 | 248 | resolution = number resolution-unit 249 | = 'dpi' | 'dpcm' | 'dppx' | 'x' 250 | 251 | ") 252 | 253 | (def components 254 | [{:id :arbitrary-property 255 | :since-version [:tw 3] 256 | :rules " 257 | arbitrary-property = <'['> #'[^\\] ]*' <']'> 258 | " 259 | :garden (fn [{[value] :component-data}] 260 | (let [[prop val] (-> value 261 | ;; negative-lookbehind isn't supported in javascript 262 | (str/replace #"(^|[^\\])_" "$1 ") 263 | (str/replace #"\\_" "_") 264 | (str/split #":" 2))] 265 | {(keyword prop) val}))}]) 266 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/core.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.core 2 | (:require 3 | [clojure.string :as str] 4 | [clojure.set :as set] 5 | [instaparse.core :as insta] 6 | [girouette.util :as util] 7 | [girouette.tw.common :as common] 8 | [girouette.tw.color :as color] 9 | [girouette.tw.typography :as typography])) 10 | 11 | 12 | (defn- assemble-grammar [components {:keys [color-map font-family-map]}] 13 | (let [root-rule (str "css-class = prefixes (" 14 | (->> components 15 | (map (comp name :id)) 16 | (str/join " | ")) 17 | ")\n")] 18 | (->> (concat 19 | [root-rule] 20 | (map :rules components) 21 | [common/common-rules 22 | (color/color-rules color-map) 23 | (typography/font-family-rules font-family-map)]) 24 | (apply str)))) 25 | 26 | 27 | (defn- parsed-data->props [class-name parsed-data predef-props] 28 | (let [[_ 29 | [_ & prefixes] 30 | [component-id & component-data]] parsed-data 31 | {[media-query-min-width] :media-query-min-width 32 | [media-query-color-scheme] :media-query-color-scheme 33 | [media-query-reduced-motion] :media-query-reduced-motion 34 | [media-query-orientation] :media-query-orientation 35 | state-variants :state-variant} (util/group-by first second prefixes)] 36 | (assoc predef-props 37 | :class-name class-name 38 | :prefixes {:media-query-min-width media-query-min-width 39 | :media-query-color-scheme media-query-color-scheme 40 | :media-query-reduced-motion media-query-reduced-motion 41 | :media-query-orientation media-query-orientation 42 | :state-variants state-variants} 43 | :component-id component-id 44 | :component-data (vec component-data)))) 45 | 46 | 47 | (defn- pipeline->transform [pipeline] 48 | (fn [rule props] 49 | (reduce (fn [rule f] (f rule props)) 50 | rule 51 | (->> pipeline 52 | ((juxt :media-queries 53 | :outer-state-variants 54 | :class-name 55 | :inner-state-variants)) 56 | (apply concat) 57 | reverse)))) 58 | 59 | 60 | (defn- complement-before-rules-after-rules 61 | "Ensures that each :before-rules relation has a :after-rules relation, and vice-versa." 62 | [id->component] 63 | (reduce (fn [id->component [id {:keys [before-rules after-rules]}]] 64 | (let [;; Ensure the presence of all :after-rules from :before-rules 65 | id->component (reduce (fn [id->component before-id] 66 | (update-in id->component [before-id :after-rules] 67 | (fnil conj #{}) id)) 68 | id->component 69 | before-rules) 70 | ;; Ensure the presence of all :before-rules from :after-rules 71 | id->component (reduce (fn [id->component after-id] 72 | (update-in id->component [after-id :before-rules] 73 | (fnil conj #{}) id)) 74 | id->component 75 | after-rules)] 76 | id->component)) 77 | id->component 78 | id->component)) 79 | 80 | 81 | (defn- assoc-ordering-level 82 | "Sorts the components typologically and assign them an ordering level in the graph." 83 | [id->component] 84 | (loop [id->component id->component 85 | visited-ids #{} 86 | level 0 87 | candidates-ids (keys id->component)] 88 | (if (empty? candidates-ids) 89 | id->component 90 | (let [root-ids (into #{} 91 | (filter (fn [id] 92 | (empty? (set/difference (get-in id->component [id :after-rules]) 93 | visited-ids)))) 94 | candidates-ids) 95 | id->component (reduce (fn [id->component id] 96 | (assoc-in id->component [id :ordering-level] level)) 97 | id->component 98 | root-ids) 99 | visited-ids (into visited-ids root-ids) 100 | level (inc level) 101 | candidates-ids (into #{} 102 | (mapcat (fn [id] 103 | (get-in id->component [id :before-rules]))) 104 | root-ids)] 105 | (recur id->component visited-ids level candidates-ids))))) 106 | 107 | 108 | (defn make-api 109 | "Creates an API based on a collection of Girouette components." 110 | [components {:keys [color-map 111 | font-family-map 112 | unitless-length-conversion] 113 | :or {unitless-length-conversion {:unit "rem" 114 | :value-fn common/div-4}}}] 115 | (let [components (util/into-one-vector components) ;; flatten the structure 116 | flattened-color-map (color/flatten-color-map color-map) 117 | grammar (assemble-grammar components {:color-map flattened-color-map 118 | :font-family-map font-family-map}) 119 | parser (insta/parser grammar) 120 | component-by-id (-> (into {} 121 | (map (juxt :id identity)) 122 | components) 123 | complement-before-rules-after-rules 124 | assoc-ordering-level) 125 | predef-props {:read-color (partial color/read-color flattened-color-map) 126 | :font-family-map font-family-map 127 | :unitless-length-conversion unitless-length-conversion} 128 | class-name->garden (fn [class-name] 129 | (let [parsed-data (insta/parse parser class-name)] 130 | (when-not (insta/failure? parsed-data) 131 | (let [props (parsed-data->props class-name parsed-data predef-props) 132 | component (component-by-id (:component-id props)) 133 | garden-fn (:garden component) 134 | pipeline (:pipeline component common/default-pipeline) 135 | transform (pipeline->transform pipeline)] 136 | (-> (garden-fn props) 137 | (transform props) 138 | (with-meta {:girouette/props props 139 | :girouette/component component 140 | :girouette/component-by-id component-by-id}))))))] 141 | {:grammar grammar 142 | :parser parser 143 | :component-by-id component-by-id 144 | :class-name->garden class-name->garden})) 145 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/default_api.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.default-api 2 | (:require 3 | [girouette.tw.core :as gtw] 4 | [girouette.version :as version] 5 | [girouette.tw.common :as common] 6 | [girouette.tw.color :as color] 7 | [girouette.tw.layout :as layout] 8 | [girouette.tw.flexbox :as flexbox] 9 | [girouette.tw.grid :as grid] 10 | [girouette.tw.box-alignment :as box-alignment] 11 | [girouette.tw.spacing :as spacing] 12 | [girouette.tw.sizing :as sizing] 13 | [girouette.tw.typography :as typography] 14 | [girouette.tw.background :as background] 15 | [girouette.tw.border :as border] 16 | [girouette.tw.effect :as effect] 17 | [girouette.tw.filter :as filter] 18 | [girouette.tw.table :as table] 19 | [girouette.tw.animation :as animation] 20 | [girouette.tw.transform :as transform] 21 | [girouette.tw.interactivity :as interactivity] 22 | [girouette.tw.svg :as svg] 23 | [girouette.tw.accessibility :as accessibility])) 24 | 25 | (def all-tw-components 26 | [common/components 27 | layout/components 28 | flexbox/components 29 | grid/components 30 | box-alignment/components 31 | spacing/components 32 | sizing/components 33 | typography/components 34 | background/components 35 | border/components 36 | effect/components 37 | filter/components 38 | table/components 39 | animation/components 40 | transform/components 41 | interactivity/components 42 | svg/components 43 | accessibility/components]) 44 | 45 | 46 | ;; The API built using the Tailwind v2 components. 47 | (let [{:keys [parser class-name->garden]} (-> all-tw-components 48 | (version/filter-components-by-version [:tw 2]) 49 | (gtw/make-api {:color-map color/tw-v2-colors 50 | :font-family-map typography/tw-v2-font-family-map}))] 51 | (def tw-v2-parser parser) 52 | (def tw-v2-class-name->garden class-name->garden)) 53 | 54 | 55 | ;; The API built using the Tailwind v3 components. 56 | (let [{:keys [parser class-name->garden]} (-> all-tw-components 57 | (version/filter-components-by-version [:tw 3]) 58 | (gtw/make-api {:color-map color/tw-v3-unified-colors-extended 59 | :font-family-map typography/tw-v2-font-family-map 60 | ;; Customization of unitless-length conversion is possible. 61 | ;; e.g. "p-2" -> {:padding "8px"} 62 | #_#_ 63 | :unitless-length-conversion {:unit "px" 64 | :value-fn common/mul-4}}))] 65 | (def tw-v3-parser parser) 66 | (def tw-v3-class-name->garden class-name->garden)) 67 | 68 | 69 | ;; Feel free to fork the snippet above and add your own components, 70 | ;; as that's what Girouette was made for: customization. 71 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/effect.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.effect 2 | (:require [clojure.string :as str] 3 | [girouette.tw.common :refer [value-unit->css div-100]] 4 | [girouette.tw.color :refer [color->css]])) 5 | 6 | (def components 7 | [{:id :box-shadow 8 | :since-version [:tw 2] 9 | :removed-in-version [:tw 3] 10 | :rules " 11 | box-shadow = <'shadow'> (<'-'> box-shadow-value)? 12 | box-shadow-value = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'inner' | 'none' 13 | " 14 | :garden (fn [{data :component-data}] 15 | (let [{:keys [box-shadow-value]} (into {} data) 16 | shadow-params (case box-shadow-value 17 | "sm" "0 1px 2px 0 rgba(0,0,0,0.05)" 18 | nil (str "0 1px 3px 0 rgba(0,0,0,0.1)," 19 | "0 1px 2px 0 rgba(0,0,0,0.06)") 20 | "md" (str "0 4px 6px -1px rgba(0,0,0,0.1)," 21 | "0 2px 4px -1px rgba(0,0,0,0.06)") 22 | "lg" (str "0 10px 15px -3px rgba(0,0,0,0.1)," 23 | "0 4px 6px -2px rgba(0,0,0,0.05)") 24 | "xl" (str "0 20px 25px -5px rgba(0,0,0,0.1)," 25 | "0 10px 10px -5px rgba(0,0,0,0.04)") 26 | "2xl" "0 25px 50px -12px rgba(0,0,0,0.25)" 27 | "inner" "inset 0 2px 4px 0 rgba(0,0,0,0.06)" 28 | "none" "0 0 #0000")] 29 | {:--gi-shadow shadow-params 30 | :box-shadow (str "var(--gi-ring-offset-shadow,0 0 #0000)," 31 | "var(--gi-ring-shadow,0 0 #0000)," 32 | "var(--gi-shadow)")}))} 33 | 34 | {:id :box-shadow 35 | :since-version [:tw 3] 36 | :rules " 37 | box-shadow = <'shadow'> (<'-'> box-shadow-value)? 38 | box-shadow-value = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'inner' | 'none' 39 | " 40 | :garden (fn [{data :component-data}] 41 | (let [{:keys [box-shadow-value]} (into {} data) 42 | [shadows-params shadow-color] (case box-shadow-value 43 | "sm" [["0 1px 2px 0"] "rgba(0,0,0,0.05)"] 44 | nil [["0 1px 3px 0" 45 | "0 1px 2px -1px"] "rgba(0,0,0,0.1)"] 46 | "md" [["0 4px 6px -1px" 47 | "0 2px 4px -2px"] "rgba(0,0,0,0.1)"] 48 | "lg" [["0 10px 15px -3px" 49 | "0 4px 6px -4px"] "rgba(0,0,0,0.1)"] 50 | "xl" [["0 20px 25px -5px" 51 | "0 8px 10px -6px"] "rgba(0,0,0,0.1)"] 52 | "2xl" [["0 25px 50px -12px"] "rgba(0,0,0,0.25)"] 53 | "inner" [["inset 0 2px 4px 0"] "rgba(0,0,0,0.05)"] 54 | "none" [["0 0"] "#0000"])] 55 | {:--gi-shadow (->> shadows-params 56 | (map (fn [shadow-params] 57 | (str shadow-params " " shadow-color))) 58 | (str/join ",")) 59 | :--gi-shadow-colored (->> shadows-params 60 | (map (fn [shadow-params] 61 | (str shadow-params " var(--gi-shadow-color)"))) 62 | (str/join ",")) 63 | :box-shadow (str "var(--gi-ring-offset-shadow,0 0 #0000)," 64 | "var(--gi-ring-shadow,0 0 #0000)," 65 | "var(--gi-shadow)")}))} 66 | 67 | 68 | {:id :box-shadow-color 69 | :since-version [:tw 2] 70 | :rules " 71 | box-shadow-color = <'shadow-'> ('inherit' | color) 72 | " 73 | :garden (fn [{[color] :component-data 74 | read-color :read-color}] 75 | {:--gi-shadow-color (if (= color "inherit") 76 | "inherit" 77 | (color->css (read-color color))) 78 | :--gi-shadow "var(--gi-shadow-colored)"})} 79 | 80 | 81 | {:id :opacity 82 | :since-version [:tw 2] 83 | :rules " 84 | opacity = <'opacity-'> (number | percentage | fraction) 85 | " 86 | :garden (fn [{[value] :component-data}] 87 | {:opacity (value-unit->css value {:number {:value-fn div-100}})})} 88 | 89 | 90 | {:id :mix-blend-mode 91 | :since-version [:tw 3] 92 | :rules " 93 | mix-blend-mode = <'mix-blend-'> mix-blend-mode-value 94 | = 'normal' | 'multiply' | 'screen' | 'overlay' | 95 | 'darken' | 'lighten' | 'color-dodge' | 96 | 'color-burn' | 'hard-light' | 'soft-light' | 97 | 'difference' | 'exclusion' | 'hue' | 'saturation' | 98 | 'color' | 'luminosity' 99 | " 100 | :garden (fn [{[blend-mode] :component-data}] 101 | {:mix-blend-mode blend-mode})} 102 | 103 | 104 | {:id :background-blend-mode 105 | :since-version [:tw 3] 106 | :rules " 107 | background-blend-mode = <'bg-blend-'> background-blend-mode-value 108 | = 'normal' | 'multiply' | 'screen' | 'overlay' | 109 | 'darken' | 'lighten' | 'color-dodge' | 110 | 'color-burn' | 'hard-light' | 'soft-light' | 111 | 'difference' | 'exclusion' | 'hue' | 'saturation' | 112 | 'color' | 'luminosity' 113 | " 114 | :garden (fn [{[blend-mode] :component-data}] 115 | {:background-blend-mode blend-mode})}]) 116 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/filter.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.filter 2 | (:require [girouette.tw.common :refer [value-unit->css div-100 mul-100]])) 3 | 4 | (def ^:private filter-rule 5 | (str "var(--gi-blur, ) var(--gi-brightness, ) var(--gi-contrast, ) " 6 | "var(--gi-grayscale, ) var(--gi-hue-rotate, ) var(--gi-invert, ) " 7 | "var(--gi-saturate, ) var(--gi-sepia, ) var(--gi-drop-shadow, )")) 8 | 9 | (def ^:private backdrop-filter-rule 10 | (str "var(--gi-backdrop-blur) var(--gi-backdrop-brightness) var(--gi-backdrop-contrast) " 11 | "var(--gi-backdrop-grayscale) var(--gi-backdrop-hue-rotate) var(--gi-backdrop-invert) " 12 | "var(--gi-backdrop-opacity) var(--gi-backdrop-saturate) var(--gi-backdrop-sepia)")) 13 | 14 | (def components 15 | [{:id :blur 16 | :since-version [:tw 3] 17 | :rules " 18 | blur = <'blur'> (<'-'> (blur-value-fix | blur-value-number))? 19 | blur-value-fix = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'none' 20 | blur-value-number = length 21 | " 22 | :garden (fn [{value :component-data}] 23 | (let [blur (let [{:keys [blur-value-number blur-value-fix]} (into {} value)] 24 | (if (some? blur-value-number) 25 | (value-unit->css blur-value-number) 26 | ({"none" "0" 27 | "sm" "4px" 28 | nil "8px" 29 | "md" "12px" 30 | "lg" "16px" 31 | "xl" "24px" 32 | "2xl" "40px" 33 | "3xl" "64px"} blur-value-fix)))] 34 | {:--gi-blur (str "blur(" blur ")") 35 | :filter filter-rule}))} 36 | 37 | 38 | {:id :brightness 39 | :since-version [:tw 3] 40 | :rules " 41 | brightness = <'brightness-'> (number | percentage | fraction) 42 | " 43 | :garden (fn [{[value] :component-data}] 44 | {:--gi-brightness (str "brightness(" 45 | (value-unit->css value {:number {:value-fn div-100}}) 46 | ")") 47 | :filter filter-rule})} 48 | 49 | 50 | {:id :contrast 51 | :since-version [:tw 3] 52 | :rules " 53 | contrast = <'contrast-'> (number | percentage | fraction) 54 | " 55 | :garden (fn [{[value] :component-data}] 56 | {:--gi-contrast (str "contrast(" 57 | (value-unit->css value {:number {:value-fn div-100}}) 58 | ")") 59 | :filter filter-rule})} 60 | 61 | 62 | {:id :drop-shadow 63 | :since-version [:tw 3] 64 | :rules " 65 | drop-shadow = <'drop-shadow'> (<'-'> drop-shadow-value)? 66 | = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'none' 67 | " 68 | :garden (fn [{[value] :component-data}] 69 | {:--gi-drop-shadow (case value 70 | "sm" "drop-shadow(0 1px 1px rgb(0 0 0 / 0.05))" 71 | nil "drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06))" 72 | "md" "drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06))" 73 | "lg" "drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1))" 74 | "xl" "drop-shadow(0 20px 13px rgb(0 0 0 / 0.03)) drop-shadow(0 8px 5px rgb(0 0 0 / 0.08))" 75 | "2xl" "drop-shadow(0 25px 25px rgb(0 0 0 / 0.15))" 76 | "none" "drop-shadow(0 0 #0000)") 77 | :filter filter-rule})} 78 | 79 | 80 | {:id :grayscale 81 | :since-version [:tw 3] 82 | :rules " 83 | grayscale = <'grayscale'> (<'-'> (number | percentage | fraction))? 84 | " 85 | :garden (fn [{[value] :component-data}] 86 | {:--gi-grayscale (str "grayscale(" 87 | (if (nil? value) 88 | "100%" 89 | (value-unit->css value {:number {:value-fn div-100}})) 90 | ")") 91 | :filter filter-rule})} 92 | 93 | 94 | {:id :hue-rotate 95 | :since-version [:tw 3] 96 | :rules " 97 | hue-rotate = <'hue-rotate-'> (number | angle) 98 | " 99 | :garden (fn [{[value] :component-data}] 100 | {:--gi-hue-rotate (str "hue-rotate(" 101 | (value-unit->css value {:number {:unit "deg"}}) 102 | ")") 103 | :filter filter-rule})} 104 | 105 | 106 | {:id :invert 107 | :since-version [:tw 3] 108 | :rules " 109 | invert = <'invert'> (<'-'> (number | percentage | fraction))? 110 | " 111 | :garden (fn [{[value] :component-data}] 112 | {:--gi-invert (str "invert(" 113 | (if (nil? value) 114 | "100%" 115 | (value-unit->css value {:number {:value-fn div-100}})) 116 | ")") 117 | :filter filter-rule})} 118 | 119 | 120 | {:id :saturate 121 | :since-version [:tw 3] 122 | :rules " 123 | saturate = <'saturate-'> (number | percentage | fraction) 124 | " 125 | :garden (fn [{[value] :component-data}] 126 | {:--gi-saturate (str "saturate(" 127 | (value-unit->css value {:number {:value-fn div-100}}) 128 | ")") 129 | :filter filter-rule})} 130 | 131 | 132 | {:id :sepia 133 | :since-version [:tw 3] 134 | :rules " 135 | sepia = <'sepia'> (<'-'> (number | percentage | fraction))? 136 | " 137 | :garden (fn [{[value] :component-data}] 138 | {:--gi-sepia (str "sepia(" 139 | (if (nil? value) 140 | "100%" 141 | (value-unit->css value {:number {:value-fn div-100}})) 142 | ")") 143 | :filter filter-rule})} 144 | 145 | 146 | {:id :backdrop-blur 147 | :since-version [:tw 3] 148 | :rules " 149 | backdrop-blur = <'backdrop-blur'> (<'-'> (backdrop-blur-value-fix | backdrop-blur-value-number))? 150 | backdrop-blur-value-fix = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'none' 151 | backdrop-blur-value-number = length 152 | " 153 | :garden (fn [{value :component-data}] 154 | (let [blur (let [{:keys [backdrop-blur-value-number 155 | backdrop-blur-value-fix]} (into {} value)] 156 | (if (some? backdrop-blur-value-number) 157 | (value-unit->css backdrop-blur-value-number) 158 | ({"none" "0" 159 | "sm" "4px" 160 | nil "8px" 161 | "md" "12px" 162 | "lg" "16px" 163 | "xl" "24px" 164 | "2xl" "40px" 165 | "3xl" "64px"} backdrop-blur-value-fix)))] 166 | {:--gi-backdrop-blur (str "blur(" blur ")") 167 | :backdrop-filter backdrop-filter-rule}))} 168 | 169 | 170 | {:id :backdrop-brightness 171 | :since-version [:tw 3] 172 | :rules " 173 | backdrop-brightness = <'backdrop-brightness-'> (number | percentage | fraction) 174 | " 175 | :garden (fn [{[value] :component-data}] 176 | {:--gi-backdrop-brightness (str "brightness(" 177 | (value-unit->css value {:number {:value-fn div-100}}) 178 | ")") 179 | :backdrop-filter backdrop-filter-rule})} 180 | 181 | 182 | {:id :backdrop-contrast 183 | :since-version [:tw 3] 184 | :rules " 185 | backdrop-contrast = <'backdrop-contrast-'> (number | percentage | fraction) 186 | " 187 | :garden (fn [{[value] :component-data}] 188 | {:--gi-backdrop-contrast (str "contrast(" 189 | (value-unit->css value {:number {:value-fn div-100}}) 190 | ")") 191 | :backdrop-filter backdrop-filter-rule})} 192 | 193 | 194 | {:id :backdrop-grayscale 195 | :since-version [:tw 3] 196 | :rules " 197 | backdrop-grayscale = <'backdrop-grayscale'> (<'-'> (number | percentage | fraction))? 198 | " 199 | :garden (fn [{[value] :component-data}] 200 | {:--gi-backdrop-grayscale (str "grayscale(" 201 | (if (nil? value) 202 | "100%" 203 | (value-unit->css value {:number {:value-fn div-100}})) 204 | ")") 205 | :backdrop-filter backdrop-filter-rule})} 206 | 207 | 208 | {:id :backdrop-hue-rotate 209 | :since-version [:tw 3] 210 | :rules " 211 | backdrop-hue-rotate = <'backdrop-hue-rotate-'> (number | angle) 212 | " 213 | :garden (fn [{[value] :component-data}] 214 | {:--gi-backdrop-hue-rotate (str "hue-rotate(" 215 | (value-unit->css value {:number {:unit "deg"}}) 216 | ")") 217 | :backdrop-filter backdrop-filter-rule})} 218 | 219 | 220 | {:id :backdrop-invert 221 | :since-version [:tw 3] 222 | :rules " 223 | backdrop-invert = <'backdrop-invert'> (<'-'> (number | percentage | fraction))? 224 | " 225 | :garden (fn [{[value] :component-data}] 226 | {:--gi-backdrop-invert (str "invert(" 227 | (if (nil? value) 228 | "100%" 229 | (value-unit->css value {:number {:value-fn div-100}})) 230 | ")") 231 | :backdrop-filter backdrop-filter-rule})} 232 | 233 | 234 | {:id :backdrop-opacity 235 | :since-version [:tw 3] 236 | :rules " 237 | backdrop-opacity = <'backdrop-opacity-'> (number | percentage | fraction) 238 | " 239 | :garden (fn [{[value] :component-data}] 240 | {:--gi-backdrop-opacity (str "opacity(" 241 | (value-unit->css value {:number {:value-fn div-100}}) 242 | ")") 243 | :backdrop-filter backdrop-filter-rule})} 244 | 245 | 246 | {:id :backdrop-saturate 247 | :since-version [:tw 3] 248 | :rules " 249 | backdrop-saturate = <'backdrop-saturate-'> (number | percentage | fraction) 250 | " 251 | :garden (fn [{[value] :component-data}] 252 | {:--gi-backdrop-saturate (str "saturate(" 253 | (value-unit->css value {:number {:value-fn div-100}}) 254 | ")") 255 | :backdrop-filter backdrop-filter-rule})} 256 | 257 | 258 | {:id :backdrop-sepia 259 | :since-version [:tw 3] 260 | :rules " 261 | backdrop-sepia = <'backdrop-sepia'> (<'-'> (number | percentage | fraction))? 262 | " 263 | :garden (fn [{[value] :component-data}] 264 | {:--gi-backdrop-sepia (str "sepia(" 265 | (value-unit->css value {:number {:value-fn div-100}}) 266 | ")") 267 | :backdrop-filter backdrop-filter-rule})}]) 268 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/flexbox.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.flexbox 2 | (:require [girouette.tw.common :refer [value-unit->css div-4 mul-100]])) 3 | 4 | (def components 5 | [{:id :flex-grow 6 | :since-version [:tw 2] 7 | :rules " 8 | flex-grow = <'flex-'>? <'grow'> (<'-'> flex-grow-value)? 9 | flex-grow-value = number | fraction 10 | " 11 | :garden (fn [{data :component-data}] 12 | {:flex-grow (let [{:keys [flex-grow-value]} (into {} data)] 13 | (if (nil? flex-grow-value) 14 | 1 15 | (value-unit->css flex-grow-value)))})} 16 | 17 | 18 | {:id :flex-shrink 19 | :since-version [:tw 2] 20 | :rules " 21 | flex-shrink = <'flex-'>? <'shrink'> (<'-'> flex-shrink-value)? 22 | flex-shrink-value = number | fraction 23 | " 24 | :garden (fn [{data :component-data}] 25 | {:flex-shrink (let [{:keys [flex-shrink-value]} (into {} data)] 26 | (if (nil? flex-shrink-value) 27 | 1 28 | (value-unit->css flex-shrink-value)))})} 29 | 30 | 31 | {:id :flex-basis 32 | :since-version [:tw 2] 33 | :rules " 34 | flex-basis = <'flex-'>? <'basis'> (<'-'> flex-basis-value)? 35 | flex-basis-value = number | length | length-unit | fraction | percentage | full-100% | auto 36 | " 37 | :garden (fn [{data :component-data 38 | :keys [unitless-length-conversion]}] 39 | {:flex-basis (let [{:keys [flex-basis-value]} (into {} data)] 40 | (if (nil? flex-basis-value) 41 | 1 42 | (value-unit->css flex-basis-value {:zero-unit "px" 43 | :number unitless-length-conversion 44 | :fraction {:unit "%" 45 | :value-fn mul-100}})))})} 46 | 47 | 48 | {:id :flex-shorthand 49 | :since-version [:tw 2] 50 | :rules " 51 | flex-shorthand = <'flex-'> (flex-shorthand-1-arg | flex-shorthand-2-args | flex-shorthand-3-args) 52 | flex-shorthand-1-arg = number | fraction | 'auto' | 'initial' | 'none' 53 | flex-shorthand-2-args = flex-grow-value <'-'> (flex-shrink-value | flex-basis-value) 54 | flex-shorthand-3-args = flex-grow-value <'-'> flex-shrink-value <'-'> flex-basis-value 55 | " 56 | :garden (fn [{[[shorthand-type & args]] :component-data 57 | :keys [unitless-length-conversion]}] 58 | {:flex (case shorthand-type 59 | :flex-shorthand-1-arg 60 | (case (first args) 61 | "none" "none" 62 | "initial" "0 1 auto" 63 | "auto" "1 1 auto" 64 | (let [size (value-unit->css (first args))] 65 | (str size " " size " 0%"))) 66 | 67 | :flex-shorthand-2-args 68 | (let [[[_ grow-data] [_ shrink-basis-data]] args 69 | grow-value (value-unit->css grow-data) 70 | shrink-basis-value (value-unit->css shrink-basis-data)] 71 | (str grow-value " " shrink-basis-value)) 72 | 73 | :flex-shorthand-3-args 74 | (let [[[_ grow-data] [_ shrink-data] [_ basis-data]] args 75 | grow-value (value-unit->css grow-data) 76 | shrink-value (value-unit->css shrink-data) 77 | basis-value (value-unit->css basis-data {:zero-unit nil 78 | :number unitless-length-conversion 79 | :fraction {:unit "%" 80 | :value-fn mul-100}})] 81 | (str grow-value " " shrink-value " " basis-value)))})} 82 | 83 | 84 | {:id :flex-direction 85 | :since-version [:tw 2] 86 | :rules " 87 | flex-direction = <'flex-'> ('row' | 'row-reverse' | 'col' | 'col-reverse') 88 | " 89 | :garden (fn [{[direction] :component-data}] 90 | {:flex-direction ({"row" "row" 91 | "row-reverse" "row-reverse" 92 | "col" "column" 93 | "col-reverse" "column-reverse"} direction)})} 94 | 95 | 96 | {:id :flex-wrap 97 | :since-version [:tw 2] 98 | :rules " 99 | flex-wrap = <'flex-'> ('wrap' | 'wrap-reverse' | 'nowrap') 100 | " 101 | :garden (fn [{[wrap] :component-data}] 102 | {:flex-wrap wrap})} 103 | 104 | 105 | {:id :order 106 | :since-version [:tw 2] 107 | :rules " 108 | order = signus? <'order-'> order-param 109 | order-param = integer | 'first' | 'last' | 'none' 110 | " 111 | :garden (fn [props] 112 | (let [{:keys [signus order-param]} (into {} (:component-data props))] 113 | {:order (case order-param 114 | "first" -9999 115 | "last" 9999 116 | "none" 0 117 | (value-unit->css order-param {:signus signus}))}))}]) 118 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/grid.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.grid 2 | (:require [girouette.tw.common :refer [value-unit->css div-4]])) 3 | 4 | (def components 5 | [{:id :grid-template-columns 6 | :since-version [:tw 2] 7 | :rules " 8 | grid-template-columns = <'grid-cols-'> (integer | none) 9 | " 10 | :garden (fn [{[repeat] :component-data}] 11 | (let [repeat (value-unit->css repeat)] 12 | {:grid-template-columns (if (#{"none" 0} repeat) 13 | "none" 14 | (str "repeat(" repeat ", minmax(0, 1fr))"))}))} 15 | 16 | 17 | {:id :grid-column-auto 18 | :since-version [:tw 2] 19 | :rules " 20 | grid-column-auto = 'col-auto' 21 | " 22 | :garden (fn [props] 23 | {:grid-column "auto"})} 24 | 25 | 26 | {:id :grid-column-span 27 | :since-version [:tw 2] 28 | :rules " 29 | grid-column-span = <'col-span-'> (integer | full) 30 | " 31 | :garden (fn [{[param] :component-data}] 32 | (let [param (value-unit->css param)] 33 | {:grid-column (if (= param "full") 34 | "-1 / 1" 35 | ;; TODO: do we need to repeat the span twice? 36 | (str "span " param " / span " param))}))} 37 | 38 | 39 | {:id :grid-column-start 40 | :since-version [:tw 2] 41 | :rules " 42 | grid-column-start = <'col-start-'> (integer | auto) 43 | " 44 | :garden (fn [{[param] :component-data}] 45 | {:grid-column-start (value-unit->css param)})} 46 | 47 | 48 | {:id :grid-column-end 49 | :since-version [:tw 2] 50 | :rules " 51 | grid-column-end = <'col-end-'> (integer | auto) 52 | " 53 | :garden (fn [{[param] :component-data}] 54 | {:grid-column-end (value-unit->css param)})} 55 | 56 | 57 | {:id :grid-template-rows 58 | :since-version [:tw 2] 59 | :rules " 60 | grid-template-rows = <'grid-rows-'> (integer | none) 61 | " 62 | :garden (fn [{[repeat] :component-data}] 63 | (let [repeat (value-unit->css repeat)] 64 | {:grid-template-rows (if (#{"none" 0} repeat) 65 | "none" 66 | (str "repeat(" repeat ", minmax(0, 1fr))"))}))} 67 | 68 | 69 | {:id :grid-row-auto 70 | :since-version [:tw 2] 71 | :rules " 72 | grid-row-auto = 'row-auto' 73 | " 74 | :garden (fn [props] 75 | {:grid-row "auto"})} 76 | 77 | 78 | {:id :grid-row-span 79 | :since-version [:tw 2] 80 | :rules " 81 | grid-row-span = <'row-span-'> (integer | full) 82 | " 83 | :garden (fn [{[param] :component-data}] 84 | (let [param (value-unit->css param)] 85 | {:grid-row (if (= param "full") 86 | "-1 / 1" 87 | ;; TODO: do we need to repeat the span twice? 88 | (str "span " param " / span " param))}))} 89 | 90 | 91 | {:id :grid-row-start 92 | :since-version [:tw 2] 93 | :rules " 94 | grid-row-start = <'row-start-'> (integer | auto) 95 | " 96 | :garden (fn [{[param] :component-data}] 97 | {:grid-row-start (value-unit->css param)})} 98 | 99 | 100 | {:id :grid-row-end 101 | :since-version [:tw 2] 102 | :rules " 103 | grid-row-end = <'row-end-'> (integer | auto) 104 | " 105 | :garden (fn [{[param] :component-data}] 106 | {:grid-row-end (value-unit->css param)})} 107 | 108 | 109 | {:id :grid-auto-flow 110 | :since-version [:tw 2] 111 | :rules " 112 | grid-auto-flow = <'grid-flow-'> ('row' | 'col' | 'row-dense' | 'col-dense') 113 | " 114 | :garden (fn [{[param] :component-data}] 115 | {:grid-auto-flow ({"row" "row" 116 | "col" "column" 117 | "row-dense" "row dense" 118 | "col-dense" "column dense"} param)})} 119 | 120 | 121 | {:id :grid-auto-columns 122 | :since-version [:tw 2] 123 | :rules " 124 | grid-auto-columns = <'auto-cols-'> ('auto' | 'min' | 'max' | 'fr') 125 | " 126 | :garden (fn [{[param] :component-data}] 127 | {:grid-auto-columns ({"auto" "auto" 128 | "min" "min-content" 129 | "max" "max-content" 130 | "fr" "minmax(0, 1fr)"} param)})} 131 | 132 | 133 | {:id :grid-auto-rows 134 | :since-version [:tw 2] 135 | :rules " 136 | grid-auto-rows = <'auto-rows-'> ('auto' | 'min' | 'max' | 'fr') 137 | " 138 | :garden (fn [{[param] :component-data}] 139 | {:grid-auto-rows ({"auto" "auto" 140 | "min" "min-content" 141 | "max" "max-content" 142 | "fr" "minmax(0, 1fr)"} param)})} 143 | 144 | 145 | {:id :gap 146 | :since-version [:tw 2] 147 | :rules " 148 | gap = <'gap-'> (axis <'-'>)? gap-value 149 | gap-value = number | length | length-unit | percentage 150 | " 151 | :garden (fn [{data :component-data 152 | :keys [unitless-length-conversion]}] 153 | (let [{:keys [axis gap-value]} (into {} data)] 154 | {({"x" :column-gap 155 | "y" :row-gap 156 | nil :gap} axis) 157 | (value-unit->css gap-value {:zero-unit nil 158 | :number unitless-length-conversion})}))}]) 159 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/interactivity.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.interactivity 2 | (:require 3 | [girouette.tw.color :refer [color->css]] 4 | [girouette.tw.common :refer [value-unit->css div-4 mul-100]])) 5 | 6 | (def components 7 | [{:id :accent-color 8 | :since-version [:tw 3] 9 | :rules " 10 | accent-color = <'accent-'> ('inherit' | 'auto' | color) 11 | " 12 | :garden (fn [{[color] :component-data read-color :read-color}] 13 | {:accent-color (case color 14 | "inherit" "inherit" 15 | "auto" "auto" 16 | (color->css (read-color color)))})} 17 | 18 | {:id :appearance 19 | :since-version [:tw 2] 20 | :rules " 21 | appearance = 'appearance-none' 22 | " 23 | :garden (fn [_] 24 | {:appearance "none"})} 25 | 26 | 27 | {:id :cursor 28 | :since-version [:tw 2] 29 | :rules " 30 | cursor = <'cursor-'> ('auto' | 'default' | 'pointer' | 'wait' | 31 | 'text' | 'move' | 'help' | 'not-allowed' | 32 | 'none' | 'context-menu' | 'progress' | 'cell' | 33 | 'crosshair' | 'vertical-text' | 'alias' | 'copy' | 34 | 'no-drop' | 'grab' | 'grabbing' | 'all-scroll' | 35 | 'col-resize' | 'row-resize' | 36 | 'n-resize' | 'e-resize' | 's-resize' | 'w-resize' | 37 | 'ne-resize' | 'nw-resize' | 'se-resize' | 'sw-resize' | 38 | 'nesw-resize' | 'nwse-resize' | 39 | 'zoom-in' | 'zoom-out') 40 | " 41 | :garden (fn [{[type] :component-data}] 42 | {:cursor type})} 43 | 44 | 45 | {:id :outline 46 | :since-version [:tw 2] 47 | :removed-in-version [:tw 3] 48 | :rules " 49 | outline = <'outline-'> ('none' | 'white' | 'black') 50 | " 51 | :garden (fn [{[type] :component-data}] 52 | {:outline ({"none" "2px solid transparent" 53 | "white" "2px dotted white" 54 | "black" "2px dotted black"} type) 55 | :outline-offset "2px"})} 56 | 57 | 58 | {:id :pointer-events 59 | :since-version [:tw 2] 60 | :rules " 61 | pointer-events = <'pointer-events-'> ('none' | 'auto') 62 | " 63 | :garden (fn [{[type] :component-data}] 64 | {:pointer-events type})} 65 | 66 | 67 | {:id :resize 68 | :since-version [:tw 2] 69 | :rules " 70 | resize = <'resize'> (<'-'> ('none' | 'x' | 'y'))? 71 | " 72 | :garden (fn [{[type] :component-data}] 73 | {:resize ({"none" "none" 74 | "x" "horizontal" 75 | "y" "vertical" 76 | nil "both"} type)})} 77 | 78 | 79 | {:id :scroll-behavior 80 | :since-version [:tw 3] 81 | :rules " 82 | scroll-behavior = <'scroll-'> ('auto' | 'smooth') 83 | " 84 | :garden (fn [{[value] :component-data}] 85 | {:scroll-behavior value})} 86 | 87 | 88 | {:id :scroll-margin 89 | :since-version [:tw 3] 90 | :rules " 91 | scroll-margin = signus? <'scroll-m'> (direction | axis)? <'-'> scroll-margin-value 92 | scroll-margin-value = number | length | length-unit 93 | " 94 | :garden (fn [{:keys [component-data unitless-length-conversion]}] 95 | (let [{:keys [signus direction axis scroll-margin-value]} (into {} component-data) 96 | directions (case direction 97 | "t" ["top"] 98 | "r" ["right"] 99 | "b" ["bottom"] 100 | "l" ["left"] 101 | (case axis 102 | "x" ["left" "right"] 103 | "y" ["top" "bottom"] 104 | nil)) 105 | props (if (nil? directions) 106 | [:scroll-margin] 107 | (mapv (fn [dir] 108 | (keyword (str "scroll-margin-" dir))) 109 | directions)) 110 | value-css (value-unit->css scroll-margin-value 111 | {:signus signus 112 | :zero-unit "px" 113 | :number unitless-length-conversion})] 114 | (into {} 115 | (map (fn [prop] 116 | [prop value-css])) 117 | props)))} 118 | 119 | 120 | {:id :scroll-padding 121 | :since-version [:tw 3] 122 | :rules " 123 | scroll-padding = signus? <'scroll-p'> (direction | axis)? <'-'> scroll-padding-value 124 | scroll-padding-value = number | length | length-unit | fraction | percentage 125 | " 126 | :garden (fn [{:keys [component-data unitless-length-conversion]}] 127 | (let [{:keys [signus direction axis scroll-padding-value]} 128 | (into {} component-data) 129 | directions (case direction 130 | "t" ["top"] 131 | "r" ["right"] 132 | "b" ["bottom"] 133 | "l" ["left"] 134 | (case axis 135 | "x" ["left" "right"] 136 | "y" ["top" "bottom"] 137 | nil)) 138 | props (if (nil? directions) 139 | [:scroll-padding] 140 | (mapv (fn [dir] 141 | (keyword (str "scroll-padding-" dir))) 142 | directions)) 143 | value-css (value-unit->css scroll-padding-value 144 | {:signus signus 145 | :zero-unit "px" 146 | :number unitless-length-conversion 147 | :fraction {:unit "%" 148 | :value-fn mul-100}})] 149 | (into {} 150 | (map (fn [prop] 151 | [prop value-css])) 152 | props)))} 153 | 154 | 155 | {:id :scroll-snap-align 156 | :since-version [:tw 3] 157 | :rules " 158 | scroll-snap-align = <'snap-'> ('start' | 'end' | 'center' | (<'align-'> 'none')) 159 | " 160 | :garden (fn [{[value] :component-data}] 161 | {:scroll-snap-align value})} 162 | 163 | 164 | {:id :scroll-snap-stop 165 | :since-version [:tw 3] 166 | :rules " 167 | scroll-snap-stop = <'snap-'> ('normal' | 'always') 168 | " 169 | :garden (fn [{[value] :component-data}] 170 | {:scroll-snap-stop value})} 171 | 172 | 173 | {:id :scroll-snap-type 174 | :since-version [:tw 3] 175 | :rules " 176 | scroll-snap-type = <'snap-'> (scroll-snap-type-dir | scroll-snap-type-strictness) 177 | scroll-snap-type-dir = 'none' | 'x' | 'y' | 'both' 178 | scroll-snap-type-strictness = 'mandatory' | 'proximity' 179 | " 180 | :garden (fn [{:keys [component-data]}] 181 | (let [{:keys [scroll-snap-type-dir scroll-snap-type-strictness]} (into {} component-data)] 182 | (cond 183 | (some? scroll-snap-type-strictness) 184 | {:--gi-scroll-snap-strictness scroll-snap-type-strictness} 185 | 186 | (= scroll-snap-type-dir "none") 187 | {:scroll-snap-type "none"} 188 | 189 | :else 190 | {:scroll-snap-type (str scroll-snap-type-dir " var(--gi-scroll-snap-strictness)")})))} 191 | 192 | 193 | {:id :touch-action 194 | :since-version [:tw 3] 195 | :rules " 196 | touch-action = <'touch-'> ('auto' | 'none' | 197 | 'pan-x' | 'pan-y' | 198 | 'pan-up' | 'pan-right' | 'pan-down' |'pan-left' | 199 | 'pinch-zoom' | 'manipulation') 200 | " 201 | :garden (fn [{[value] :component-data}] 202 | {:touch-action value})} 203 | 204 | 205 | {:id :user-select 206 | :since-version [:tw 2] 207 | :rules " 208 | user-select = <'select-'> ('none' | 'text' | 'all' | 'auto') 209 | " 210 | :garden (fn [{[selection-type] :component-data}] 211 | {:user-select selection-type})} 212 | 213 | 214 | {:id :will-change 215 | :since-version [:tw 3] 216 | :rules " 217 | will-change = <'will-change-'> ('auto' | 'scroll' | 'contents' | 'transform') 218 | " 219 | :garden (fn [{[value] :component-data}] 220 | {:will-change ({"auto" "auto" 221 | "scroll" "scroll-position" 222 | "contents" "contents" 223 | "transform" "transform"} value)})}]) 224 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/layout.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.layout 2 | (:require [clojure.string :as str] 3 | [girouette.tw.common :refer [value-unit->css breakpoint->pixels div-4 mul-100 ratio-str]])) 4 | 5 | (def components 6 | [{:id :aspect-ratio 7 | :since-version [:tw 3] 8 | :rules " 9 | aspect-ratio = <'aspect-'> (aspect-ratio-fixed | aspect-ratio-as-ratio) 10 | aspect-ratio-fixed = 'auto' | 'square' | 'video' 11 | aspect-ratio-as-ratio = ratio 12 | " 13 | :garden (fn [{:keys [component-data]}] 14 | (let [{:keys [aspect-ratio-fixed aspect-ratio-as-ratio]} (into {} component-data)] 15 | {:aspect-ratio (if (some? aspect-ratio-fixed) 16 | (case aspect-ratio-fixed 17 | "auto" "auto" 18 | "square" "1 / 1" 19 | "video" "16 / 9") 20 | (value-unit->css aspect-ratio-as-ratio {:ratio {:value-fn ratio-str}}))}))} 21 | 22 | 23 | {:id :container 24 | :since-version [:tw 2] 25 | :rules " 26 | container = <'container'> 27 | " 28 | :garden (fn [props] 29 | (if-some [media-query-min-width (-> props :prefixes :media-query-min-width)] 30 | {:max-width (breakpoint->pixels media-query-min-width)} 31 | {:width "100%"}))} 32 | 33 | 34 | {:id :columns 35 | :since-version [:tw 3] 36 | :rules " 37 | columns = <'columns-'> (columns-count | columns-width | columns-count <'-'> columns-width) 38 | columns-count = integer | 'auto' 39 | columns-width = '3xs' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 40 | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl' | 41 | length | 'auto' 42 | " 43 | :garden (fn [{:keys [component-data]}] 44 | (let [{:keys [columns-count columns-width]} (into {} component-data)] 45 | {:columns (->> [(when (some? columns-count) 46 | (if (= columns-count "auto") 47 | "auto" 48 | (value-unit->css columns-count))) 49 | (when (some? columns-width) 50 | (case columns-width 51 | "3xs" "16rem" 52 | "2xs" "18rem" 53 | "xs" "20rem" 54 | "sm" "24rem" 55 | "md" "28rem" 56 | "lg" "32rem" 57 | "xl" "36rem" 58 | "2xl" "42rem" 59 | "3xl" "48rem" 60 | "4xl" "56rem" 61 | "5xl" "64rem" 62 | "6xl" "72rem" 63 | "7xl" "80rem" 64 | "auto" "auto" 65 | (value-unit->css columns-width)))] 66 | (remove nil?) 67 | (str/join " "))}))} 68 | 69 | 70 | {:id :break-after 71 | :since-version [:tw 3] 72 | :rules " 73 | break-after = <'break-after-'> ('auto' | 'avoid' | 'all' | 'avoid-page' | 74 | 'page' | 'left' | 'right' | 'column') 75 | " 76 | :garden (fn [{[value] :component-data}] 77 | {:break-after value})} 78 | 79 | 80 | {:id :break-before 81 | :since-version [:tw 3] 82 | :rules " 83 | break-before = <'break-before-'> ('auto' | 'avoid' | 'all' | 'avoid-page' | 84 | 'page' | 'left' | 'right' | 'column') 85 | " 86 | :garden (fn [{[value] :component-data}] 87 | {:break-before value})} 88 | 89 | 90 | {:id :break-inside 91 | :since-version [:tw 3] 92 | :rules " 93 | break-inside = <'break-inside-'> ('auto' | 'avoid' | 'avoid-page' | 'avoid-column') 94 | " 95 | :garden (fn [{[value] :component-data}] 96 | {:break-inside value})} 97 | 98 | 99 | {:id :box-decoration-break 100 | :since-version [:tw 3] 101 | :rules " 102 | box-decoration-break = <'box-decoration-'> ('clone' | 'slice') 103 | " 104 | :garden (fn [{[decoration-break] :component-data}] 105 | {:box-decoration-break decoration-break})} 106 | 107 | 108 | {:id :box-sizing 109 | :since-version [:tw 2] 110 | :rules " 111 | box-sizing = <'box-'> ('border' | 'content') 112 | " 113 | :garden (fn [{[box-model] :component-data}] 114 | {:box-sizing (case box-model 115 | "border" "border-box" 116 | "content" "content-box")})} 117 | 118 | 119 | {:id :display 120 | :since-version [:tw 2] 121 | :rules " 122 | display = 'block' | 'inline-block' | 'inline' | 'flex' | 'inline-flex' | 123 | 'table' | 'inline-table' | 'table-caption' | 'table-cell' | 'table-column' | 124 | 'table-column-group' | 'table-footer-group' | 'table-header-group' | 'table-row-group' | 125 | 'table-row' | 'flow-root' | 'grid' |'inline-grid' | 'contents' | 'list-item' | 'hidden' 126 | " 127 | :garden (fn [{[display-mode] :component-data}] 128 | {:display (if (= "hidden" display-mode) 129 | "none" 130 | display-mode)})} 131 | 132 | 133 | {:id :floats 134 | :since-version [:tw 2] 135 | :rules " 136 | floats = <'float-'> ('left' | 'right' | 'none') 137 | " 138 | :garden (fn [{[direction] :component-data}] 139 | {:float direction})} 140 | 141 | 142 | {:id :clear 143 | :since-version [:tw 2] 144 | :rules " 145 | clear = <'clear-'> ('left' | 'right' | 'both' | 'none') 146 | " 147 | :garden (fn [{[direction] :component-data}] 148 | {:clear direction})} 149 | 150 | {:id :isolation 151 | :since-version [:tw 3] 152 | :rules " 153 | isolation = 'isolate' | <'isolation-'> 'auto' 154 | " 155 | :garden (fn [{[isolation] :component-data}] 156 | {:isolation isolation})} 157 | 158 | {:id :object-fit 159 | :since-version [:tw 2] 160 | :rules " 161 | object-fit = <'object-'> ('contain' | 'cover' | 'fill' | 'none' | 'scale-down') 162 | " 163 | :garden (fn [{[fitness] :component-data}] 164 | {:object-fit fitness})} 165 | 166 | 167 | {:id :object-position 168 | :since-version [:tw 2] 169 | :rules " 170 | object-position = <'object-'> object-position-side 171 | = 'left' | 'left-bottom' | 'left-top' | 172 | 'right' | 'right-bottom' | 'right-top' | 173 | 'center' | 'bottom' | 'top' 174 | " 175 | :garden (fn [{[position] :component-data}] 176 | {:object-position (str/escape position {\- \space})})} 177 | 178 | 179 | {:id :overflow 180 | :since-version [:tw 2] 181 | :rules " 182 | overflow = <'overflow-'> (axis <'-'>)? overflow-mode 183 | overflow-mode = 'auto' | 'hidden' | 'clip' | 'visible' | 'scroll' 184 | " 185 | :garden (fn [{:keys [component-data]}] 186 | (let [{:keys [axis overflow-mode]} (into {} component-data) 187 | property (str "overflow" (when axis (str "-" axis)))] 188 | {(keyword property) overflow-mode}))} 189 | 190 | 191 | {:id :overscroll 192 | :since-version [:tw 2] 193 | :rules " 194 | overscroll = <'overscroll-'> (axis <'-'>)? overscroll-mode 195 | overscroll-mode = 'auto' | 'contain' | 'none' 196 | " 197 | :garden (fn [{:keys [component-data]}] 198 | (let [{:keys [axis overscroll-mode]} (into {} component-data) 199 | property (str "overscroll" (when axis (str "-" axis)))] 200 | {(keyword property) overscroll-mode}))} 201 | 202 | 203 | {:id :position 204 | :since-version [:tw 2] 205 | :rules " 206 | position = 'static' | 'fixed' | 'absolute' | 'relative' | 'sticky' 207 | " 208 | :garden (fn [{[position] :component-data}] 209 | {:position position})} 210 | 211 | 212 | {:id :positioning 213 | :since-version [:tw 2] 214 | :rules " 215 | positioning = signus? positioning-mode <'-'> positioning-value 216 | positioning-mode = 'top' | 'right' | 'bottom' | 'left' | #'inset(-x|-y)?' 217 | positioning-value = number | length | length-unit | fraction | percentage | full-100% | auto 218 | " 219 | :garden (fn [{:keys [component-data unitless-length-conversion]}] 220 | (let [{:keys [signus positioning-mode positioning-value]} (into {} component-data) 221 | directions ({"inset" [:top :right :bottom :left] 222 | "inset-x" [:right :left] 223 | "inset-y" [:top :bottom] 224 | "top" [:top] 225 | "right" [:right] 226 | "bottom" [:bottom] 227 | "left" [:left]} positioning-mode) 228 | value-css (value-unit->css positioning-value 229 | {:signus signus 230 | :zero-unit nil 231 | :number unitless-length-conversion 232 | :fraction {:unit "%" 233 | :value-fn mul-100}})] 234 | (into {} 235 | (map (fn [direction] [direction value-css])) 236 | directions)))} 237 | 238 | 239 | {:id :visibility 240 | :since-version [:tw 2] 241 | :rules " 242 | visibility = 'visible' | 'invisible' 243 | " 244 | :garden (fn [{[visibility] :component-data}] 245 | {:visibility ({"visible" "visible" 246 | "invisible" "hidden"} visibility)})} 247 | 248 | 249 | {:id :z-index 250 | :since-version [:tw 2] 251 | :rules " 252 | z-index = <'z-'> (integer | auto) 253 | " 254 | :garden (fn [{[index] :component-data}] 255 | {:z-index (value-unit->css index)})}]) 256 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/sizing.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.sizing 2 | (:require [girouette.tw.common :refer [value-unit->css div-4 mul-100]])) 3 | 4 | (def components 5 | [{:id :width 6 | :since-version [:tw 2] 7 | :rules " 8 | width = <'w-'> (number | length | length-unit | fraction | percentage | full-100% | 9 | auto | screen-100vw | min-content | max-content | fit-content) 10 | " 11 | :garden (fn [{[value-data] :component-data 12 | :keys [unitless-length-conversion]}] 13 | {:width (value-unit->css value-data 14 | {:zero-unit nil 15 | :number unitless-length-conversion 16 | :fraction {:unit "%" 17 | :value-fn mul-100}})})} 18 | 19 | 20 | {:id :min-width 21 | :since-version [:tw 2] 22 | :rules " 23 | min-width = <'min-w-'> (number | length | length-unit | fraction | percentage | full-100% | 24 | auto | screen-100vw | min-content | max-content | fit-content) 25 | " 26 | :garden (fn [{[value-data] :component-data 27 | :keys [unitless-length-conversion]}] 28 | {:min-width (value-unit->css value-data 29 | {:zero-unit nil 30 | :number unitless-length-conversion 31 | :fraction {:unit "%" 32 | :value-fn mul-100}})})} 33 | 34 | 35 | {:id :max-width 36 | :since-version [:tw 2] 37 | :rules " 38 | max-width = <'max-w-'> (max-width-fixed-size | max-width-generic-size) 39 | max-width-fixed-size = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl' | 40 | 'prose' | 'screen-sm' | 'screen-md' | 'screen-lg' | 'screen-xl' | 'screen-2xl' 41 | max-width-generic-size = number | length | length-unit | fraction | percentage | full-100% | 42 | none | screen-100vw | min-content | max-content | fit-content 43 | " 44 | :garden (fn [{:keys [component-data unitless-length-conversion]}] 45 | (let [{:keys [max-width-fixed-size max-width-generic-size]} (into {} component-data)] 46 | {:max-width (if (some? max-width-fixed-size) 47 | ({"xs" "20rem" 48 | "sm" "24rem" 49 | "md" "28rem" 50 | "lg" "32rem" 51 | "xl" "36rem" 52 | "2xl" "42rem" 53 | "3xl" "48rem" 54 | "4xl" "56rem" 55 | "5xl" "64rem" 56 | "6xl" "72rem" 57 | "7xl" "80rem" 58 | "prose" "65ch" 59 | "screen-sm" "640px" 60 | "screen-md" "768px" 61 | "screen-lg" "1024px" 62 | "screen-xl" "1280px" 63 | "screen-2xl" "1536px"} max-width-fixed-size) 64 | (value-unit->css max-width-generic-size 65 | {:zero-unit nil 66 | :number unitless-length-conversion 67 | :fraction {:unit "%" 68 | :value-fn mul-100}}))}))} 69 | 70 | 71 | {:id :height 72 | :since-version [:tw 2] 73 | :rules " 74 | height = <'h-'> (number | length | length-unit | fraction | percentage | full-100% | 75 | auto | screen-100vh | min-content | max-content | fit-content) 76 | " 77 | :garden (fn [{[value-data] :component-data 78 | :keys [unitless-length-conversion]}] 79 | {:height (value-unit->css value-data 80 | {:zero-unit nil 81 | :number unitless-length-conversion 82 | :fraction {:unit "%" 83 | :value-fn mul-100}})})} 84 | 85 | 86 | {:id :min-height 87 | :since-version [:tw 2] 88 | :rules " 89 | min-height = <'min-h-'> (number | length | length-unit | fraction | percentage | full-100% | 90 | auto | screen-100vh | min-content | max-content | fit-content) 91 | " 92 | :garden (fn [{[value-data] :component-data 93 | :keys [unitless-length-conversion]}] 94 | {:min-height (value-unit->css value-data 95 | {:zero-unit nil 96 | :number unitless-length-conversion 97 | :fraction {:unit "%" 98 | :value-fn mul-100}})})} 99 | 100 | 101 | {:id :max-height 102 | :since-version [:tw 2] 103 | :rules " 104 | max-height = <'max-h-'> (number | length | length-unit | fraction | percentage | full-100% | 105 | none | screen-100vh | min-content | max-content | fit-content) 106 | " 107 | :garden (fn [{[value-data] :component-data 108 | :keys [unitless-length-conversion]}] 109 | {:max-height (value-unit->css value-data 110 | {:zero-unit nil 111 | :number unitless-length-conversion 112 | :fraction {:unit "%" 113 | :value-fn mul-100}})})}]) 114 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/spacing.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.spacing 2 | (:require [girouette.tw.common :refer [value-unit->css div-4 between-children-selector]])) 3 | 4 | 5 | (def components 6 | [{:id :padding 7 | :since-version [:tw 2] 8 | :rules " 9 | padding = signus? <'p'> (direction | axis)? <'-'> padding-value 10 | padding-value = number | length | length-unit 11 | " 12 | :garden (fn [{:keys [component-data unitless-length-conversion]}] 13 | (let [{:keys [signus direction axis padding-value]} (into {} component-data) 14 | directions (case direction 15 | "t" ["top"] 16 | "r" ["right"] 17 | "b" ["bottom"] 18 | "l" ["left"] 19 | (case axis 20 | "x" ["left" "right"] 21 | "y" ["top" "bottom"] 22 | nil)) 23 | value-css (value-unit->css padding-value {:signus signus 24 | :zero-unit nil 25 | :number unitless-length-conversion})] 26 | (if (nil? directions) 27 | {:padding value-css} 28 | (into {} 29 | (map (fn [direction] [(keyword (str "padding-" direction)) value-css])) 30 | directions))))} 31 | 32 | 33 | {:id :margin 34 | :since-version [:tw 2] 35 | :rules " 36 | margin = signus? <'m'> (direction | axis)? <'-'> margin-value 37 | margin-value = number | length | length-unit | auto 38 | " 39 | :garden (fn [{:keys [component-data unitless-length-conversion]}] 40 | (let [{:keys [signus direction axis margin-value]} (into {} component-data) 41 | directions (case direction 42 | "t" ["top"] 43 | "r" ["right"] 44 | "b" ["bottom"] 45 | "l" ["left"] 46 | (case axis 47 | "x" ["left" "right"] 48 | "y" ["top" "bottom"] 49 | nil)) 50 | value-css (value-unit->css margin-value {:signus signus 51 | :zero-unit nil 52 | :number unitless-length-conversion})] 53 | (if (nil? directions) 54 | {:margin value-css} 55 | (into {} 56 | (map (fn [direction] [(keyword (str "margin-" direction)) value-css])) 57 | directions))))} 58 | 59 | 60 | {:id :space-between 61 | :since-version [:tw 2] 62 | :rules " 63 | space-between = signus? <'space-'> axis <'-'> space-between-value (<'-'> space-between-reverse)? 64 | space-between-value = number | length | length-unit 65 | space-between-reverse = 'reverse' 66 | " 67 | :garden (fn [{:keys [component-data unitless-length-conversion]}] 68 | (let [{:keys [signus axis space-between-value space-between-reverse]} (into {} component-data) 69 | direction ({["x" false] "left" 70 | ["x" true] "right" 71 | ["y" false] "top" 72 | ["y" true] "bottom"} [axis (some? space-between-reverse)]) 73 | value-css (value-unit->css space-between-value {:signus signus 74 | :zero-unit nil 75 | :number unitless-length-conversion})] 76 | [between-children-selector {(keyword (str "margin-" direction)) value-css}]))}]) 77 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/svg.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.svg 2 | (:require [girouette.tw.color :refer [color->css]] 3 | [girouette.tw.common :refer [value-unit->css]])) 4 | 5 | (def components 6 | [{:id :fill 7 | :since-version [:tw 2] 8 | :rules " 9 | fill = <'fill-'> color 10 | " 11 | :garden (fn [{[color] :component-data 12 | read-color :read-color}] 13 | {:fill (color->css (read-color color))})} 14 | 15 | 16 | {:id :stroke 17 | :since-version [:tw 2] 18 | :rules " 19 | stroke = <'stroke-'> color 20 | " 21 | :garden (fn [{[color] :component-data 22 | read-color :read-color}] 23 | {:stroke (color->css (read-color color))})} 24 | 25 | 26 | {:id :stroke-width 27 | :since-version [:tw 2] 28 | :rules " 29 | stroke-width = <'stroke-'> number 30 | " 31 | :garden (fn [{[thickness] :component-data}] 32 | {:stroke-width (value-unit->css thickness)})}]) 33 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/table.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.table) 2 | 3 | (def components 4 | [{:id :border-collapse 5 | :since-version [:tw 2] 6 | :rules " 7 | border-collapse = <'border-'> ('collapse' | 'separate') 8 | " 9 | :garden (fn [{[type] :component-data}] 10 | {:border-collapse type})} 11 | 12 | 13 | {:id :table-layout 14 | :since-version [:tw 2] 15 | :rules " 16 | table-layout = <'table-'> ('auto' | 'fixed') 17 | " 18 | :garden (fn [{[type] :component-data}] 19 | {:table-layout type})}]) 20 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/tw/transform.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.tw.transform 2 | (:require [clojure.string :as str] 3 | [girouette.tw.common :refer [value-unit->css div-100 div-4 mul-100]])) 4 | 5 | (def components 6 | [{:id :transform 7 | :since-version [:tw 2] 8 | :rules " 9 | transform = 'transform' | 'transform-gpu' | 'transform-none' 10 | " 11 | :garden (fn [{[transform-type] :component-data}] 12 | (case transform-type 13 | "transform" {:--gi-translate-x 0 14 | :--gi-translate-y 0 15 | :--gi-rotate 0 16 | :--gi-skew-x 0 17 | :--gi-skew-y 0 18 | :--gi-scale-x 1 19 | :--gi-scale-y 1 20 | :transform (str "translateX(var(--gi-translate-x)) " 21 | "translateY(var(--gi-translate-y)) " 22 | "rotate(var(--gi-rotate)) " 23 | "skewX(var(--gi-skew-x)) " 24 | "skewY(var(--gi-skew-y)) " 25 | "scaleX(var(--gi-scale-x)) " 26 | "scaleY(var(--gi-scale-y))")} 27 | "transform-gpu" {:--gi-translate-x 0 28 | :--gi-translate-y 0 29 | :--gi-rotate 0 30 | :--gi-skew-x 0 31 | :--gi-skew-y 0 32 | :--gi-scale-x 1 33 | :--gi-scale-y 1 34 | :transform (str "translate3d(var(--gi-translate-x),var(--gi-translate-y),0) " 35 | "rotate(var(--gi-rotate)) " 36 | "skewX(var(--gi-skew-x)) " 37 | "skewY(var(--gi-skew-y)) " 38 | "scaleX(var(--gi-scale-x)) " 39 | "scaleY(var(--gi-scale-y))")} 40 | "transform-none" {:transform "none"})) 41 | :before-rules #{:translate :rotate :skew :scale}} 42 | 43 | 44 | {:id :transform-origin 45 | :since-version [:tw 2] 46 | :rules " 47 | transform-origin = <'origin-'> ('top-left'| 'top' | 'top-right' | 48 | 'left' | 'center' | 'right' | 49 | 'bottom-left' | 'bottom' | 'bottom-right') 50 | " 51 | :garden (fn [{[direction] :component-data}] 52 | {:transform-origin (str/escape direction {\- \space})})} 53 | 54 | 55 | {:id :scale 56 | :since-version [:tw 2] 57 | :rules " 58 | scale = signus? <'scale-'> (axis <'-'>)? scale-value 59 | scale-value = number 60 | " 61 | :garden (fn [{data :component-data}] 62 | (let [{:keys [signus axis scale-value]} (into {} data) 63 | axes ({"x" ["x"] 64 | "y" ["y"] 65 | nil ["x" "y"]} axis) 66 | value (value-unit->css scale-value {:signus signus 67 | :value-fn div-100})] 68 | (into {} 69 | (map (fn [axis] 70 | [(keyword (str "--gi-scale-" axis)) value])) 71 | axes)))} 72 | 73 | 74 | {:id :rotate 75 | :since-version [:tw 2] 76 | :rules " 77 | rotate = signus? <'rotate-'> rotate-value 78 | rotate-value = number | angle 79 | " 80 | :garden (fn [{data :component-data}] 81 | (let [{:keys [signus rotate-value]} (into {} data)] 82 | {:--gi-rotate (value-unit->css rotate-value {:signus signus 83 | :zero-unit nil 84 | :number {:unit "deg"}})}))} 85 | 86 | 87 | {:id :translate 88 | :since-version [:tw 2] 89 | :rules " 90 | translate = signus? <'translate-'> axis <'-'> translate-value 91 | translate-value = number | length | length-unit | fraction | percentage | full-100% 92 | " 93 | :garden (fn [{data :component-data 94 | :keys [unitless-length-conversion]}] 95 | (let [{:keys [signus axis translate-value]} (into {} data) 96 | attribute ({"x" :--gi-translate-x 97 | "y" :--gi-translate-y} axis)] 98 | {attribute (value-unit->css translate-value 99 | {:signus signus 100 | :zero-unit nil 101 | :number unitless-length-conversion 102 | :fraction {:unit "%" 103 | :value-fn mul-100}})}))} 104 | 105 | 106 | {:id :skew 107 | :since-version [:tw 2] 108 | :rules " 109 | skew = signus? <'skew-'> axis <'-'> skew-value 110 | skew-value = number | angle 111 | " 112 | :garden (fn [{data :component-data}] 113 | (let [{:keys [signus axis skew-value]} (into {} data) 114 | attribute ({"x" :--gi-skew-x 115 | "y" :--gi-skew-y} axis) 116 | value (value-unit->css skew-value {:signus signus 117 | :zero-unit nil 118 | :number {:unit "deg"}})] 119 | {attribute value}))}]) 120 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/util.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc girouette.util 2 | #?(:cljs (:require-macros girouette.util)) 3 | (:refer-clojure :exclude [group-by]) 4 | (:require [clojure.core :as cc])) 5 | 6 | (defmacro implies [x y] 7 | `(or (not ~x) ~y)) 8 | 9 | (defmacro comp-> [& args] 10 | `(comp ~@(reverse args))) 11 | 12 | (defn group-by 13 | "Same as clojure.core/group-by, but with some handy new arities which apply 14 | custom map & reduce operations to the elements grouped together under the same key." 15 | ([kf coll] 16 | ;(group-by kf identity conj [] coll) 17 | (cc/group-by kf coll)) 18 | ([kf vf coll] 19 | (group-by kf vf conj [] coll)) 20 | ([kf vf rf coll] 21 | (group-by kf vf rf (rf) coll)) 22 | ([kf vf rf init coll] 23 | (->> coll 24 | (reduce (fn [ret x] 25 | (let [k (kf x) 26 | v (vf x)] 27 | (assoc! ret k (rf (get ret k init) v)))) 28 | (transient {})) 29 | persistent!))) 30 | 31 | #_ (group-by first [[:a 1] [:a 2] [:b 3] [:a 4] [:b 5]]) 32 | #_ (group-by first second [[:a 1] [:a 2] [:b 3] [:a 4] [:b 5]]) 33 | #_ (group-by first second + [[:a 1] [:a 2] [:b 3] [:a 4] [:b 5]]) 34 | #_ (group-by first second + 10 [[:a 1] [:a 2] [:b 3] [:a 4] [:b 5]]) 35 | 36 | (defn index-by 37 | ([kf coll] 38 | (index-by kf identity coll)) 39 | ([kf vf coll] 40 | (->> coll 41 | (reduce (fn [ret x] 42 | (assoc! ret (kf x) (vf x))) 43 | (transient {})) 44 | persistent!))) 45 | 46 | #_ (index-by first [[:a 1] [:a 2] [:b 3] [:a 4] [:b 5]]) 47 | #_ (index-by first second [[:a 1] [:a 2] [:b 3] [:a 4] [:b 5]]) 48 | 49 | (defn into-one-vector 50 | "Flattens the outer vectors/lists/seqs/sets in the provided data and returns one vector with the data inside." 51 | [data] 52 | (if (or (sequential? data) 53 | (set? data)) 54 | (into [] (mapcat into-one-vector) data) 55 | [data])) 56 | 57 | #_ (into-one-vector 3) 58 | #_ (into-one-vector [3 4 5 6]) 59 | #_ (into-one-vector [3 [4 5 6]]) 60 | #_ (into-one-vector [[3] [[4] 5 [6]]]) 61 | #_ (into-one-vector [[3] [] [[]] [[4] [5]] 6]) 62 | -------------------------------------------------------------------------------- /lib/girouette/src/girouette/version.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.version 2 | (:require [girouette.util :as util])) 3 | 4 | (defn- version-category-= [version1 version2] 5 | (= (first version1) (first version2))) 6 | 7 | 8 | (defn- version-< [version1 version2] 9 | (loop [;; Skips the version category, e.g. `:tw` or `:gi` 10 | v1 (next version1) 11 | v2 (next version2)] 12 | (if (or (seq v1) (seq v2)) 13 | (let [x1 (or (first v1) 0) 14 | x2 (or (first v2) 0)] 15 | (cond 16 | (< x1 x2) true 17 | (> x1 x2) false 18 | :else (recur (next v1) (next v2)))) 19 | false))) 20 | 21 | 22 | (defn- version-<= [version1 version2] 23 | (loop [;; Skips the version category, e.g. `:tw` or `:gi` 24 | v1 (next version1) 25 | v2 (next version2)] 26 | (if (or (seq v1) (seq v2)) 27 | (let [x1 (or (first v1) 0) 28 | x2 (or (first v2) 0)] 29 | (cond 30 | (< x1 x2) true 31 | (> x1 x2) false 32 | :else (recur (next v1) (next v2)))) 33 | true))) 34 | 35 | 36 | (defn filter-components-by-version 37 | "Filters the `components` according to a given version." 38 | [components version] 39 | (filter (fn [component] 40 | (let [{:keys [since-version removed-in-version]} component] 41 | (and (version-category-= since-version version) 42 | (version-<= since-version version) 43 | (util/implies (some? removed-in-version) 44 | (version-< version removed-in-version))))) 45 | (util/into-one-vector components))) 46 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/garden/util_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.garden.util-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]] 4 | [girouette.tw.common :refer [dot]] 5 | [girouette.garden.util :refer [apply-class-rules]])) 6 | 7 | (deftest apply-class-rules-test 8 | (are [target-class-name gi-class-names expected-garden] 9 | (= expected-garden 10 | (apply-class-rules (dot target-class-name) 11 | (mapv tw-v3-class-name->garden gi-class-names) 12 | (mapv dot gi-class-names))) 13 | 14 | "my-class" 15 | ["p-3" 16 | "m-3" 17 | "hover:p-4" 18 | "sm:p-1" 19 | "sm:m-1" 20 | "sm:hover:p-2"] 21 | '([".my-class" {:margin "0.75rem" 22 | :padding "0.75rem"}] 23 | [".my-class" [:&:hover {:padding "1rem"}]] 24 | #garden.types.CSSAtRule {:identifier :media 25 | :value {:media-queries {:min-width "640px"} 26 | :rules [[".my-class" {:padding "0.25rem" 27 | :margin "0.25rem"}] 28 | [".my-class" [:&:hover {:padding "0.5rem"}]]]}}))) 29 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/grammar/hiccup_tag_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.grammar.hiccup-tag-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.grammar.hiccup-tag :refer [hiccup-tag-parser]])) 4 | 5 | (deftest parser-test 6 | (are [kw expected-parsed-data] 7 | (= expected-parsed-data (hiccup-tag-parser (name kw))) 8 | 9 | :div 10 | [:hiccup-tag [:html-tag "div"]] 11 | 12 | :div#app.foo 13 | [:hiccup-tag [:html-tag "div"] [:id "app"] [:class-name "foo"]] 14 | 15 | :div.foo#here 16 | [:hiccup-tag [:html-tag "div"] [:class-name "foo"] [:id "here"]] 17 | 18 | :div#app.foo#here.bar 19 | [:hiccup-tag [:html-tag "div"] [:id "app"] [:class-name "foo"] [:id "here"] [:class-name "bar"]])) 20 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/accessibility_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.accessibility-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 4 | 5 | (deftest component-test 6 | (are [class-name expected-garden] 7 | (= expected-garden (tw-v3-class-name->garden class-name)) 8 | 9 | "sr-only" 10 | [".sr-only" {:position "absolute" 11 | :width "1px" 12 | :height "1px" 13 | :padding 0 14 | :margin "-1px" 15 | :overflow "hidden" 16 | :clip "rect(0,0,0,0)" 17 | :white-space "nowrap" 18 | :border-width 0}])) 19 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/animation_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.animation-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 4 | 5 | (deftest component-test 6 | (are [class-name expected-garden] 7 | (= expected-garden (tw-v3-class-name->garden class-name)) 8 | 9 | "transition" 10 | [".transition" 11 | {:transition-property "background-color,border-color,color,fill,stroke,opacity,box-shadow,transform" 12 | :transition-timing-function "cubic-bezier(0.4,0,0.2,1)" 13 | :transition-duration "150ms"}] 14 | 15 | "transition-colors" 16 | [".transition-colors" 17 | {:transition-property "background-color,border-color,color,fill,stroke" 18 | :transition-timing-function "cubic-bezier(0.4,0,0.2,1)" 19 | :transition-duration "150ms"}] 20 | 21 | "duration-0" 22 | [".duration-0" {:transition-duration "0s"}] 23 | 24 | "duration-100" 25 | [".duration-100" {:transition-duration "100ms"}] 26 | 27 | "duration-100ms" 28 | [".duration-100ms" {:transition-duration "100ms"}] 29 | 30 | "duration-2s" 31 | [".duration-2s" {:transition-duration "2s"}] 32 | 33 | "ease-linear" 34 | [".ease-linear" {:transition-timing-function "linear"}] 35 | 36 | "ease-in" 37 | [".ease-in" {:transition-timing-function "cubic-bezier(0.4,0,1,1)"}] 38 | 39 | "delay-0" 40 | [".delay-0" {:transition-delay "0s"}] 41 | 42 | "delay-100" 43 | [".delay-100" {:transition-delay "100ms"}] 44 | 45 | "delay-100ms" 46 | [".delay-100ms" {:transition-delay "100ms"}] 47 | 48 | "delay-2s" 49 | [".delay-2s" {:transition-delay "2s"}] 50 | 51 | "animate-none" 52 | [".animate-none" {:animation "none"}] 53 | 54 | "animate-spin" 55 | [".animate-spin" 56 | [{:animation "spin 1s linear infinite"} 57 | #garden.types.CSSAtRule{:identifier :keyframes 58 | :value {:identifier "spin" 59 | :frames ([:from {:transform "rotate(0)"}] 60 | [:to {:transform "rotate(360deg)"}])}}]] 61 | 62 | "animate-ping" 63 | [".animate-ping" 64 | [{:animation "ping 1s cubic-bezier(0,0,0.2,1) infinite"} 65 | #garden.types.CSSAtRule{:identifier :keyframes 66 | :value {:identifier "ping" 67 | :frames (["75%" "100%" {:transform "scale(2)", :opacity 0}])}}]] 68 | 69 | "animate-pulse" 70 | [".animate-pulse" 71 | [{:animation "pulse 2s cubic-bezier(0.4,0,0.6,1) infinite"} 72 | #garden.types.CSSAtRule{:identifier :keyframes 73 | :value {:identifier "pulse" 74 | :frames (["0%" "100%" {:opacity 1}] ["50%" {:opacity 0.5}])}}]] 75 | 76 | "animate-bounce" 77 | [".animate-bounce" 78 | [{:animation "bounce 1s infinite"} 79 | #garden.types.CSSAtRule{:identifier :keyframes 80 | :value {:identifier "bounce" 81 | :frames (["0%" 82 | "100%" 83 | {:transform "translateY(-25%)" 84 | :animation-timing-function "cubic-bezier(0.8,0,1,1)"}] 85 | ["50%" 86 | {:transform "translateY(0)" 87 | :animation-timing-function "cubic-bezier(0,0,0.2,1)"}])}}]])) 88 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/background_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.background-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 4 | 5 | (deftest component-test 6 | (are [class-name expected-garden] 7 | (= expected-garden (tw-v3-class-name->garden class-name)) 8 | 9 | "bg-transparent" 10 | [".bg-transparent" {:background-color "transparent"}] 11 | 12 | "bg-green-300" 13 | [".bg-green-300" {:--gi-bg-opacity 1 14 | :background-color "rgba(134,239,172,var(--gi-bg-opacity))"}] 15 | 16 | "bg-repeat-round" 17 | [".bg-repeat-round" {:background-repeat "round"}] 18 | 19 | "bg-auto" 20 | [".bg-auto" {:background-size "auto"}] 21 | 22 | "bg-size-auto-auto" 23 | [".bg-size-auto-auto" {:background-size [["auto" "auto"]]}] 24 | 25 | "bg-size-10px-20px" 26 | [".bg-size-10px-20px" {:background-size [["10px" "20px"]]}] 27 | 28 | "bg-size-px-20%" 29 | [".bg-size-px-20\\%" {:background-size [["1px" "20%"]]}] 30 | 31 | "bg-none" 32 | [".bg-none" {:background-image "none"}] 33 | 34 | "bg-gradient-to-r" 35 | [".bg-gradient-to-r" {:background-image "linear-gradient(to right,var(--gi-gradient-stops))"}] 36 | 37 | "bg-gradient-to-tr" 38 | [".bg-gradient-to-tr" {:background-image "linear-gradient(to top right,var(--gi-gradient-stops))"}] 39 | 40 | "bg-origin-border" 41 | [".bg-origin-border" {:background-origin "border-box"}] 42 | 43 | "bg-origin-padding" 44 | [".bg-origin-padding" {:background-origin "padding-box"}] 45 | 46 | "bg-origin-content" 47 | [".bg-origin-content" {:background-origin "content-box"}] 48 | 49 | "from-blue-500" 50 | [".from-blue-500" {:--gi-gradient-from "#3b82f6" 51 | :--gi-gradient-stops "var(--gi-gradient-from),var(--gi-gradient-to,#3b82f600)"}] 52 | 53 | "to-red-500" 54 | [".to-red-500" {:--gi-gradient-to "#ef4444"}] 55 | 56 | "via-white" 57 | [".via-white" {:--gi-gradient-stops "var(--gi-gradient-from),#ffffff,var(--gi-gradient-to,#ffffff00)"}])) 58 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/border_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.border-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 4 | 5 | (deftest component-test 6 | (are [class-name expected-garden] 7 | (= expected-garden (tw-v3-class-name->garden class-name)) 8 | ;; Border Radius 9 | "rounded-none" 10 | [".rounded-none" {:border-radius "0px"}] 11 | 12 | "rounded" 13 | [".rounded" {:border-radius "0.25rem"}] 14 | 15 | "rounded-xl" 16 | [".rounded-xl" {:border-radius "0.75rem"}] 17 | 18 | "rounded-t-sm" 19 | [".rounded-t-sm" {:border-top-left-radius "0.125rem" 20 | :border-top-right-radius "0.125rem"}] 21 | 22 | "rounded-tl-full" 23 | [".rounded-tl-full" {:border-top-left-radius "9999px"}] 24 | 25 | ;; Border Width 26 | "border" 27 | [".border" {:border-width "1px"}] 28 | 29 | "border-2" 30 | [".border-2" {:border-width "2px"}] 31 | 32 | "border-t" 33 | [".border-t" {:border-top-width "1px"}] 34 | 35 | "border-l-4" 36 | [".border-l-4" {:border-left-width "4px"}] 37 | 38 | "border-b-2em" 39 | [".border-b-2em" {:border-bottom-width "2em"}] 40 | 41 | "border-y-2em" 42 | [".border-y-2em" {:border-top-width "2em" 43 | :border-bottom-width "2em"}] 44 | 45 | "border-3px" 46 | [".border-3px" {:border-width "3px"}] 47 | 48 | ;; Border Color 49 | "border-transparent" 50 | [".border-transparent" {:border-color "transparent"}] 51 | 52 | "border-green-300" 53 | [".border-green-300" {:--gi-border-opacity 1 54 | :border-color "rgba(134,239,172,var(--gi-border-opacity))"}] 55 | 56 | "border-t-green-300" 57 | [".border-t-green-300" 58 | {:--gi-border-opacity 1 59 | :border-top-color "rgba(134,239,172,var(--gi-border-opacity))"}] 60 | 61 | "border-y-green-300" 62 | [".border-y-green-300" 63 | {:--gi-border-opacity 1 64 | :border-bottom-color "rgba(134,239,172,var(--gi-border-opacity))" 65 | :border-top-color "rgba(134,239,172,var(--gi-border-opacity))"}] 66 | 67 | "border-b-rgba-c0ffee50" 68 | [".border-b-rgba-c0ffee50" {:border-bottom-color "#c0ffee50"}] 69 | 70 | ;; Border Opacity 71 | "border-opacity-25" 72 | [".border-opacity-25" {:--gi-border-opacity 0.25}] 73 | 74 | ;; Border Style 75 | "border-dotted" 76 | [".border-dotted" {:border-style "dotted"}] 77 | 78 | "border-hidden" 79 | [".border-hidden" {:border-style "hidden"}] 80 | 81 | ;; Divide Width 82 | "divide-x" 83 | [".divide-x" [(garden.selectors.CSSSelector. "&>:not([hidden])~:not([hidden])") 84 | {:--gi-divide-x-reverse 0 85 | :border-left-width "calc(1px * calc(1 - var(--gi-divide-x-reverse)))" 86 | :border-right-width "calc(1px * var(--gi-divide-x-reverse))"}]] 87 | 88 | "divide-y-2" 89 | [".divide-y-2" [(garden.selectors.CSSSelector. "&>:not([hidden])~:not([hidden])") 90 | {:--gi-divide-y-reverse 0 91 | :border-top-width "calc(2px * calc(1 - var(--gi-divide-y-reverse)))" 92 | :border-bottom-width "calc(2px * var(--gi-divide-y-reverse))"}]] 93 | 94 | "divide-y-reverse" 95 | [".divide-y-reverse" [(garden.selectors.CSSSelector. "&>:not([hidden])~:not([hidden])") 96 | {:--gi-divide-y-reverse 1}]] 97 | 98 | ;; Divide Color 99 | "divide-current" 100 | [".divide-current" [(garden.selectors.CSSSelector. "&>:not([hidden])~:not([hidden])") 101 | {:border-color "currentColor"}]] 102 | 103 | "divide-gray-100" 104 | [".divide-gray-100" [(garden.selectors.CSSSelector. "&>:not([hidden])~:not([hidden])") 105 | {:--gi-divide-opacity 1 106 | :border-color "rgba(243,244,246,var(--gi-divide-opacity))"}]] 107 | 108 | ;; Divide Opacity 109 | "divide-opacity-70" 110 | [".divide-opacity-70" {:--gi-divide-opacity 0.7}] 111 | 112 | ;; Divide Style 113 | "divide-double" 114 | [".divide-double" [(garden.selectors.CSSSelector. "&>:not([hidden])~:not([hidden])") 115 | {:border-style "double"}]] 116 | 117 | ;; Outline width 118 | "outline-0" 119 | [".outline-0" {:outline-width "0px"}] 120 | 121 | "outline-3" 122 | [".outline-3" {:outline-width "3px"}] 123 | 124 | "outline-1rem" 125 | [".outline-1rem" {:outline-width "1rem"}] 126 | 127 | "outline-rem" 128 | [".outline-rem" {:outline-width "1rem"}] 129 | 130 | ;; Outline color 131 | "outline-inherit" 132 | [".outline-inherit" {:outline-color "inherit"}] 133 | 134 | "outline-gray-100" 135 | [".outline-gray-100" {:outline-color "#f3f4f6"}] 136 | 137 | ;; Outline style 138 | "outline-none" 139 | [".outline-none" {:outline "2px solid transparent" 140 | :outline-offset "2px"}] 141 | 142 | "outline" 143 | [".outline" {:outline-style "solid"}] 144 | 145 | ;; Outline offset 146 | "outline-offset-0" 147 | [".outline-offset-0" {:outline-offset "0px"}] 148 | 149 | "outline-offset-2" 150 | [".outline-offset-2" {:outline-offset "2px"}] 151 | 152 | "outline-offset-2rem" 153 | [".outline-offset-2rem" {:outline-offset "2rem"}] 154 | 155 | "outline-offset-cm" 156 | [".outline-offset-cm" {:outline-offset "1cm"}] 157 | 158 | ;; Ring Width 159 | ;; TODO: Test for `* box-shadow: 0 0 #0000;` 160 | "ring" 161 | [".ring" {:box-shadow "var(--gi-ring-inset) 0 0 0 calc(3px + var(--gi-ring-offset-width)) var(--gi-ring-color)"}] 162 | "ring-4" 163 | [".ring-4" {:box-shadow "var(--gi-ring-inset) 0 0 0 calc(4px + var(--gi-ring-offset-width)) var(--gi-ring-color)"}] 164 | "ring-inset" 165 | [".ring-inset" {:--gi-ring-inset "inset"}] 166 | 167 | ;; Ring Color 168 | "ring-pink-400" 169 | [".ring-pink-400", {:--gi-ring-color "rgba(244,114,182,var(--gi-ring-opacity))"}] 170 | 171 | ;; Ring Opacity 172 | "ring-opacity-50" 173 | [".ring-opacity-50" {:--gi-ring-opacity 0.5}] 174 | 175 | ;; Ring Offset Width 176 | "ring-offset-1" 177 | [".ring-offset-1" {:--gi-ring-offset-width "1px" 178 | :box-shadow "0 0 0 var(--gi-ring-offset-width) var(--gi-ring-offset-color), var(--gi-ring-shadow)"}] 179 | 180 | ;; Ring Offset Color 181 | "ring-offset-yellow-300" 182 | [".ring-offset-yellow-300" {:--gi-ring-offset-color "rgba(253,224,71,var(--gi-ring-opacity))" 183 | :box-shadow "0 0 0 var(--gi-ring-offset-width) var(--gi-ring-offset-color), var(--gi-ring-shadow)"}])) 184 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/box_alignment_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.box-alignment-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 4 | 5 | (deftest component-test 6 | (are [class-name expected-garden] 7 | (= expected-garden (tw-v3-class-name->garden class-name)) 8 | 9 | "justify-start" 10 | [".justify-start" {:justify-content "flex-start"}] 11 | 12 | "justify-items-stretch" 13 | [".justify-items-stretch" {:justify-items "stretch"}] 14 | 15 | "justify-self-auto" 16 | [".justify-self-auto" {:justify-self "auto"}] 17 | 18 | "content-evenly" 19 | [".content-evenly" {:align-content "space-evenly"}] 20 | 21 | "items-baseline" 22 | [".items-baseline" {:align-items "baseline"}] 23 | 24 | "self-baseline" 25 | [".self-baseline" {:align-self "baseline"}] 26 | 27 | "self-center" 28 | [".self-center" {:align-self "center"}] 29 | 30 | "self-baseline" 31 | [".self-baseline" {:align-self "baseline"}] 32 | 33 | "place-content-between" 34 | [".place-content-between" {:place-content "space-between"}] 35 | 36 | "place-items-auto" 37 | [".place-items-auto" {:place-items "auto"}] 38 | 39 | "place-self-center" 40 | [".place-self-center" {:place-self "center"}])) 41 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/color_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.color-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.color :as color :refer [read-color flatten-color-map]])) 4 | 5 | (deftest read-color-test 6 | (are [color-data expected-output] 7 | (= expected-output (read-color (flatten-color-map color/tw-v2-colors) color-data)) 8 | 9 | [:color [:color-rgb "123"]] 10 | [0x11 0x22 0x33 nil] 11 | 12 | [:color [:color-rgb "112233"]] 13 | [0x11 0x22 0x33 nil] 14 | 15 | [:color [:color-rgba "1234"]] 16 | [0x11 0x22 0x33 0x44] 17 | 18 | [:color [:color-rgba "11223344"]] 19 | [0x11 0x22 0x33 0x44] 20 | 21 | [:color [:color-hsl [:number "0"] 22 | [:number "100"] 23 | [:number "50"]]] 24 | [0xff 0 0 nil] 25 | 26 | [:color [:color-hsl [:number "120"] 27 | [:number "100"] 28 | [:number "50"]]] 29 | [0 0xff 0 nil] 30 | 31 | [:color [:color-hsl [:number "-120"] 32 | [:number "100"] 33 | [:number "50"]]] 34 | [0 0 0xff nil] 35 | 36 | [:color [:color-hsla [:number "0"] 37 | [:number "100"] 38 | [:number "50"] 39 | [:number "0"]]] 40 | [0xff 0 0 0] 41 | 42 | [:color [:color-hsla [:number "0"] 43 | [:number "100"] 44 | [:number "50"] 45 | [:number "50"]]] 46 | [0xff 0 0 0x7f] 47 | 48 | [:color [:color-hsla [:number "0"] 49 | [:number "100"] 50 | [:number "50"] 51 | [:number "100"]]] 52 | [0xff 0 0 0xff] 53 | 54 | [:color [:special-color "transparent"]] 55 | "transparent" 56 | 57 | [:color [:special-color "current"]] 58 | "currentColor" 59 | 60 | [:color [:predefined-color-opacity "green-300"]] 61 | [134 239 172 nil] 62 | 63 | [:color [:predefined-color-opacity "green-300" [:integer "50"]]] 64 | [134 239 172 127] 65 | 66 | [:color [:predefined-color-opacity "green-300" [:number "95_5"]]] 67 | [134 239 172 243])) 68 | 69 | 70 | (deftest read-tw3-color-test 71 | (are [color-data expected-output] 72 | (= expected-output (read-color (flatten-color-map color/tw-v3-unified-colors) color-data)) 73 | 74 | [:color [:predefined-color-opacity "slate-300"]] 75 | [203 213 225 nil] 76 | 77 | [:color [:predefined-color-opacity "slate-300" [:integer "50"]]] 78 | [203 213 225 127] 79 | 80 | [:color [:predefined-color-opacity "slate-300" [:number "95_5"]]] 81 | [203 213 225 243])) 82 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/common_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.common-test 2 | (:require 3 | [clojure.test :refer [deftest testing is are]] 4 | [girouette.tw.common :refer [dot read-number number->double-or-int value-unit->css 5 | div-100 div-4 mul-100]] 6 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 7 | 8 | (deftest dot-test 9 | (are [input expected-output] 10 | (= expected-output (dot input)) 11 | 12 | "abc-_-de%f" ".abc-_-de\\%f" 13 | "taiwan-#1" ".taiwan-\\#1")) 14 | 15 | 16 | (deftest read-number-test 17 | (are [input expected-output] 18 | (= expected-output (read-number input)) 19 | 20 | [:integer "2"] 2 21 | [:number "2"] 2 22 | [:number "2_5"] 2.5 23 | [:number "2.5"] 2.5 24 | 25 | "2" 2 26 | "2_5" 2.5 27 | "2.5" 2.5)) 28 | 29 | 30 | (deftest number->double-or-int-test 31 | (are [input expected-output] 32 | (= expected-output (number->double-or-int input)) 33 | 34 | 2.5 2.5 35 | 5 5 36 | 37 | ;; Note: ratio numbers do not exist in CLJS. 38 | (/ 10 4) 2.5 39 | (/ 10 2) 5)) 40 | 41 | 42 | (deftest value-unit->css-test 43 | (are [data options expected-output] 44 | (= expected-output (value-unit->css data options)) 45 | 46 | [:auto] {} "auto" 47 | [:auto] {:signus "-"} "auto" 48 | [:full] {} "full" 49 | [:full] {:signus "-"} "full" 50 | 51 | [:full-100%] {} "100%" 52 | [:screen-100vw] {} "100vw" 53 | [:screen-100vh] {} "100vh" 54 | [:screen-100vh] {:signus "-"} "-100vh" 55 | 56 | [:integer "1"] {} 1 57 | [:number "1"] {} 1 58 | [:integer "1"] {:unit "foo"} "1foo" 59 | [:integer "1"] {:integer {:unit "foo"}} "1foo" 60 | [:integer "70"] {:value-fn div-100} 0.7 61 | [:integer "100"] {:value-fn div-100} 1 62 | [:number "1_5"] {} 1.5 63 | [:number "1.5"] {} 1.5 64 | [:number "1.5"] {:signus "-"} -1.5 65 | [:number "100_5"] {:value-fn div-100} 1.005 66 | [:number "1_5"] {:number {:unit "foo"}} "1.5foo" 67 | [:number "1.5"] {:number {:unit "foo"}} "1.5foo" 68 | [:number "1.5"] {:signus "-" 69 | :unit "foo"} "-1.5foo" 70 | 71 | [:length [:number "0"] "cm"] {} "0cm" 72 | [:length [:number "0"] "cm"] {:zero-unit nil} 0 73 | [:length [:number "0"] "cm"] {:length {:zero-unit nil}} 0 74 | [:length [:number "0"] "cm"] {:signus "-"} "0cm" 75 | [:length [:number "0"] "cm"] {:signus "-" 76 | :zero-unit nil} 0 77 | [:length [:number "0"] "cm"] {:zero-unit "banana"} "0banana" 78 | [:length [:number "0"] "cm"] {:signus "-" 79 | :zero-unit "banana"} "0banana" 80 | [:length [:number "1_5"] "cm"] {} "1.5cm" 81 | [:length-unit "cm"] {} "1cm" 82 | [:length-unit "cm"] {:signus "-"} "-1cm" 83 | 84 | [:percentage [:number "1_5"]] {} "1.5%" 85 | [:percentage [:number "1_5"]] {:signus "-"} "-1.5%" 86 | 87 | [:fraction [:number "5"] [:number "2_5"]] {} 2 88 | [:fraction [:number "5"] [:number "2_5"]] {:fraction {:unit "px"}} "2px" 89 | [:fraction [:number "5"] [:number "2_5"]] {:fraction {:unit "rem" 90 | :value-fn div-4}} "0.5rem" 91 | [:fraction [:number "5"] [:number "2_5"]] {:signus "-" 92 | :fraction {:unit "rem" 93 | :value-fn div-4}} "-0.5rem" 94 | [:fraction [:number "5"] [:number "2_5"]] {:fraction {:unit "%" 95 | :value-fn mul-100}} "200%" 96 | [:fraction [:number "5"] [:number "2_5"]] {:fraction {:unit "apple" 97 | :zero-unit "banana"}} "2apple" 98 | [:fraction [:number "0"] [:number "2_5"]] {:fraction {:unit "apple" 99 | :zero-unit "banana"}} "0banana" 100 | [:fraction [:number "0"] [:number "-2_5"]] {:signus "-" 101 | :fraction {:unit "apple" 102 | :zero-unit "banana"}} "0banana")) 103 | 104 | 105 | (deftest prefixes-test 106 | (are [class-name expected-garden] 107 | (= expected-garden (tw-v3-class-name->garden class-name)) 108 | 109 | "group-hover:container" 110 | [".group:hover" [".group-hover\\:container" {:width "100%"}]] 111 | 112 | "group-invalid:container" 113 | [".group:invalid" [".group-invalid\\:container" {:width "100%"}]] 114 | 115 | "group-odd:container" 116 | [".group:nth-child(odd)" [".group-odd\\:container" {:width "100%"}]] 117 | 118 | "peer-active:container" 119 | [".peer:active ~ .peer-active\\:container" {:width "100%"}] 120 | 121 | "peer-odd:container" 122 | [".peer:nth-child(odd) ~ .peer-odd\\:container" 123 | {:width "100%"}] 124 | 125 | "dark:container" 126 | #garden.types.CSSAtRule{:identifier :media 127 | :value {:media-queries {:prefers-color-scheme "dark"} 128 | :rules ([".dark\\:container" {:width "100%"}])}} 129 | 130 | "light:container" 131 | #garden.types.CSSAtRule{:identifier :media 132 | :value {:media-queries {:prefers-color-scheme "light"} 133 | :rules ([".light\\:container" {:width "100%"}])}} 134 | 135 | "motion-safe:container" 136 | #garden.types.CSSAtRule{:identifier :media 137 | :value {:media-queries {:prefers-reduced-motion "no-preference"} 138 | :rules ([".motion-safe\\:container" {:width "100%"}])}} 139 | 140 | "motion-reduce:container" 141 | #garden.types.CSSAtRule{:identifier :media 142 | :value {:media-queries {:prefers-reduced-motion "reduced"} 143 | :rules ([".motion-reduce\\:container" {:width "100%"}])}} 144 | 145 | "landscape:container" 146 | #garden.types.CSSAtRule{:identifier :media 147 | :value {:media-queries {:orientation "landscape"} 148 | :rules ([".landscape\\:container" {:width "100%"}])}} 149 | 150 | "file:container" 151 | [".file\\:container" 152 | [(keyword "&::file-selector-button") {:width "100%"}]] 153 | 154 | "file:hover:container" 155 | [".file\\:hover\\:container" 156 | [(keyword "&::file-selector-button") 157 | [:&:hover {:width "100%"}]]] 158 | 159 | "hover:file:container" 160 | [".hover\\:file\\:container" 161 | [:&:hover 162 | [(keyword "&::file-selector-button") {:width "100%"}]]] 163 | 164 | "open:bg-white" 165 | [".open\\:bg-white" 166 | [(keyword "&[open]") {:background-color "rgba(255,255,255,var(--gi-bg-opacity))" 167 | :--gi-bg-opacity 1}]] 168 | 169 | "hover:file:container" 170 | [".hover\\:file\\:container" 171 | [:&:hover 172 | [(keyword "&::file-selector-button") {:width "100%"}]]] 173 | 174 | "focus:container" 175 | [".focus\\:container" [:&:focus {:width "100%"}]] 176 | 177 | "first:container" 178 | [".first\\:container" [:&:first-child {:width "100%"}]] 179 | 180 | "last:container" 181 | [".last\\:container" [:&:last-child {:width "100%"}]] 182 | 183 | "odd:container" 184 | [".odd\\:container" [(keyword "&:nth-child(odd)") {:width "100%"}]] 185 | 186 | "even:container" 187 | [".even\\:container" [(keyword "&:nth-child(even)") {:width "100%"}]] 188 | 189 | "invalid:bg-#d66f" 190 | [".invalid\\:bg-\\#d66f" [:&:invalid {:background-color "#dd6666ff"}]] 191 | 192 | "sm:focus:container" 193 | #garden.types.CSSAtRule{:identifier :media 194 | :value {:media-queries {:min-width "640px"} 195 | :rules ([".sm\\:focus\\:container" [:&:focus {:max-width "640px"}]])}} 196 | 197 | "sm:first:focus:container" 198 | #garden.types.CSSAtRule{:identifier :media 199 | :value {:media-queries {:min-width "640px"} 200 | :rules ([".sm\\:first\\:focus\\:container" [:&:first-child [:&:focus {:max-width "640px"}]]])}} 201 | 202 | "placeholder:text-red-500" 203 | [".placeholder\\:text-red-500" [(keyword "&::placeholder") 204 | {:color "rgba(239,68,68,var(--gi-text-opacity))" 205 | :--gi-text-opacity 1}]] 206 | 207 | "before:ml-1" 208 | [".before\\:ml-1" [(keyword "&::before") {:margin-left "0.25rem"}]])) 209 | 210 | (deftest arbitrary-test 211 | (are [class-name expected-garden] 212 | (= expected-garden (tw-v3-class-name->garden class-name)) 213 | 214 | "hover:[mask-type:alpha]" 215 | [".hover\\:\\[mask-type\\:alpha\\]" 216 | [:&:hover {:mask-type "alpha"}]])) 217 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/core_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.core-test 2 | (:require 3 | [clojure.test :refer [deftest testing is are]] 4 | [girouette.tw.layout :as layout] 5 | [girouette.tw.core :as gc])) 6 | 7 | (deftest assoc-ordering-level-test 8 | (let [id->component {1 {:before-rules #{2 3}} 9 | 2 {} 10 | 3 {} 11 | 4 {:after-rules #{3}} 12 | 5 {}}] 13 | (testing "The :before-rules and :after-rules should become symmetric." 14 | (is (= (-> id->component 15 | (#'gc/complement-before-rules-after-rules)) 16 | {1 {:before-rules #{2 3}} 17 | 2 {:after-rules #{1}} 18 | 3 {:before-rules #{4} 19 | :after-rules #{1}} 20 | 4 {:after-rules #{3}} 21 | 5 {}}))) 22 | 23 | (testing "The ordering levels should be correct." 24 | (is (= (-> id->component 25 | (#'gc/complement-before-rules-after-rules) 26 | (#'gc/assoc-ordering-level)) 27 | {1 {:ordering-level 0 28 | :before-rules #{2 3}} 29 | 2 {:ordering-level 1 30 | :after-rules #{1}} 31 | 3 {:ordering-level 1 32 | :after-rules #{1} 33 | :before-rules #{4}} 34 | 4 {:ordering-level 2 35 | :after-rules #{3}} 36 | 5 {:ordering-level 0}}))))) 37 | 38 | (deftest make-api-test 39 | (testing "The API still work without any color and font family." 40 | (let [{:keys [class-name->garden]} (gc/make-api layout/components {})] 41 | (is (= [".flex" {:display "flex"}] 42 | (class-name->garden "flex")))))) 43 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/effect_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.effect-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v2-class-name->garden 4 | tw-v3-class-name->garden]])) 5 | 6 | (deftest component-v2-test 7 | (are [class-name expected-garden] 8 | (= expected-garden (tw-v2-class-name->garden class-name)) 9 | 10 | ;; Box shadow v2 11 | "shadow" 12 | [".shadow" {:--gi-shadow "0 1px 3px 0 rgba(0,0,0,0.1),0 1px 2px 0 rgba(0,0,0,0.06)" 13 | :box-shadow "var(--gi-ring-offset-shadow,0 0 #0000),var(--gi-ring-shadow,0 0 #0000),var(--gi-shadow)"}] 14 | 15 | "shadow-none" 16 | [".shadow-none" {:--gi-shadow "0 0 #0000" 17 | :box-shadow "var(--gi-ring-offset-shadow,0 0 #0000),var(--gi-ring-shadow,0 0 #0000),var(--gi-shadow)"}] 18 | 19 | "shadow-inner" 20 | [".shadow-inner" {:--gi-shadow "inset 0 2px 4px 0 rgba(0,0,0,0.06)" 21 | :box-shadow "var(--gi-ring-offset-shadow,0 0 #0000),var(--gi-ring-shadow,0 0 #0000),var(--gi-shadow)"}] 22 | 23 | "shadow-2xl" 24 | [".shadow-2xl" {:--gi-shadow "0 25px 50px -12px rgba(0,0,0,0.25)" 25 | :box-shadow "var(--gi-ring-offset-shadow,0 0 #0000),var(--gi-ring-shadow,0 0 #0000),var(--gi-shadow)"}])) 26 | 27 | 28 | ;; The v3 components which are incompatible with v2. 29 | (deftest component-v3-test 30 | (are [class-name expected-garden] 31 | (= expected-garden (tw-v3-class-name->garden class-name)) 32 | 33 | ;; Box shadow v3 34 | "shadow" 35 | [".shadow" {:--gi-shadow "0 1px 3px 0 rgba(0,0,0,0.1),0 1px 2px -1px rgba(0,0,0,0.1)" 36 | :--gi-shadow-colored "0 1px 3px 0 var(--gi-shadow-color),0 1px 2px -1px var(--gi-shadow-color)" 37 | :box-shadow "var(--gi-ring-offset-shadow,0 0 #0000),var(--gi-ring-shadow,0 0 #0000),var(--gi-shadow)"}] 38 | 39 | "shadow-none" 40 | [".shadow-none" {:--gi-shadow "0 0 #0000", 41 | :--gi-shadow-colored "0 0 var(--gi-shadow-color)", 42 | :box-shadow "var(--gi-ring-offset-shadow,0 0 #0000),var(--gi-ring-shadow,0 0 #0000),var(--gi-shadow)"}] 43 | 44 | "shadow-inner" 45 | [".shadow-inner" {:--gi-shadow "inset 0 2px 4px 0 rgba(0,0,0,0.05)", 46 | :--gi-shadow-colored "inset 0 2px 4px 0 var(--gi-shadow-color)" 47 | :box-shadow "var(--gi-ring-offset-shadow,0 0 #0000),var(--gi-ring-shadow,0 0 #0000),var(--gi-shadow)"}] 48 | 49 | "shadow-2xl" 50 | [".shadow-2xl" {:--gi-shadow "0 25px 50px -12px rgba(0,0,0,0.25)", 51 | :--gi-shadow-colored "0 25px 50px -12px var(--gi-shadow-color)" 52 | :box-shadow "var(--gi-ring-offset-shadow,0 0 #0000),var(--gi-ring-shadow,0 0 #0000),var(--gi-shadow)"}] 53 | 54 | ;; Box shadow color 55 | "shadow-inherit" 56 | [".shadow-inherit" {:--gi-shadow-color "inherit" 57 | :--gi-shadow "var(--gi-shadow-colored)"}] 58 | 59 | "shadow-current" 60 | [".shadow-current" {:--gi-shadow-color "currentColor" 61 | :--gi-shadow "var(--gi-shadow-colored)"}] 62 | 63 | "shadow-cyan-500-50" 64 | [".shadow-cyan-500-50" {:--gi-shadow-color "#06b6d47f" 65 | :--gi-shadow "var(--gi-shadow-colored)"}] 66 | 67 | "shadow-cyan-500/50" 68 | [".shadow-cyan-500\\/50" {:--gi-shadow-color "#06b6d47f" 69 | :--gi-shadow "var(--gi-shadow-colored)"}] 70 | 71 | ;; Opacity 72 | "opacity-0" 73 | [".opacity-0" {:opacity 0}] 74 | 75 | "opacity-20" 76 | [".opacity-20" {:opacity 0.2}] 77 | 78 | "opacity-100" 79 | [".opacity-100" {:opacity 1}] 80 | 81 | ;; Mix Blend Color 82 | "mix-blend-normal" 83 | [".mix-blend-normal" {:mix-blend-mode "normal"}] 84 | 85 | "mix-blend-multiply" 86 | [".mix-blend-multiply" {:mix-blend-mode "multiply"}] 87 | 88 | "mix-blend-screen" 89 | [".mix-blend-screen" {:mix-blend-mode "screen"}] 90 | 91 | "mix-blend-overlay" 92 | [".mix-blend-overlay" {:mix-blend-mode "overlay"}] 93 | 94 | "mix-blend-darken" 95 | [".mix-blend-darken" {:mix-blend-mode "darken"}] 96 | 97 | "mix-blend-lighten" 98 | [".mix-blend-lighten" {:mix-blend-mode "lighten"}] 99 | 100 | "mix-blend-color-dodge" 101 | [".mix-blend-color-dodge" {:mix-blend-mode "color-dodge"}] 102 | 103 | "mix-blend-color-burn" 104 | [".mix-blend-color-burn" {:mix-blend-mode "color-burn"}] 105 | 106 | "mix-blend-hard-light" 107 | [".mix-blend-hard-light" {:mix-blend-mode "hard-light"}] 108 | 109 | "mix-blend-soft-light" 110 | [".mix-blend-soft-light" {:mix-blend-mode "soft-light"}] 111 | 112 | "mix-blend-difference" 113 | [".mix-blend-difference" {:mix-blend-mode "difference"}] 114 | 115 | "mix-blend-exclusion" 116 | [".mix-blend-exclusion" {:mix-blend-mode "exclusion"}] 117 | 118 | "mix-blend-hue" 119 | [".mix-blend-hue" {:mix-blend-mode "hue"}] 120 | 121 | "mix-blend-saturation" 122 | [".mix-blend-saturation" {:mix-blend-mode "saturation"}] 123 | 124 | "mix-blend-color" 125 | [".mix-blend-color" {:mix-blend-mode "color"}] 126 | 127 | "mix-blend-luminosity" 128 | [".mix-blend-luminosity" {:mix-blend-mode "luminosity"}] 129 | 130 | ;; background blend mode 131 | "bg-blend-normal" 132 | [".bg-blend-normal" {:background-blend-mode "normal"}] 133 | 134 | "bg-blend-multiply" 135 | [".bg-blend-multiply" {:background-blend-mode "multiply"}] 136 | 137 | "bg-blend-screen" 138 | [".bg-blend-screen" {:background-blend-mode "screen"}] 139 | 140 | "bg-blend-overlay" 141 | [".bg-blend-overlay" {:background-blend-mode "overlay"}] 142 | 143 | "bg-blend-darken" 144 | [".bg-blend-darken" {:background-blend-mode "darken"}] 145 | 146 | "bg-blend-lighten" 147 | [".bg-blend-lighten" {:background-blend-mode "lighten"}] 148 | 149 | "bg-blend-color-dodge" 150 | [".bg-blend-color-dodge" {:background-blend-mode "color-dodge"}] 151 | 152 | "bg-blend-color-burn" 153 | [".bg-blend-color-burn" {:background-blend-mode "color-burn"}] 154 | 155 | "bg-blend-hard-light" 156 | [".bg-blend-hard-light" {:background-blend-mode "hard-light"}] 157 | 158 | "bg-blend-soft-light" 159 | [".bg-blend-soft-light" {:background-blend-mode "soft-light"}] 160 | 161 | "bg-blend-difference" 162 | [".bg-blend-difference" {:background-blend-mode "difference"}] 163 | 164 | "bg-blend-exclusion" 165 | [".bg-blend-exclusion" {:background-blend-mode "exclusion"}] 166 | 167 | "bg-blend-hue" 168 | [".bg-blend-hue" {:background-blend-mode "hue"}] 169 | 170 | "bg-blend-saturation" 171 | [".bg-blend-saturation" {:background-blend-mode "saturation"}] 172 | 173 | "bg-blend-color" 174 | [".bg-blend-color" {:background-blend-mode "color"}] 175 | 176 | "bg-blend-luminosity" 177 | [".bg-blend-luminosity" {:background-blend-mode "luminosity"}])) 178 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/filter_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.filter-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.filter :as filter] 4 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 5 | 6 | (deftest component-test 7 | (are [class-name expected-garden] 8 | (= expected-garden (tw-v3-class-name->garden class-name)) 9 | 10 | ;; Blur 11 | "blur-none" 12 | [".blur-none" {:--gi-blur "blur(0)" 13 | :filter @#'filter/filter-rule}] 14 | 15 | "blur" 16 | [".blur" {:--gi-blur "blur(8px)" 17 | :filter @#'filter/filter-rule}] 18 | 19 | "blur-lg" 20 | [".blur-lg" {:--gi-blur "blur(16px)" 21 | :filter @#'filter/filter-rule}] 22 | 23 | "blur-1rem" 24 | [".blur-1rem" {:--gi-blur "blur(1rem)" 25 | :filter @#'filter/filter-rule}] 26 | 27 | "blur-1px" 28 | [".blur-1px" {:--gi-blur "blur(1px)" 29 | :filter @#'filter/filter-rule}] 30 | 31 | ;; Brightness 32 | "brightness-0" 33 | [".brightness-0" {:--gi-brightness "brightness(0)" 34 | :filter @#'filter/filter-rule}] 35 | 36 | "brightness-75" 37 | [".brightness-75" {:--gi-brightness "brightness(0.75)" 38 | :filter @#'filter/filter-rule}] 39 | 40 | "brightness-75%" 41 | [".brightness-75\\%" {:--gi-brightness "brightness(75%)" 42 | :filter @#'filter/filter-rule}] 43 | 44 | "brightness-150" 45 | [".brightness-150" {:--gi-brightness "brightness(1.5)" 46 | :filter @#'filter/filter-rule}] 47 | 48 | ;; Contrast 49 | "contrast-0" 50 | [".contrast-0" {:--gi-contrast "contrast(0)" 51 | :filter @#'filter/filter-rule}] 52 | 53 | "contrast-75" 54 | [".contrast-75" {:--gi-contrast "contrast(0.75)" 55 | :filter @#'filter/filter-rule}] 56 | 57 | "contrast-75%" 58 | [".contrast-75\\%" {:--gi-contrast "contrast(75%)" 59 | :filter @#'filter/filter-rule}] 60 | 61 | "contrast-150" 62 | [".contrast-150" {:--gi-contrast "contrast(1.5)" 63 | :filter @#'filter/filter-rule}] 64 | 65 | ;; Drop shadow 66 | "drop-shadow" 67 | [".drop-shadow" 68 | {:--gi-drop-shadow "drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06))" 69 | :filter @#'filter/filter-rule}] 70 | 71 | "drop-shadow-lg" 72 | [".drop-shadow-lg" 73 | {:--gi-drop-shadow "drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1))" 74 | :filter @#'filter/filter-rule}] 75 | 76 | ;; Grayscale 77 | "grayscale" 78 | [".grayscale" {:--gi-grayscale "grayscale(100%)" 79 | :filter @#'filter/filter-rule}] 80 | 81 | "grayscale-0" 82 | [".grayscale-0" {:--gi-grayscale "grayscale(0)" 83 | :filter @#'filter/filter-rule}] 84 | 85 | "grayscale-1/2" 86 | [".grayscale-1\\/2" {:--gi-grayscale "grayscale(0.5)" 87 | :filter @#'filter/filter-rule}] 88 | 89 | "grayscale-50" 90 | [".grayscale-50" {:--gi-grayscale "grayscale(0.5)" 91 | :filter @#'filter/filter-rule}] 92 | 93 | "grayscale-100%" 94 | [".grayscale-100\\%" {:--gi-grayscale "grayscale(100%)" 95 | :filter @#'filter/filter-rule}] 96 | 97 | ;; Hue rotate 98 | "hue-rotate-30" 99 | [".hue-rotate-30" {:--gi-hue-rotate "hue-rotate(30deg)" 100 | :filter @#'filter/filter-rule}] 101 | 102 | "hue-rotate-30deg" 103 | [".hue-rotate-30deg" {:--gi-hue-rotate "hue-rotate(30deg)" 104 | :filter @#'filter/filter-rule}] 105 | 106 | "hue-rotate-0_5turn" 107 | [".hue-rotate-0_5turn" {:--gi-hue-rotate "hue-rotate(0.5turn)" 108 | :filter @#'filter/filter-rule}] 109 | 110 | ;; Invert 111 | "invert" 112 | [".invert" {:--gi-invert "invert(100%)" 113 | :filter @#'filter/filter-rule}] 114 | 115 | "invert-0" 116 | [".invert-0" {:--gi-invert "invert(0)" 117 | :filter @#'filter/filter-rule}])) 118 | 119 | ;; Saturate 120 | ;; Sepia 121 | 122 | ;; Backdrop blur 123 | ;; Backdrop brightness 124 | ;; Backdrop contrast 125 | ;; Backdrop gray scale 126 | ;; Backdrop hue rotate 127 | ;; Backdrop invert 128 | ;; Backdrop opacity 129 | ;; Backdrop saturate 130 | ;; Backdrop sepia 131 | 132 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/flexbox_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.flexbox-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 4 | 5 | (deftest component-test 6 | (are [class-name expected-garden] 7 | (= expected-garden (tw-v3-class-name->garden class-name)) 8 | 9 | "flex-grow" 10 | [".flex-grow" {:flex-grow 1}] 11 | 12 | "grow-0" 13 | [".grow-0" {:flex-grow 0}] 14 | 15 | "flex-grow-3" 16 | [".flex-grow-3" {:flex-grow 3}] 17 | 18 | "flex-grow-3/2" 19 | [".flex-grow-3\\/2" {:flex-grow 1.5}] 20 | 21 | "flex-shrink" 22 | [".flex-shrink" {:flex-shrink 1}] 23 | 24 | "flex-shrink-3" 25 | [".flex-shrink-3" {:flex-shrink 3}] 26 | 27 | "flex-basis" 28 | [".flex-basis" {:flex-basis 1}] 29 | 30 | "flex-basis-0" 31 | [".flex-basis-0" {:flex-basis "0px"}] 32 | 33 | "flex-basis-3" 34 | [".flex-basis-3" {:flex-basis "0.75rem"}] 35 | 36 | "flex-basis-px" 37 | [".flex-basis-px" {:flex-basis "1px"}] 38 | 39 | "flex-basis-18px" 40 | [".flex-basis-18px" {:flex-basis "18px"}] 41 | 42 | "flex-basis-auto" 43 | [".flex-basis-auto" {:flex-basis "auto"}] 44 | 45 | "flex-basis-full" 46 | [".flex-basis-full" {:flex-basis "100%"}] 47 | 48 | "flex-0" 49 | [".flex-0" {:flex "0 0 0%"}] 50 | 51 | "flex-1" 52 | [".flex-1" {:flex "1 1 0%"}] 53 | 54 | "flex-2" 55 | [".flex-2" {:flex "2 2 0%"}] 56 | 57 | "flex-4/5" 58 | [".flex-4\\/5" {:flex "0.8 0.8 0%"}] 59 | 60 | "flex-auto" 61 | [".flex-auto" {:flex "1 1 auto"}] 62 | 63 | "flex-initial" 64 | [".flex-initial" {:flex "0 1 auto"}] 65 | 66 | "flex-2-3" 67 | [".flex-2-3" {:flex "2 3"}] 68 | 69 | "flex-2-3%" 70 | [".flex-2-3\\%" {:flex "2 3%"}] 71 | 72 | "flex-2-px" 73 | [".flex-2-px" {:flex "2 1px"}] 74 | 75 | "flex-2-3-4" 76 | [".flex-2-3-4" {:flex "2 3 1rem"}] 77 | 78 | "flex-none" 79 | [".flex-none" {:flex "none"}] 80 | 81 | "flex-col-reverse" 82 | [".flex-col-reverse" {:flex-direction "column-reverse"}] 83 | 84 | "flex-nowrap" 85 | [".flex-nowrap" {:flex-wrap "nowrap"}] 86 | 87 | "order-none" 88 | [".order-none" {:order 0}] 89 | 90 | "-order-1" 91 | [".-order-1" {:order -1}] 92 | 93 | "order-1" 94 | [".order-1" {:order 1}])) 95 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/grid_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.grid-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 4 | 5 | (deftest component-test 6 | (are [class-name expected-garden] 7 | (= expected-garden (tw-v3-class-name->garden class-name)) 8 | 9 | "grid-cols-none" 10 | [".grid-cols-none" {:grid-template-columns "none"}] 11 | "grid-cols-0" 12 | [".grid-cols-0" {:grid-template-columns "none"}] 13 | "grid-cols-10" 14 | [".grid-cols-10" {:grid-template-columns "repeat(10, minmax(0, 1fr))"}] 15 | 16 | "col-auto" 17 | [".col-auto" {:grid-column "auto"}] 18 | 19 | "col-span-5" 20 | [".col-span-5" {:grid-column "span 5 / span 5"}] 21 | "col-span-full" 22 | [".col-span-full" {:grid-column "-1 / 1"}] 23 | 24 | "col-start-1" 25 | [".col-start-1" {:grid-column-start 1}] 26 | "col-start-auto" 27 | [".col-start-auto" {:grid-column-start "auto"}] 28 | 29 | "col-end-1" 30 | [".col-end-1" {:grid-column-end 1}] 31 | "col-end-auto" 32 | [".col-end-auto" {:grid-column-end "auto"}] 33 | 34 | "grid-rows-none" 35 | [".grid-rows-none" {:grid-template-rows "none"}] 36 | "grid-rows-0" 37 | [".grid-rows-0" {:grid-template-rows "none"}] 38 | "grid-rows-10" 39 | [".grid-rows-10" {:grid-template-rows "repeat(10, minmax(0, 1fr))"}] 40 | 41 | "row-auto" 42 | [".row-auto" {:grid-row "auto"}] 43 | 44 | "row-span-5" 45 | [".row-span-5" {:grid-row "span 5 / span 5"}] 46 | "row-span-full" 47 | [".row-span-full" {:grid-row "-1 / 1"}] 48 | 49 | "row-start-1" 50 | [".row-start-1" {:grid-row-start 1}] 51 | "row-start-auto" 52 | [".row-start-auto" {:grid-row-start "auto"}] 53 | 54 | "row-end-1" 55 | [".row-end-1" {:grid-row-end 1}] 56 | "row-end-auto" 57 | [".row-end-auto" {:grid-row-end "auto"}] 58 | 59 | "grid-flow-row" 60 | [".grid-flow-row" {:grid-auto-flow "row"}] 61 | "grid-flow-col-dense" 62 | [".grid-flow-col-dense" {:grid-auto-flow "column dense"}] 63 | 64 | "auto-cols-auto" 65 | [".auto-cols-auto" {:grid-auto-columns "auto"}] 66 | "auto-cols-min" 67 | [".auto-cols-min" {:grid-auto-columns "min-content"}] 68 | 69 | "auto-rows-auto" 70 | [".auto-rows-auto" {:grid-auto-rows "auto"}] 71 | "auto-rows-min" 72 | [".auto-rows-min" {:grid-auto-rows "min-content"}] 73 | 74 | "gap-0" 75 | [".gap-0" {:gap 0}] 76 | "gap-x-0" 77 | [".gap-x-0" {:column-gap 0}] 78 | 79 | "gap-1" 80 | [".gap-1" {:gap "0.25rem"}] 81 | "gap-y-1" 82 | [".gap-y-1" {:row-gap "0.25rem"}] 83 | 84 | "gap-px" 85 | [".gap-px" {:gap "1px"}] 86 | "gap-y-px" 87 | [".gap-y-px" {:row-gap "1px"}] 88 | 89 | "gap-10%" 90 | [".gap-10\\%" {:gap "10%"}] 91 | "gap-y-10%" 92 | [".gap-y-10\\%" {:row-gap "10%"}])) 93 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/interactivity_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.interactivity-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v2-class-name->garden 4 | tw-v3-class-name->garden]])) 5 | 6 | (deftest component-v2-test 7 | (are [class-name expected-garden] 8 | (= expected-garden (tw-v2-class-name->garden class-name)) 9 | 10 | "outline-none" 11 | [".outline-none" {:outline "2px solid transparent" 12 | :outline-offset "2px"}])) 13 | 14 | 15 | (deftest component-v3-test 16 | (are [class-name expected-garden] 17 | (= expected-garden (tw-v3-class-name->garden class-name)) 18 | 19 | "accent-current" 20 | [".accent-current" {:accent-color "currentColor"}] 21 | 22 | "accent-#abcdef11" 23 | [".accent-\\#abcdef11" {:accent-color "#abcdef11"}] 24 | 25 | "accent-auto" 26 | [".accent-auto" {:accent-color "auto"}] 27 | 28 | "appearance-none" 29 | [".appearance-none" {:appearance "none"}] 30 | 31 | "cursor-help" 32 | [".cursor-help" {:cursor "help"}] 33 | 34 | "cursor-wait" 35 | [".cursor-wait" {:cursor "wait"}] 36 | 37 | "pointer-events-none" 38 | [".pointer-events-none" {:pointer-events "none"}] 39 | 40 | "resize" 41 | [".resize" {:resize "both"}] 42 | 43 | "resize-x" 44 | [".resize-x" {:resize "horizontal"}] 45 | 46 | "scroll-auto" 47 | [".scroll-auto" {:scroll-behavior "auto"}] 48 | 49 | "scroll-smooth" 50 | [".scroll-smooth" {:scroll-behavior "smooth"}] 51 | 52 | ;; Scroll margin 53 | "scroll-m-8" 54 | [".scroll-m-8" {:scroll-margin "2rem"}] 55 | 56 | "scroll-mt-8" 57 | [".scroll-mt-8" {:scroll-margin-top "2rem"}] 58 | 59 | "scroll-ml-0" 60 | [".scroll-ml-0" {:scroll-margin-left "0px"}] 61 | 62 | "scroll-my-11vh" 63 | [".scroll-my-11vh" {:scroll-margin-top "11vh" 64 | :scroll-margin-bottom "11vh"}] 65 | 66 | "scroll-mx-8" 67 | [".scroll-mx-8" {:scroll-margin-left "2rem" 68 | :scroll-margin-right "2rem"}] 69 | 70 | ;; Scroll padding 71 | "scroll-p-8" 72 | [".scroll-p-8" {:scroll-padding "2rem"}] 73 | 74 | "scroll-p-10%" 75 | [".scroll-p-10\\%" {:scroll-padding "10%"}] 76 | 77 | "scroll-p-1/2" 78 | [".scroll-p-1\\/2" {:scroll-padding "50%"}] 79 | 80 | "scroll-pt-8" 81 | [".scroll-pt-8" {:scroll-padding-top "2rem"}] 82 | 83 | "scroll-px-8" 84 | [".scroll-px-8" {:scroll-padding-left "2rem" 85 | :scroll-padding-right "2rem"}] 86 | 87 | ;; Scroll snap align 88 | "snap-start" 89 | [".snap-start" {:scroll-snap-align "start"}] 90 | 91 | "snap-align-none" 92 | [".snap-align-none" {:scroll-snap-align "none"}] 93 | 94 | ;; Scroll snap stop 95 | "snap-normal" 96 | [".snap-normal" {:scroll-snap-stop "normal"}] 97 | 98 | ;; Scroll snap type 99 | "snap-x" 100 | [".snap-x" {:scroll-snap-type "x var(--gi-scroll-snap-strictness)"}] 101 | 102 | "snap-mandatory" 103 | [".snap-mandatory" {:--gi-scroll-snap-strictness "mandatory"}] 104 | 105 | ;; Touch action 106 | "touch-auto" 107 | [".touch-auto" {:touch-action "auto"}] 108 | 109 | "touch-manipulation" 110 | [".touch-manipulation" {:touch-action "manipulation"}] 111 | 112 | ;; User select 113 | "select-all" 114 | [".select-all" {:user-select "all"}] 115 | 116 | ;; Will change 117 | "will-change-auto" 118 | [".will-change-auto" {:will-change "auto"}] 119 | 120 | "will-change-scroll" 121 | [".will-change-scroll" {:will-change "scroll-position"}])) 122 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/layout_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.layout-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 4 | 5 | (deftest component-test 6 | (are [class-name expected-garden] 7 | (= expected-garden (tw-v3-class-name->garden class-name)) 8 | 9 | ;; Aspect Ratio 10 | "aspect-auto" 11 | [".aspect-auto" {:aspect-ratio "auto"}] 12 | 13 | "aspect-square" 14 | [".aspect-square" {:aspect-ratio "1 / 1"}] 15 | 16 | "aspect-video" 17 | [".aspect-video" {:aspect-ratio "16 / 9"}] 18 | 19 | "aspect-23/57" 20 | [".aspect-23\\/57" {:aspect-ratio "23 / 57"}] 21 | 22 | ;; Container 23 | "container" 24 | [".container" {:width "100%"}] 25 | 26 | "sm:container" 27 | #garden.types.CSSAtRule {:identifier :media 28 | :value {:media-queries {:min-width "640px"} 29 | :rules ([".sm\\:container" {:max-width "640px"}])}} 30 | 31 | "md:container" 32 | #garden.types.CSSAtRule {:identifier :media 33 | :value {:media-queries {:min-width "768px"} 34 | :rules ([".md\\:container" {:max-width "768px"}])}} 35 | 36 | ;; Columns 37 | "columns-4" 38 | [".columns-4" {:columns "4"}] 39 | 40 | "columns-auto" 41 | [".columns-auto" {:columns "auto"}] 42 | 43 | "columns-2xl" 44 | [".columns-2xl" {:columns "42rem"}] 45 | 46 | "columns-2-auto" 47 | [".columns-2-auto" {:columns "2 auto"}] 48 | 49 | "columns-2-300px" 50 | [".columns-2-300px" {:columns "2 300px"}] 51 | 52 | "columns-auto-auto" 53 | [".columns-auto-auto" {:columns "auto auto"}] 54 | 55 | ;; Break after 56 | "break-after-auto" 57 | [".break-after-auto" {:break-after "auto"}] 58 | 59 | ;; Break before 60 | "break-before-page" 61 | [".break-before-page" {:break-before "page"}] 62 | 63 | ;; Break inside 64 | "break-inside-avoid-column" 65 | [".break-inside-avoid-column" {:break-inside "avoid-column"}] 66 | 67 | ;; Box decoration break 68 | "box-decoration-clone" 69 | [".box-decoration-clone" {:box-decoration-break "clone"}] 70 | 71 | "box-decoration-slice" 72 | [".box-decoration-slice" {:box-decoration-break "slice"}] 73 | 74 | ;; Box sizing 75 | "box-content" 76 | [".box-content" {:box-sizing "content-box"}] 77 | 78 | ;; Display 79 | "flex" 80 | [".flex" {:display "flex"}] 81 | 82 | "sm:flex" 83 | #garden.types.CSSAtRule {:identifier :media 84 | :value {:media-queries {:min-width "640px"} 85 | :rules ([".sm\\:flex" 86 | {:display "flex"}])}} 87 | 88 | "inline-table" 89 | [".inline-table" {:display "inline-table"}] 90 | 91 | "list-item" 92 | [".list-item" {:display "list-item"}] 93 | 94 | "hidden" 95 | [".hidden" {:display "none"}] 96 | 97 | ;; Floats 98 | 99 | ;; Clear 100 | 101 | ;; Isolation 102 | "isolate" 103 | [".isolate" {:isolation "isolate"}] 104 | 105 | "isolation-auto" 106 | [".isolation-auto" {:isolation "auto"}] 107 | 108 | ;; Object fit 109 | 110 | ;; Object position 111 | "object-left-top" 112 | [".object-left-top" {:object-position "left top"}] 113 | 114 | ;; Overflow 115 | "overflow-hidden" 116 | [".overflow-hidden" {:overflow "hidden"}] 117 | 118 | "overflow-clip" 119 | [".overflow-clip" {:overflow "clip"}] 120 | 121 | "overflow-x-auto" 122 | [".overflow-x-auto" {:overflow-x "auto"}] 123 | 124 | "overflow-x-clip" 125 | [".overflow-x-clip" {:overflow-x "clip"}] 126 | 127 | ;; Overscroll behavior 128 | "overscroll-x-auto" 129 | [".overscroll-x-auto" {:overscroll-x "auto"}] 130 | 131 | "overscroll-none" 132 | [".overscroll-none" {:overscroll "none"}] 133 | 134 | ;; Position 135 | 136 | ;; (Positioning) Top / Right / Bottom / Left 137 | "-inset-3/8" 138 | [".-inset-3\\/8" 139 | {:bottom "-37.5%" 140 | :left "-37.5%" 141 | :right "-37.5%" 142 | :top "-37.5%"}] 143 | 144 | "inset-x-auto" 145 | [".inset-x-auto" {:left "auto" 146 | :right "auto"}] 147 | 148 | "top-0" 149 | [".top-0" {:top 0}] 150 | 151 | "top-0.5" 152 | [".top-0\\.5" {:top "0.125rem"}] 153 | 154 | "top-0_5" 155 | [".top-0_5" {:top "0.125rem"}] 156 | 157 | "top-1" 158 | [".top-1" {:top "0.25rem"}] 159 | 160 | "top-cm" 161 | [".top-cm" {:top "1cm"}] 162 | 163 | "top-1px" 164 | [".top-1px" {:top "1px"}] 165 | 166 | "top-1rem" 167 | [".top-1rem" {:top "1rem"}] 168 | 169 | "top-1%" 170 | [".top-1\\%" {:top "1%"}] 171 | 172 | "top-3/8" 173 | [".top-3\\/8" {:top "37.5%"}] 174 | 175 | "top-full" 176 | [".top-full" {:top "100%"}] 177 | 178 | ;; Visibility 179 | "invisible" 180 | [".invisible" {:visibility "hidden"}] 181 | 182 | ;; Z-index 183 | "z-0" 184 | [".z-0" {:z-index 0}] 185 | 186 | "z-1" 187 | [".z-1" {:z-index 1}] 188 | 189 | "z-auto" 190 | [".z-auto" {:z-index "auto"}])) 191 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/sizing_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.sizing-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 4 | 5 | (deftest component-test 6 | (are [class-name expected-garden] 7 | (= expected-garden (tw-v3-class-name->garden class-name)) 8 | 9 | "w-10" 10 | [".w-10" {:width "2.5rem"}] 11 | 12 | "w-px" 13 | [".w-px" {:width "1px"}] 14 | 15 | "w-30%" 16 | [".w-30\\%" {:width "30%"}] 17 | 18 | "w-1/4" 19 | [".w-1\\/4" {:width "25%"}] 20 | 21 | "w-auto" 22 | [".w-auto" {:width "auto"}] 23 | 24 | "w-full" 25 | [".w-full" {:width "100%"}] 26 | 27 | "w-screen" 28 | [".w-screen" {:width "100vw"}] 29 | 30 | "w-min" 31 | [".w-min" {:width "min-content"}] 32 | 33 | "w-max" 34 | [".w-max" {:width "max-content"}] 35 | 36 | "min-w-auto" 37 | [".min-w-auto" {:min-width "auto"}] 38 | 39 | "min-w-full" 40 | [".min-w-full" {:min-width "100%"}] 41 | 42 | "min-w-min" 43 | [".min-w-min" {:min-width "min-content"}] 44 | 45 | "min-w-max" 46 | [".min-w-max" {:min-width "max-content"}] 47 | 48 | "max-w-none" 49 | [".max-w-none" {:max-width "none"}] 50 | 51 | "max-w-full" 52 | [".max-w-full" {:max-width "100%"}] 53 | 54 | "max-w-min" 55 | [".max-w-min" {:max-width "min-content"}] 56 | 57 | "max-w-max" 58 | [".max-w-max" {:max-width "max-content"}] 59 | 60 | "max-w-lg" 61 | [".max-w-lg" {:max-width "32rem"}] 62 | 63 | "max-w-prose" 64 | [".max-w-prose" {:max-width "65ch"}] 65 | 66 | "max-w-16rem" 67 | [".max-w-16rem" {:max-width "16rem"}] 68 | 69 | "max-w-screen-sm" 70 | [".max-w-screen-sm" {:max-width "640px"}] 71 | 72 | "max-h-64" 73 | [".max-h-64" {:max-height "16rem"}] 74 | 75 | "max-w-fit" 76 | [".max-w-fit" {:max-width "fit-content"}] 77 | 78 | "h-fit" 79 | [".h-fit" {:height "fit-content"}])) 80 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/spacing_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.spacing-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 4 | 5 | (deftest component-test 6 | (are [class-name expected-garden] 7 | (= expected-garden (tw-v3-class-name->garden class-name)) 8 | 9 | "p-0" 10 | [".p-0" {:padding 0}] 11 | 12 | "-p-0" 13 | [".-p-0" {:padding 0}] 14 | 15 | "p-1" 16 | [".p-1" {:padding "0.25rem"}] 17 | 18 | "-p-1" 19 | [".-p-1" {:padding "-0.25rem"}] 20 | 21 | "p-2" 22 | [".p-2" {:padding "0.5rem"}] 23 | 24 | "p-px" 25 | [".p-px" {:padding "1px"}] 26 | 27 | "-p-px" 28 | [".-p-px" {:padding "-1px"}] 29 | 30 | "px-2" 31 | [".px-2" {:padding-left "0.5rem" 32 | :padding-right "0.5rem"}] 33 | 34 | "py-3" 35 | [".py-3" {:padding-top "0.75rem" 36 | :padding-bottom "0.75rem"}] 37 | 38 | "pt-1" 39 | [".pt-1" {:padding-top "0.25rem"}] 40 | 41 | "m-40" 42 | [".m-40" {:margin "10rem"}] 43 | 44 | "mx-auto" 45 | [".mx-auto" {:margin-left "auto" 46 | :margin-right "auto"}] 47 | 48 | "-mx-2" 49 | [".-mx-2" {:margin-left "-0.5rem" 50 | :margin-right "-0.5rem"}] 51 | 52 | "space-x-2" 53 | [".space-x-2" [(garden.selectors.CSSSelector. "&>:not([hidden])~:not([hidden])") {:margin-left "0.5rem"}]] 54 | 55 | "space-y-2" 56 | [".space-y-2" [(garden.selectors.CSSSelector. "&>:not([hidden])~:not([hidden])") {:margin-top "0.5rem"}]] 57 | 58 | "space-x-2-reverse" 59 | [".space-x-2-reverse" [(garden.selectors.CSSSelector. "&>:not([hidden])~:not([hidden])") {:margin-right "0.5rem"}]] 60 | 61 | "space-y-2-reverse" 62 | [".space-y-2-reverse" [(garden.selectors.CSSSelector. "&>:not([hidden])~:not([hidden])") {:margin-bottom "0.5rem"}]])) 63 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/svg_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.svg-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 4 | 5 | (deftest component-test 6 | (are [class-name expected-garden] 7 | (= expected-garden (tw-v3-class-name->garden class-name)) 8 | 9 | "fill-current" 10 | [".fill-current" {:fill "currentColor"}] 11 | 12 | "stroke-current" 13 | [".stroke-current" {:stroke "currentColor"}] 14 | 15 | "stroke-0" 16 | [".stroke-0" {:stroke-width 0}] 17 | 18 | "stroke-3_5" 19 | [".stroke-3_5" {:stroke-width 3.5}])) 20 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/table_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.table-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 4 | 5 | (deftest component-test 6 | (are [class-name expected-garden] 7 | (= expected-garden (tw-v3-class-name->garden class-name)) 8 | 9 | "border-separate" 10 | [".border-separate" {:border-collapse "separate"}] 11 | 12 | "table-auto" 13 | [".table-auto" {:table-layout "auto"}])) 14 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/transform_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.transform-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 4 | 5 | (deftest component-test 6 | (are [class-name expected-garden] 7 | (= expected-garden (tw-v3-class-name->garden class-name)) 8 | 9 | "transform" 10 | [".transform" {:--gi-rotate 0 11 | :--gi-scale-x 1 12 | :--gi-scale-y 1 13 | :--gi-skew-x 0 14 | :--gi-skew-y 0 15 | :--gi-translate-x 0 16 | :--gi-translate-y 0 17 | :transform (str "translateX(var(--gi-translate-x)) " 18 | "translateY(var(--gi-translate-y)) " 19 | "rotate(var(--gi-rotate)) " 20 | "skewX(var(--gi-skew-x)) " 21 | "skewY(var(--gi-skew-y)) " 22 | "scaleX(var(--gi-scale-x)) " 23 | "scaleY(var(--gi-scale-y))")}] 24 | 25 | "transform-none" 26 | [".transform-none" {:transform "none"}] 27 | 28 | "origin-top-right" 29 | [".origin-top-right" {:transform-origin "top right"}] 30 | 31 | "scale-10" 32 | [".scale-10" {:--gi-scale-x 0.1 33 | :--gi-scale-y 0.1}] 34 | 35 | "scale-x-20" 36 | [".scale-x-20" {:--gi-scale-x 0.2}] 37 | 38 | "-scale-y-30" 39 | [".-scale-y-30" {:--gi-scale-y -0.3}] 40 | 41 | "rotate-0" 42 | [".rotate-0" {:--gi-rotate 0}] 43 | 44 | "rotate-45" 45 | [".rotate-45" {:--gi-rotate "45deg"}] 46 | 47 | "-rotate-45" 48 | [".-rotate-45" {:--gi-rotate "-45deg"}] 49 | 50 | "-rotate-2turn" 51 | [".-rotate-2turn" {:--gi-rotate "-2turn"}] 52 | 53 | "translate-x-0" 54 | [".translate-x-0" {:--gi-translate-x 0}] 55 | 56 | "translate-x-1" 57 | [".translate-x-1" {:--gi-translate-x "0.25rem"}] 58 | 59 | "translate-x-1_5" 60 | [".translate-x-1_5" {:--gi-translate-x "0.375rem"}] 61 | 62 | "translate-x-px" 63 | [".translate-x-px" {:--gi-translate-x "1px"}] 64 | 65 | "translate-y-1/2" 66 | [".translate-y-1\\/2" {:--gi-translate-y "50%"}] 67 | 68 | "translate-y-20%" 69 | [".translate-y-20\\%" {:--gi-translate-y "20%"}] 70 | 71 | "translate-y-full" 72 | [".translate-y-full" {:--gi-translate-y "100%"}] 73 | 74 | "skew-x-0" 75 | [".skew-x-0" {:--gi-skew-x 0}] 76 | 77 | "skew-y-10" 78 | [".skew-y-10" {:--gi-skew-y "10deg"}] 79 | 80 | "-skew-y-10" 81 | [".-skew-y-10" {:--gi-skew-y "-10deg"}] 82 | 83 | "-skew-y-0_5rad" 84 | [".-skew-y-0_5rad" {:--gi-skew-y "-0.5rad"}])) 85 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/tw/typography_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.tw.typography-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [girouette.tw.typography :refer [placeholder-pseudo-element]] 4 | [girouette.tw.default-api :refer [tw-v3-class-name->garden]])) 5 | 6 | (deftest component-test 7 | (are [class-name expected-garden] 8 | (= expected-garden (tw-v3-class-name->garden class-name)) 9 | 10 | ;; Font family 11 | "font-sans" 12 | [".font-sans" {: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\""}] 13 | 14 | ;; Font size 15 | "text-sm" 16 | [".text-sm" {:font-size (str 0.875 "rem") 17 | :line-height (str 1.25 "rem")}] 18 | 19 | "text-5xl" 20 | [".text-5xl" {:font-size (str 3 "rem") 21 | :line-height (str 1)}] 22 | 23 | ;; Font size 2 24 | "font-size-10" 25 | [".font-size-10" {:font-size "2.5rem"}] 26 | 27 | "font-size-10%" 28 | [".font-size-10\\%" {:font-size "10%"}] 29 | 30 | "font-size-10vw" 31 | [".font-size-10vw" {:font-size "10vw"}] 32 | 33 | ;; Font smoothing 34 | "antialiased" 35 | [".antialiased" {:-webkit-font-smoothing "antialiased" 36 | :-moz-osx-font-smoothing "grayscale"}] 37 | 38 | ;; Font style 39 | "italic" 40 | [".italic" {:font-style "italic"}] 41 | 42 | ;; Font weight 43 | "font-thin" 44 | [".font-thin" {:font-weight 100}] 45 | 46 | ;; Font variant numeric 47 | 48 | ;; Letter spacing 49 | "tracking-wide" 50 | [".tracking-wide" {:letter-spacing (str 0.025 "em")}] 51 | 52 | ;; Line height 53 | "leading-0" 54 | [".leading-0" {:line-height 0}] 55 | 56 | "leading-3" 57 | [".leading-3" {:line-height (str 0.75 "rem")}] 58 | 59 | "leading-normal" 60 | [".leading-normal" {:line-height 1.5}] 61 | 62 | ;; Line height 2 63 | "line-height-0" 64 | [".line-height-0" {:line-height 0}] 65 | 66 | "line-height-0pt" 67 | [".line-height-0pt" {:line-height "0pt"}] 68 | 69 | "line-height-1_2" 70 | [".line-height-1_2" {:line-height 1.2}] 71 | 72 | "line-height-1/2" 73 | [".line-height-1\\/2" {:line-height "50%"}] 74 | 75 | "line-height-80%" 76 | [".line-height-80\\%" {:line-height "80%"}] 77 | 78 | ;; List style type 79 | 80 | ;; List style position 81 | 82 | ;; Placeholder color 83 | "placeholder-current" 84 | [".placeholder-current" [placeholder-pseudo-element 85 | {:color "currentColor"}]] 86 | 87 | "placeholder-green-300" 88 | [".placeholder-green-300" [placeholder-pseudo-element 89 | {:--gi-placeholder-opacity 1 90 | :color "rgba(134,239,172,var(--gi-placeholder-opacity))"}]] 91 | 92 | "placeholder-green-300-50" 93 | [".placeholder-green-300-50" [placeholder-pseudo-element 94 | {:color "#86efac7f"}]] 95 | 96 | "placeholder-green-300/50" 97 | [".placeholder-green-300\\/50" [placeholder-pseudo-element 98 | {:color "#86efac7f"}]] 99 | 100 | ;; Placeholder opacity 101 | "placeholder-opacity-20" 102 | [".placeholder-opacity-20" {:--gi-placeholder-opacity 0.2}] 103 | 104 | ;; Text align 105 | 106 | ;; Text color 107 | "text-black" 108 | [".text-black" {:--gi-text-opacity 1 109 | :color "rgba(0,0,0,var(--gi-text-opacity))"}] 110 | 111 | "text-current" 112 | [".text-current" {:color "currentColor"}] 113 | 114 | "text-green-300" 115 | [".text-green-300" {:--gi-text-opacity 1 116 | :color "rgba(134,239,172,var(--gi-text-opacity))"}] 117 | 118 | "text-green-300-50" 119 | [".text-green-300-50" {:color "#86efac7f"}] 120 | 121 | "text-#733" 122 | [".text-\\#733" {:--gi-text-opacity 1 123 | :color "rgba(119,51,51,var(--gi-text-opacity))"}] 124 | 125 | "text-#7338" 126 | [".text-\\#7338" {:color "#77333388"}] 127 | 128 | "text-#703030ff" 129 | [".text-\\#703030ff" {:color "#703030ff"}] 130 | 131 | "text-rgb-733" 132 | [".text-rgb-733" {:--gi-text-opacity 1 133 | :color "rgba(119,51,51,var(--gi-text-opacity))"}] 134 | 135 | "text-rgba-7338" 136 | [".text-rgba-7338" {:color "#77333388"}] 137 | 138 | "text-rgba-703030ff" 139 | [".text-rgba-703030ff" {:color "#703030ff"}] 140 | 141 | "text-hsl-0-100-50" 142 | [".text-hsl-0-100-50" {:--gi-text-opacity 1 143 | :color "rgba(255,0,0,var(--gi-text-opacity))"}] 144 | 145 | "text-hsla-0-100-50-50" 146 | [".text-hsla-0-100-50-50" {:color "#ff00007f"}] 147 | 148 | ;; Text opacity 149 | "text-opacity-0" 150 | [".text-opacity-0" {:--gi-text-opacity 0}] 151 | 152 | "text-opacity-5" 153 | [".text-opacity-5" {:--gi-text-opacity 0.05}] 154 | 155 | "text-opacity-100" 156 | [".text-opacity-100" {:--gi-text-opacity 1}] 157 | 158 | ;; Text decoration 159 | "underline" 160 | [".underline" {:text-decoration-line "underline"}] 161 | 162 | ;; Text decoration color 163 | "decoration-inherit" 164 | [".decoration-inherit" {:text-decoration-color "inherit"}] 165 | 166 | "decoration-orange-100" 167 | [".decoration-orange-100" {:text-decoration-color "#ffedd5"}] 168 | 169 | "decoration-#abcdef" 170 | [".decoration-\\#abcdef" {:text-decoration-color "#abcdef"}] 171 | 172 | ;; Text decoration style 173 | "decoration-wavy" 174 | [".decoration-wavy" {:text-decoration-style "wavy"}] 175 | 176 | ;; Text decoration thickness 177 | "decoration-auto" 178 | [".decoration-auto" {:text-decoration-thickness "auto"}] 179 | 180 | "decoration-from-font" 181 | [".decoration-from-font" {:text-decoration-thickness "from-font"}] 182 | 183 | "decoration-3" 184 | [".decoration-3" {:text-decoration-thickness "3px"}] 185 | 186 | "decoration-1rem" 187 | [".decoration-1rem" {:text-decoration-thickness "1rem"}] 188 | 189 | "decoration-1/5" 190 | [".decoration-1\\/5" {:text-decoration-thickness "20%"}] 191 | 192 | ;; Text underline offset 193 | "underline-auto" 194 | [".underline-auto" {:text-underline-offset "auto"}] 195 | 196 | "underline-3" 197 | [".underline-3" {:text-underline-offset "3px"}] 198 | 199 | "underline-1rem" 200 | [".underline-1rem" {:text-underline-offset "1rem"}] 201 | 202 | "underline-1/5" 203 | [".underline-1\\/5" {:text-underline-offset "20%"}] 204 | 205 | ;; Text transform 206 | 207 | ;; Text overflow 208 | "text-clip" 209 | [".text-clip" {:text-overflow "clip"}] 210 | 211 | ;; Text indent 212 | "indent-0" 213 | [".indent-0" {:text-indent "0px"}] 214 | 215 | "indent-1" 216 | [".indent-1" {:text-indent "0.25rem"}] 217 | 218 | "indent-px" 219 | [".indent-px" {:text-indent "1px"}] 220 | 221 | "indent-2em" 222 | [".indent-2em" {:text-indent "2em"}] 223 | 224 | ;; Vertical alignment 225 | "align-sub" 226 | [".align-sub" {:vertical-align "sub"}] 227 | 228 | ;; Whitespace control 229 | 230 | ;; Word break 231 | 232 | ;; Content 233 | 234 | "content-['foo']" 235 | [".content-\\[\\'foo\\'\\]" {:content "\"foo\""}] 236 | 237 | "content-['foo\\'s']" 238 | [".content-\\[\\'foo\\\\\\'s\\'\\]" {:content "\"foo's\""}] 239 | 240 | "content-['foo_bar']" 241 | [".content-\\[\\'foo_bar\\'\\]" {:content "\"foo bar\""}] 242 | 243 | "content-['foo\\_bar']" 244 | [".content-\\[\\'foo\\\\_bar\\'\\]" {:content "\"foo_bar\""}] 245 | 246 | "content-[attr(foo)]" 247 | [".content-\\[attr\\(foo\\)\\]" {:content "attr(foo)"}])) 248 | -------------------------------------------------------------------------------- /lib/girouette/test/girouette/version_test.cljc: -------------------------------------------------------------------------------- 1 | (ns girouette.version-test 2 | (:require 3 | [clojure.test :refer [deftest testing is are]] 4 | [girouette.version :as gv])) 5 | 6 | (deftest version-<-test 7 | (is (false? (#'gv/version-< [:tw 2] [:tw 1]))) 8 | (is (false? (#'gv/version-< [:tw 2] [:tw 1 9]))) 9 | (is (false? (#'gv/version-< [:tw 2] [:tw 2]))) 10 | (is (false? (#'gv/version-< [:tw 2] [:tw 2 0]))) 11 | (is (true? (#'gv/version-< [:tw 2] [:tw 2 0 1]))) 12 | (is (true? (#'gv/version-< [:tw 2] [:tw 3])))) 13 | 14 | (deftest version-<=-test 15 | (is (false? (#'gv/version-<= [:tw 2] [:tw 1]))) 16 | (is (false? (#'gv/version-<= [:tw 2] [:tw 1 9]))) 17 | (is (true? (#'gv/version-<= [:tw 2] [:tw 2]))) 18 | (is (true? (#'gv/version-<= [:tw 2] [:tw 2 0]))) 19 | (is (true? (#'gv/version-<= [:tw 2] [:tw 2 0 1]))) 20 | (is (true? (#'gv/version-<= [:tw 2] [:tw 3])))) 21 | -------------------------------------------------------------------------------- /lib/girouette/tests.edn: -------------------------------------------------------------------------------- 1 | #kaocha/v1 2 | {:tests [{:id :unit 3 | :type :kaocha.type/clojure.test} 4 | {:id :unit-cljs 5 | :type :kaocha.type/cljs}]} 6 | -------------------------------------------------------------------------------- /lib/processor/.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache 2 | .idea/ 3 | .nrepl-port 4 | *.jar 5 | *.iml 6 | .cljs_node_repl 7 | .clj-kondo 8 | node_modules 9 | out 10 | 11 | *~ 12 | [#]*[#] 13 | .\#* -------------------------------------------------------------------------------- /lib/processor/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | 3 | :deps {org.clojure/clojure {:mvn/version "1.11.1"} 4 | org.clojure/clojurescript {:mvn/version "1.11.60"} 5 | org.clojure/tools.deps.alpha {:mvn/version "0.14.1212"} 6 | org.clojure/tools.analyzer {:mvn/version "1.1.0"} 7 | ;rewrite-clj/rewrite-clj {:mvn/version "0.6.1"} 8 | com.nextjournal/beholder {:mvn/version "1.0.0"} 9 | org.slf4j/slf4j-nop {:mvn/version "1.7.36"} 10 | 11 | garden/garden {:mvn/version "1.3.10"} 12 | ;girouette/girouette {:local/root "../../lib/girouette"} 13 | girouette/girouette {:mvn/version "0.0.8"}} 14 | 15 | :aliases {:test {:extra-paths ["test"] 16 | :extra-deps {lambdaisland/kaocha {:mvn/version "1.68.1059"} 17 | lambdaisland/kaocha-cljs {:mvn/version "1.0.107"} 18 | lambdaisland/kaocha-junit-xml {:mvn/version "0.0.76"} 19 | org.clojure/test.check {:mvn/version "1.1.1"}}} 20 | 21 | ; clojure -M:outdated --upgrade 22 | :outdated {:extra-deps {com.github.liquidz/antq {:mvn/version "1.8.847"}} 23 | :main-opts ["-m" "antq.core"]} 24 | 25 | :depstar {:replace-deps {com.github.seancorfield/depstar {:mvn/version "2.1.303"}} 26 | :exec-fn hf.depstar/jar 27 | :exec-args {:sync-pom true 28 | :group-id "girouette" 29 | :artifact-id "processor" 30 | :version "0.0.8" 31 | :jar "processor.jar"}}}} 32 | ;; Memo for deploying a new release: 33 | ;; - Change the version above, then build the jar: 34 | ;; clojure -X:depstar 35 | ;; - add a tag "v0.x.y" to the latest commit and push to repo 36 | ;; - deploy: 37 | ;; mvn deploy:deploy-file -Dfile=processor.jar -DpomFile=pom.xml -DrepositoryId=clojars -Durl=https://clojars.org/repo/ 38 | -------------------------------------------------------------------------------- /lib/processor/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | jar 5 | girouette 6 | processor 7 | 0.0.8 8 | processor 9 | 10 | https://github.com/green-coder/girouette 11 | scm:git:git://github.com/green-coder/girouette.git 12 | scm:git:ssh://git@github.com/green-coder/girouette.git 13 | master 14 | 15 | 16 | 17 | org.clojure 18 | clojure 19 | 1.11.1 20 | 21 | 22 | org.clojure 23 | tools.analyzer 24 | 1.1.0 25 | 26 | 27 | org.clojure 28 | clojurescript 29 | 1.11.54 30 | 31 | 32 | com.nextjournal 33 | beholder 34 | 1.0.0 35 | 36 | 37 | org.clojure 38 | tools.deps.alpha 39 | 0.14.1178 40 | 41 | 42 | org.slf4j 43 | slf4j-nop 44 | 1.7.36 45 | 46 | 47 | girouette 48 | girouette 49 | 0.0.8 50 | 51 | 52 | garden 53 | garden 54 | 1.3.10 55 | 56 | 57 | 58 | src 59 | 60 | 61 | 62 | clojars 63 | https://repo.clojars.org/ 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /lib/processor/src/girouette/processor/env.clj: -------------------------------------------------------------------------------- 1 | (ns girouette.processor.env) 2 | 3 | ;; Used for storing the configuration data, accessible 4 | ;; from anywhere in the tool, from any thread. 5 | (def config (atom {})) 6 | --------------------------------------------------------------------------------