├── .gitignore ├── elm-package.json ├── package.json ├── src └── Tailwind.elm ├── README.md ├── yarn.lock └── scripts └── convert.js /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | node_modules -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.2.1", 3 | "summary": "elm-tailwind. A stub of a library for using tailwind.css easily in Elm.", 4 | "repository": "https://github.com/splodingsocks/elm-tailwind.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "./src" 8 | ], 9 | "exposed-modules": [ 10 | "Tailwind", 11 | "Tailwind.Classes" 12 | ], 13 | "dependencies": { 14 | "elm-lang/core": "5.1.1 <= v < 6.0.0", 15 | "elm-lang/html": "2.0.0 <= v < 3.0.0" 16 | }, 17 | "elm-version": "0.18.0 <= v < 0.19.0" 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elm-tailwind", 3 | "version": "1.0.0", 4 | "description": "Tailwind CSS to elm conversion", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/splodingsocks/elm-tailwind.git" 12 | }, 13 | "author": "Murphy Randle", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/splodingsocks/elm-tailwind/issues" 17 | }, 18 | "homepage": "https://github.com/splodingsocks/elm-tailwind#readme", 19 | "dependencies": { 20 | "lodash": "^4.17.4", 21 | "lodash.flattendeep": "^4.4.0", 22 | "lodash.replace": "^4.1.4", 23 | "lodash.without": "^4.4.0", 24 | "postcss": "^6.0.6" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Tailwind.elm: -------------------------------------------------------------------------------- 1 | module Tailwind exposing (..) 2 | 3 | {-| A small library that makes using [tailwind.css](https://tailwindcss.com/docs/what-is-tailwind/) a little easier and a little safer to use within Elm. 4 | 5 | You'll find classes responding to Tailwind rules in Tailwind.Classes. 6 | 7 | In there you'll also find functions for appending responsive qualifiers to those classes. Here's an example of what a div using tailwind might look like: 8 | 9 | div 10 | [ tailwind 11 | <| withClasses [ "__login_page" ] -- __login_page is not a tailwind class, it's just for marking the div's purpose. 12 | <| [ m_1, lg m_6 ] 13 | ] 14 | 15 | @docs stylesheet, tailwind, withClasses, asClasses 16 | 17 | -} 18 | 19 | import Html 20 | import Html.Attributes exposing (href, rel) 21 | import Tailwind.Classes exposing (TailwindClass(..)) 22 | 23 | 24 | {-| An HTML "link" tag that will import the default tailwind stylesheet 25 | -} 26 | stylesheet : Html.Html msg 27 | stylesheet = 28 | Html.node "link" [ rel "stylesheet", href "https://cdn.jsdelivr.net/npm/tailwindcss/dist/tailwind.min.css" ] [] 29 | 30 | 31 | {-| A convenience function for specifying classes as a list of strings 32 | -} 33 | tailwind : List TailwindClass -> Html.Attribute msg 34 | tailwind cs = 35 | let 36 | folder (TailwindClass c) memo = 37 | memo ++ " " ++ c 38 | in 39 | Html.Attributes.class <| List.foldl folder "" cs 40 | 41 | 42 | {-| A convenience function for adding non-tailwind classes to an element alongside other tailwind classes. 43 | Could be used like this: 44 | 45 | div 46 | [ tailwind 47 | <| withClasses [ "superfunclass" ] 48 | <| [ sm mt_1 ] 49 | ] 50 | 51 | -} 52 | withClasses : List String -> List TailwindClass -> List TailwindClass 53 | withClasses cs twCs = 54 | twCs ++ List.map TailwindClass cs 55 | 56 | 57 | {-| Turns tailwind classes into normal strings, for when you just want a list of strings instead of an Html.Attribute. 58 | -} 59 | asClasses : List TailwindClass -> List String 60 | asClasses twCs = 61 | List.map (\(TailwindClass c) -> c) twCs 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elm-tailwind 2 | 3 | ![Tailwind logo image](https://pbs.twimg.com/profile_images/895274026783866881/E1G1nNb0.jpg) 4 | 5 | A small library that makes using [tailwind.css](https://tailwindcss.com/docs/what-is-tailwind/) a little easier and a little safer to use within Elm. 6 | 7 | You'll find classes responding to Tailwind rules in Tailwind.Classes. 8 | 9 | In there you'll also find functions for appending responsive qualifiers to those classes. Here's an example of what a div using tailwind might look like: 10 | 11 | div 12 | [ tailwind 13 | <| withClasses [ "__login_page" ] -- __login_page is not a tailwind class, it's just for marking the div's purpose. 14 | <| [ m_1, lg m_6 ] 15 | ] 16 | 17 | # Ideas for Improvement 18 | 19 | This library is mostly for autocomplete and avoiding spelling errors at the moment. A more ideal solution might be to make a type-safe api, maybe something like this: 20 | 21 | To give types to: 22 | 23 | spacing : SpacingFlavor -> Side -> SpacingLength -> String 24 | 25 | type SpacingFlavor 26 | = Padding 27 | | Margin 28 | | NegMargin 29 | 30 | type Side 31 | = All 32 | | Top 33 | | Left 34 | | Right 35 | | Bottom 36 | | X 37 | | Y 38 | 39 | type SpacingLength 40 | = S0 41 | | S1 42 | | S2 43 | | S3 44 | | S4 45 | | S5 46 | | S6 47 | | S7 48 | | S8 49 | | Spx 50 | | Sauto 51 | 52 | And a vertical padding could be expressed like so: 53 | 54 | `padding Y S2` 55 | 56 | # License 57 | ISC license: 58 | 59 | ``` 60 | Copyright 2017 Murphy Randle 61 | 62 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 63 | 64 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 65 | ``` 66 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | ansi-styles@^3.1.0: 6 | version "3.2.0" 7 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" 8 | dependencies: 9 | color-convert "^1.9.0" 10 | 11 | chalk@^2.3.0: 12 | version "2.3.0" 13 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" 14 | dependencies: 15 | ansi-styles "^3.1.0" 16 | escape-string-regexp "^1.0.5" 17 | supports-color "^4.0.0" 18 | 19 | color-convert@^1.9.0: 20 | version "1.9.1" 21 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" 22 | dependencies: 23 | color-name "^1.1.1" 24 | 25 | color-name@^1.1.1: 26 | version "1.1.3" 27 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 28 | 29 | escape-string-regexp@^1.0.5: 30 | version "1.0.5" 31 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 32 | 33 | has-flag@^2.0.0: 34 | version "2.0.0" 35 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" 36 | 37 | lodash.flattendeep@^4.4.0: 38 | version "4.4.0" 39 | resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" 40 | 41 | lodash.replace@^4.1.4: 42 | version "4.1.4" 43 | resolved "https://registry.yarnpkg.com/lodash.replace/-/lodash.replace-4.1.4.tgz#531c0960dfb5256172afc1c34a4f0ba6f0cb20b8" 44 | 45 | lodash.without@^4.4.0: 46 | version "4.4.0" 47 | resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" 48 | 49 | lodash@^4.17.4: 50 | version "4.17.4" 51 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 52 | 53 | postcss@^6.0.6: 54 | version "6.0.14" 55 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.14.tgz#5534c72114739e75d0afcf017db853099f562885" 56 | dependencies: 57 | chalk "^2.3.0" 58 | source-map "^0.6.1" 59 | supports-color "^4.4.0" 60 | 61 | source-map@^0.6.1: 62 | version "0.6.1" 63 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 64 | 65 | supports-color@^4.0.0, supports-color@^4.4.0: 66 | version "4.5.0" 67 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" 68 | dependencies: 69 | has-flag "^2.0.0" 70 | -------------------------------------------------------------------------------- /scripts/convert.js: -------------------------------------------------------------------------------- 1 | // Copyright 2007 Gage Peterson 2 | // Copyright 2007 Murphy Randle 3 | // 4 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 5 | // 6 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 7 | 8 | // to use this run `node ./convert.js` 9 | 10 | const postcss = require('postcss'); 11 | const _ = require('lodash'); 12 | 13 | const exportPath = './src/Tailwind/Classes.elm'; 14 | 15 | fs = require('fs'); 16 | 17 | const css = fs.readFileSync('./scripts/tailwind.css', 'utf8'); // minificated version introduces problems with compound selectors 18 | 19 | const root = postcss.parse(css); 20 | 21 | const classObjs = {}; 22 | 23 | const defaultIndentation = ' '.repeat(4); 24 | 25 | const ruleFormatter = (rule) => { 26 | let def = rule.toString().replace('{-', '{ -'); 27 | def = setCorrectIndentation(def); 28 | return def; 29 | }; 30 | 31 | const setCorrectIndentation = (text) => { 32 | // normalize indentation 33 | if (/ {4}/.test(text)) { 34 | text = text.replace(/\n {2}/g, '\n'); 35 | } 36 | // set indentation 37 | text = text.replace(/\n/g, `\n${defaultIndentation}`); 38 | return text; 39 | }; 40 | 41 | /** 42 | * This will walk through each of the css rules in tailwind 43 | * and pull out the relevent information. 44 | */ 45 | root.walkRules((rule) => { 46 | // 47 | // Ignore anything that's not a class 48 | // 49 | if (!rule.selector.startsWith('.')) return; 50 | 51 | // 52 | // Ignore responsive variations in favor of using utility classes for that. 53 | // 54 | if (rule.selector.startsWith('.sm\\:')) return; 55 | if (rule.selector.startsWith('.md\\:')) return; 56 | if (rule.selector.startsWith('.lg\\:')) return; 57 | if (rule.selector.startsWith('.xl\\:')) return; 58 | 59 | let name = rule.selector; 60 | 61 | // 62 | // If we're dealing with a rule that's selecting an :after or :before, ignore the rule 63 | // 64 | if (name.indexOf(':after') != -1 || name.indexOf(':before') != -1) { 65 | return; 66 | } 67 | 68 | // 69 | // Remove the leading dot 70 | // 71 | name = name.replace(/^\S*\./, ''); 72 | 73 | // 74 | // Throw away anything after a comma or a space 75 | // 76 | name = name.split(/[, ]/)[0]; 77 | 78 | let elmName = name; 79 | // 80 | // Replace the \: with a __ 81 | // 82 | elmName = elmName.split('\\:').join('__'); 83 | 84 | // 85 | // Replace the negative margin with a 'neg' 86 | // 87 | elmName = elmName.replace(/-m([lrtbxy])/g, 'neg_m$1'); 88 | 89 | // 90 | // Change a leading dash to a 'neg' 91 | // 92 | elmName = elmName.replace(/^-/, 'neg'); 93 | 94 | // 95 | // Replace dashes with underscores 96 | // 97 | elmName = elmName.replace(/-/g, '_'); 98 | 99 | // 100 | // Change the \/ in fractions to `over` 101 | // 102 | elmName = elmName.replace(/\\\//g, 'over'); 103 | 104 | // 105 | // Remove :focus 106 | // 107 | elmName = elmName.replace(':focus', ''); 108 | 109 | // 110 | // Remove :hover 111 | // 112 | elmName = elmName.replace(':hover', ''); 113 | 114 | // 115 | // Before using "name", but after basing elmName on it, escape the backslack in the Elm string 116 | // 117 | name = name.replace(/\\\//g, '/'); 118 | name = name.replace(/\\/g, '\\\\'); 119 | 120 | const obj = { 121 | name, 122 | elmName, 123 | def: ruleFormatter(rule), 124 | }; 125 | 126 | console.log(obj); 127 | 128 | if (name in classObjs) { classObjs[name].def += `\n${defaultIndentation}${obj.def}`; } // class has been already registered, only append new def 129 | else classObjs[name] = obj; 130 | }); 131 | 132 | const classes = _(classObjs).sortBy('name'); 133 | 134 | // creates an elm variable for each class 135 | const elmify = cl => ` 136 | {-| This class maps to this CSS definition: 137 | 138 | ${cl.def} 139 | 140 | -} 141 | ${cl.elmName} : TailwindClass 142 | ${cl.elmName} = 143 | TailwindClass "${cl.name}" 144 | 145 | 146 | `; 147 | 148 | // // string of the elm file 149 | const elmString = ` 150 | module Tailwind.Classes exposing (..) 151 | 152 | {-| 153 | 154 | These are all the classes ported from tailwind. __NOTE__: this is a auto-generated file by \`scripts/convert.js\` 155 | 156 | # Types 157 | 158 | @docs TailwindClass 159 | 160 | # Responsive helpers 161 | 162 | @docs sm, md, lg, xl 163 | 164 | # Useless Docs below: 165 | 166 | Yes these docs are useless, please look at the [tailwind docs](https://tailwindcss.com/docs/what-is-tailwind/) for the definitions of these classes. Keep in mind this was made with a script (not typed by hand). 167 | 168 | They do however show the minifed css definition as their comment. 169 | 170 | # Classes and their Definitions 171 | 172 | @docs ${classes.map(({ elmName }) => elmName).join(', ')} 173 | 174 | -} 175 | 176 | {-| A type to help make sure we're passing Tailwind classes to the classes function, and not classes for some other CSS utility library. (Can help with refactoring) -} 177 | type TailwindClass = TailwindClass String 178 | 179 | {-| Add a size prefix to the tailwind rule, making it only apply to that screen size and above. 180 | Notice, doesn't make sure the class being passed in is going to work with a responsive prefix.. Not all tailwind rules are responsive-capable. 181 | -} 182 | sm : TailwindClass -> TailwindClass 183 | sm (TailwindClass c) = 184 | TailwindClass ("sm:" ++ c) 185 | 186 | 187 | {-| Add a size prefix to the tailwind rule, making it only apply to that screen size and above. 188 | Notice, doesn't make sure the class being passed in is going to work with a responsive prefix.. Not all tailwind rules are responsive-capable. 189 | -} 190 | md : TailwindClass -> TailwindClass 191 | md (TailwindClass c) = 192 | TailwindClass ("md:" ++ c) 193 | 194 | 195 | {-| Add a size prefix to the tailwind rule, making it only apply to that screen size and above. 196 | Notice, doesn't make sure the class being passed in is going to work with a responsive prefix.. Not all tailwind rules are responsive-capable. 197 | -} 198 | lg : TailwindClass -> TailwindClass 199 | lg (TailwindClass c) = 200 | TailwindClass ("lg:" ++ c) 201 | 202 | 203 | {-| Add a size prefix to the tailwind rule, making it only apply to that screen size and above. 204 | Notice, doesn't make sure the class being passed in is going to work with a responsive prefix.. Not all tailwind rules are responsive-capable. 205 | -} 206 | xl : TailwindClass -> TailwindClass 207 | xl (TailwindClass c) = 208 | TailwindClass ("xl:" ++ c) 209 | 210 | 211 | ${classes.map(elmify).join('')}`; 212 | 213 | // writing the string to the file 214 | fs.writeFile(exportPath, elmString, (err) => { 215 | if (err) { 216 | return console.log(err); 217 | } 218 | 219 | console.log(exportPath, 'was saved!'); 220 | }); 221 | --------------------------------------------------------------------------------