├── .eslintrc.js ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── TODO.md ├── package.json ├── pnpm-lock.yaml ├── src ├── animate.ts ├── decoration.ts ├── future.ts ├── index.ts └── scrollbars.ts ├── tailwind.config.js └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | commonjs: true, 4 | es6: true, 5 | node: true, 6 | }, 7 | globals: { 8 | Atomics: "readonly", 9 | SharedArrayBuffer: "readonly", 10 | }, 11 | parserOptions: { 12 | ecmaVersion: 2018, 13 | }, 14 | rules: {}, 15 | }; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.html 4 | *.css 5 | tags 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## [Unreleased] 4 | 5 | - Remove classes made obsolete by Tailwind CSS v2.4 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Brandon Pittman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tailwindcss-plugin-fancy 2 | 3 | This plugin merely wraps up a collection of other plugins I've written for 4 | Tailwind that makes my life/job easier. 5 | 6 | ## Usage 7 | 8 | ```javascript 9 | const fancy = require("tailwindcss-plugin-fancy"); 10 | 11 | // tailwind.config.js 12 | module.export = { 13 | // ... 14 | plugins: [fancy], 15 | }; 16 | ``` 17 | 18 | ## Animation 19 | 20 | The Tailwind animation utility is nice, but it lacks the flexibility of the 21 | transition utilities. This plugin adds support for the following: 22 | 23 | - animation-name 24 | - animation-duration 25 | - animation-delay 26 | - animation-fill-mode 27 | - animation-direction 28 | - animation-iteration 29 | - animation-timing-function 30 | - animation-play-state 31 | 32 | ### Utilities 33 | 34 | ```css 35 | .animate-${name} { 36 | /* the name is a keyframe just how the standard Tailwind animation plugin requires */ 37 | } 38 | 39 | .animate-duration-${n} { 40 | /* duration-${n} */ 41 | } 42 | 43 | .animate-delay-${n} { 44 | /* delay-${n} */ 45 | } 46 | 47 | .animate-ease-${ease} { 48 | animation-timing-function: /* ease-${linear, in, out, in-out} */ 49 | } 50 | 51 | .running { 52 | animation-play-state: running 53 | } 54 | 55 | .paused { 56 | animation-play-state: paused 57 | } 58 | 59 | .direction-${normal, reverse, alternate, alternate-reverse} { 60 | animation-direction: /* normal, reverse, alternate, alternate-reverse */ 61 | } 62 | 63 | .iterate-${n} { 64 | animation-iteration-count: ${n} /* 0-12 and infinite */ 65 | } 66 | 67 | .fill-${none, forwards, backwards, both} { 68 | animation-fill-mode: /* none, forwards, backwards, both */ 69 | } 70 | ``` 71 | 72 | `animation-timing-function` gets support for steps! 73 | 74 | ```css 75 | .animate-steps-5 { 76 | animation-timing-function: steps(5); 77 | } 78 | 79 | .animate-step-start { 80 | animation-timing-function: steps(1, jump-start); 81 | } 82 | 83 | .animate-step-end { 84 | animation-timing-function: steps(1, jump-end); 85 | } 86 | ``` 87 | 88 | Steps go from 0–12, then 15, 30, 45, and 60 by default. 89 | 90 | Add your own in `tailwind.config.js`. 91 | 92 | ```javascript 93 | theme: { 94 | extend: { 95 | animate: { 96 | steps: [ 97 | 17, // creates a steps(17) class as .animate-step-17 98 | [47, "jump-both"] // creates a steps(47, jump-both) class as .animate-step-47-jump-both 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | ### Make It Your Own 105 | 106 | The delay, duration, and timing function utilities pull from the transtion 107 | counterparts in your theme. To add to the iteration counts, provide something 108 | like the following in `tailwind.config.js`. 109 | 110 | ```javascript 111 | theme: { 112 | animate: { 113 | iterate: ["1.5", "2.5"]; 114 | } 115 | } 116 | ``` 117 | 118 | ## Stylable Scrollbars 119 | 120 | Style your scrollbars! 121 | 122 | ```css 123 | /* W3C scrollbar styling standard (Firefox) */ 124 | body { 125 | scrollbar-width: thin; /* "auto" or "thin" */ 126 | scrollbar-color: blue orange; /* scroll thumb & track */ 127 | } 128 | 129 | /* Webkit family of browsers (Safari, Chrome, etc.) */ 130 | body::-webkit-scrollbar { 131 | width: 16px; /* width of the entire scrollbar */ 132 | } 133 | body::-webkit-scrollbar-track { 134 | background: orange; /* color of the tracking area */ 135 | } 136 | body::-webkit-scrollbar-thumb { 137 | background-color: blue; /* color of the scroll thumb */ 138 | border-radius: 20px; /* roundness of the scroll thumb */ 139 | border: 3px solid orange; /* creates padding around scroll thumb */ 140 | } 141 | ``` 142 | 143 | ### Utilities 144 | 145 | ```css 146 | /* You need to add this first one to make the other utilities work, a la Tailwind's transform utility.) */ 147 | .scrollbar .scrollbar-thumb-$color /* var(--scrollbar-thumb) */ 148 | .scrollbar-track-$color /* var(--scrollbar-track) */ .scrollbar-auto 149 | /* var(--scrollbar-width-webkit) 16px for -webkit */ .scrollbar-thin 150 | /* var(--scrollbar-width-webkit) 11px for -webkit */ .scrollbar-none 151 | /* var(--scrollbar-width-webkit) */ 152 | /* The next two utilities will only work as expected on Chrome and Safari. 153 | Firefox follows the W3C standard which treats horizontal and vertical 154 | scrollabar width equally. On Firefox, they will hide BOTH scrollbars. */ 155 | .scrollbar-x-none .scrollbar-y-none; 156 | ``` 157 | 158 | The `$color` bit can be any color in your theme. For best results, apply the 159 | utilities to the `html` tag in your templates. Since they're just utilities, 160 | you can apply different ones to scrollable elements within your site to have 161 | multiple styles. 162 | 163 | ## Tailwind 2077 164 | 165 | These are settings that I could see being added to the Tailwind standard config 166 | in the future. 167 | 168 | ### Touch 169 | 170 | There's a `touch` variant that targets `@media(hover: none)`. 171 | 172 | ### Not Touch 173 | 174 | There's a `not-touch` variant that targets `@media(hover: hover)`. 175 | 176 | ### Bleed 177 | 178 | Adds `.bleed` and `.bleed-grid` components to to make blog-style full bleed 179 | images easier to handle. 180 | 181 | ### Stripes 182 | 183 | This adds the nifty `bg-stripes` utils from Tailwind's documentation. Use 184 | `bg-stripes` to turn the utility on and then add a `bg-stripes-${color}` 185 | utility to actually set the stripe color. 186 | 187 | You can also use `bg-stripes-{0, 45, 90, 135}` to control the angle. 188 | 189 | ### `word-break: keep-all` 190 | 191 | CJK has serious issues with linebreaks. Use the `.break-keep-all` util and 192 | place `` elements wherever a line _could_ break and see some nice 193 | results! 194 | 195 | ```html 196 |

197 | この文書がちょっと 198 | 199 | 長いかもしれません。 200 |

201 | ``` 202 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Fancy Tailwind 2 | 3 | ## To-do 4 | 5 | - Ditch TSDX 6 | - Refactor modules 7 | 8 | ## In Progress 9 | 10 | ## Done 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3.2.4", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist", 8 | "src" 9 | ], 10 | "engines": { 11 | "node": ">=10" 12 | }, 13 | "scripts": { 14 | "start": "tsdx watch --target node", 15 | "build": "tsdx build --target node", 16 | "test": "tsdx test", 17 | "lint": "tsdx lint", 18 | "prepare": "tsdx build --target node", 19 | "size": "size-limit", 20 | "analyze": "size-limit --why" 21 | }, 22 | "peerDependencies": { 23 | "postcss": "^8.2.8", 24 | "tailwindcss": "^3.0.0" 25 | }, 26 | "nano-staged": { 27 | "*.{json,md,ts,tsx}": [ 28 | "prettier --write" 29 | ] 30 | }, 31 | "simple-git-hooks": { 32 | "pre-commit": "./node_modules/.bin/nano-staged" 33 | }, 34 | "name": "tailwindcss-plugin-fancy", 35 | "author": "Brandon Pittman", 36 | "module": "dist/tailwindcss-plugin-fancy.esm.js", 37 | "size-limit": [ 38 | { 39 | "path": "dist/tailwindcss-plugin-fancy.cjs.production.min.js", 40 | "limit": "10 KB" 41 | }, 42 | { 43 | "path": "dist/tailwindcss-plugin-fancy.esm.js", 44 | "limit": "10 KB" 45 | } 46 | ], 47 | "devDependencies": { 48 | "@size-limit/preset-small-lib": "^4.12.0", 49 | "autoprefixer": "^10.3.1", 50 | "nano-staged": "^0.5.0", 51 | "postcss": "^8.3.6", 52 | "prettier": "^2.5.1", 53 | "simple-git-hooks": "^2.7.0", 54 | "size-limit": "^4.12.0", 55 | "tailwindcss": "^3.0.0", 56 | "tsdx": "^0.14.1", 57 | "tslib": "^2.3.0", 58 | "typescript": "^4.3.5" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/animate.ts: -------------------------------------------------------------------------------- 1 | import plugin from "tailwindcss/plugin"; 2 | 3 | const defaultIterate = [ 4 | "0", 5 | "1", 6 | "2", 7 | "3", 8 | "4", 9 | "5", 10 | "6", 11 | "7", 12 | "8", 13 | "9", 14 | "10", 15 | "11", 16 | "12", 17 | "15", 18 | "30", 19 | "45", 20 | "60", 21 | "infinite", 22 | ]; 23 | 24 | const defaultSteps = [ 25 | "0", 26 | "1", 27 | "2", 28 | "3", 29 | "4", 30 | "5", 31 | "6", 32 | "7", 33 | "8", 34 | "9", 35 | "10", 36 | "11", 37 | "12", 38 | "15", 39 | "30", 40 | "45", 41 | "60", 42 | ]; 43 | 44 | export default plugin( 45 | ({ addUtilities, theme, e }) => { 46 | const delays = { 47 | ...Object.fromEntries( 48 | Object.entries(theme("transitionDelay")).map(([k, v]) => [ 49 | `.animate-delay-${k}`, 50 | { animationDelay: v }, 51 | ]) 52 | ), 53 | ".animate-delay-2000": { animationDelay: "2000ms" }, 54 | ".animate-delay-3000": { animationDelay: "3000ms" }, 55 | ".animate-delay-4000": { animationDelay: "4000ms" }, 56 | ".animate-delay-5000": { animationDelay: "5000ms" }, 57 | }; 58 | 59 | const durations = { 60 | ...Object.fromEntries( 61 | Object.entries(theme("transitionDelay")).map(([k, v]) => [ 62 | `.animate-duration-${k}`, 63 | { animationDuration: v }, 64 | ]) 65 | ), 66 | ".animate-duration-2000": { animationDuration: "2000ms" }, 67 | ".animate-duration-3000": { animationDuration: "3000ms" }, 68 | ".animate-duration-4000": { animationDuration: "4000ms" }, 69 | ".animate-duration-5000": { animationDuration: "5000ms" }, 70 | }; 71 | 72 | const names = Object.fromEntries( 73 | Object.keys(theme("keyframes")).map((key) => [ 74 | `.animate-${key}`, 75 | { animationName: key }, 76 | ]) 77 | ); 78 | 79 | const timingFuctions = { 80 | ".animate-step-start": { animationTimingFunction: "jump-start" }, 81 | ".animate-step-end": { animationTimingFunction: "jump-end" }, 82 | ...Object.fromEntries( 83 | Object.entries(theme("transitionTimingFunction")).map(([k, v]) => [ 84 | k === "DEFAULT" ? ".animate-ease" : `.animate-ease-${k}`, 85 | { animationTimingFunction: v }, 86 | ]) 87 | ), 88 | ...Object.fromEntries( 89 | [...defaultSteps, ...theme("animate").steps].map((step) => 90 | Array.isArray(step) 91 | ? [ 92 | `.animate-step-${step[0]}-${step[1]}`, 93 | { animationTimingFunction: `steps(${step[0]}, ${step[1]})` }, 94 | ] 95 | : [ 96 | `.animate-step-${step}`, 97 | { animationTimingFunction: `steps(${step})` }, 98 | ] 99 | ) 100 | ), 101 | }; 102 | 103 | const playStates = { 104 | ".running": { 105 | animationPlayState: "running", 106 | }, 107 | ".paused": { 108 | animationPlayState: "paused", 109 | }, 110 | }; 111 | 112 | const modes = Object.fromEntries( 113 | ["none", "forwards", "backwards", "both"].map((mode) => [ 114 | `.fill-${mode}`, 115 | { animationFillMode: mode }, 116 | ]) 117 | ); 118 | 119 | const directions = Object.fromEntries( 120 | ["normal", "reverse", "alternate", "alternate-reverse"].map( 121 | (direction) => [ 122 | `.direction-${direction}`, 123 | { animationDirection: direction }, 124 | ] 125 | ) 126 | ); 127 | 128 | const iterations = Object.fromEntries( 129 | [...defaultIterate, ...theme("animate").iterate].map((count) => [ 130 | `.${e(`iterate-${count}`)}`, 131 | { animationIterationCount: count }, 132 | ]) 133 | ); 134 | 135 | addUtilities( 136 | { 137 | ...delays, 138 | ...durations, 139 | ...names, 140 | ...timingFuctions, 141 | ...playStates, 142 | ...modes, 143 | ...directions, 144 | ...iterations, 145 | ".animate-none": { 146 | animationName: "none", 147 | }, 148 | ".animate-spin": { 149 | animationName: "spin", 150 | }, 151 | ".animate-ping": { 152 | animationName: "ping", 153 | }, 154 | ".animate-pulse": { 155 | animationName: "pulse", 156 | }, 157 | ".animate-bounce": { 158 | animationName: "bounce", 159 | }, 160 | ".animate-warp": { 161 | animationName: "bg-warp", 162 | }, 163 | }, 164 | ["responsive", "hover", "focus", "group-hover", "group-focus"] 165 | ); 166 | }, 167 | { 168 | theme: { 169 | animate: { 170 | iterate: defaultIterate, 171 | steps: defaultSteps, 172 | }, 173 | }, 174 | } 175 | ); 176 | -------------------------------------------------------------------------------- /src/decoration.ts: -------------------------------------------------------------------------------- 1 | import plugin from "tailwindcss/plugin"; 2 | 3 | export default plugin(({ addUtilities, variants, theme }) => { 4 | let { colors, spacing } = require("tailwindcss/defaultTheme"); 5 | 6 | colors = { 7 | ...colors, 8 | ...theme("colors"), 9 | }; 10 | 11 | const decorationStyles = { 12 | ".overline": { 13 | textDecorationLine: "overline", 14 | }, 15 | ".decoration-skip-none": { 16 | textDecorationSkip: "none", 17 | }, 18 | ".decoration-skip-objects": { 19 | textDecorationSkip: "objects", 20 | }, 21 | ".decoration-skip-spaces": { 22 | textDecorationSkip: "spaces", 23 | }, 24 | ".decoration-skip-edges": { 25 | textDecorationSkip: "edges", 26 | }, 27 | 28 | ".decoration-skip-ink": { 29 | textDecorationSkipInk: "auto", 30 | }, 31 | ".decoration-skip-ink-none": { 32 | textDecorationSkipInk: "none", 33 | }, 34 | 35 | ".underline-double": { 36 | textDecorationStyle: "double", 37 | }, 38 | ".underline-dotted": { 39 | textDecorationStyle: "dotted", 40 | }, 41 | ".underline-dashed": { 42 | textDecorationStyle: "dashed", 43 | }, 44 | ".underline-wavy": { 45 | textDecorationStyle: "wavy", 46 | }, 47 | }; 48 | 49 | const decorationColors = { 50 | ".decoration-current": { 51 | textDecorationColor: "currentColor", 52 | }, 53 | }; 54 | 55 | const decorationLines = {}; 56 | 57 | for (const [key, value] of Object.entries(spacing)) { 58 | decorationLines[`.underline-thickness-${key}`] = { 59 | textDecorationThickness: value, 60 | }; 61 | 62 | decorationLines[`.underline-offset-${key}`] = { 63 | textUnderlineOffset: value, 64 | }; 65 | } 66 | 67 | for (const [key, value] of Object.entries(colors)) { 68 | if (typeof value === "string") { 69 | decorationColors[`.decoration-${key}`] = { 70 | textDecorationColor: value, 71 | }; 72 | } else { 73 | for (const [k, newV] of Object.entries(colors[key])) { 74 | decorationColors[`.decoration-${key}-${k}`] = { 75 | textDecorationColor: newV, 76 | }; 77 | } 78 | } 79 | } 80 | 81 | const decorations = { 82 | ...decorationLines, 83 | ...decorationColors, 84 | ...decorationStyles, 85 | }; 86 | 87 | addUtilities(decorations, variants("decoration", ["responsive", "hover"])); 88 | }); 89 | -------------------------------------------------------------------------------- /src/future.ts: -------------------------------------------------------------------------------- 1 | import plugin from "tailwindcss/plugin"; 2 | import postcss from "postcss"; 3 | import { default as flattenColorPalette } from "tailwindcss/lib/util/flattenColorPalette"; 4 | import { toRgba } from "tailwindcss/lib/util/withAlphaVariable"; 5 | 6 | export default plugin( 7 | ({ addVariant, addComponents, addUtilities, theme, e }) => { 8 | const ariaCurrentValues = [ 9 | "page", 10 | "step", 11 | "location", 12 | "date", 13 | "time", 14 | "true", 15 | ]; 16 | 17 | addVariant("current", ({ modifySelectors, separator }) => { 18 | modifySelectors(({ className }) => { 19 | return [...ariaCurrentValues] 20 | .map( 21 | (val) => 22 | `.${e(`current${separator}${className}`)}[aria-current="${val}"]` 23 | ) 24 | .join(", "); 25 | }); 26 | }); 27 | 28 | addVariant("expanded", ({ modifySelectors, separator }) => { 29 | modifySelectors(({ className }) => { 30 | return `.${e( 31 | `expanded${separator}${className}` 32 | )}[aria-expanded="true"], [aria-expanded="true"] > .${e( 33 | `expanded${separator}${className}` 34 | )}`; 35 | }); 36 | }); 37 | 38 | addVariant("selected", ({ modifySelectors, separator }) => { 39 | modifySelectors(({ className }) => { 40 | return `.${e( 41 | `expanded${separator}${className}` 42 | )}[aria-selected="true"], [aria-selected="true"] > .${e( 43 | `expanded${separator}${className}` 44 | )}`; 45 | }); 46 | }); 47 | 48 | const stripes = { 49 | ".bg-stripes": { 50 | backgroundImage: 51 | "linear-gradient(var(--stripes-angle, 45deg), var(--stripes-color) 12.50%, transparent 12.50%, transparent 50%, var(--stripes-color) 50%, var(--stripes-color) 62.50%, transparent 62.50%, transparent 100%)", 52 | backgroundSize: "5.66px 5.66px", 53 | }, 54 | ".bg-stripes-0": { "--stripes-angle": "0deg" }, 55 | ".bg-stripes-45": { "--stripes-angle": "45deg" }, 56 | ".bg-stripes-90": { "--stripes-angle": "90deg" }, 57 | ".bg-stripes-135": { "--stripes-angle": "135deg" }, 58 | }; 59 | 60 | const addColor = (name: string, color: string) => 61 | (stripes[`.bg-stripes-${name}`] = { "--stripes-color": color }); 62 | 63 | const colors = flattenColorPalette(theme("backgroundColor")); 64 | for (let name in colors) { 65 | try { 66 | const [r, g, b, a] = toRgba(colors[name]); 67 | if (a !== undefined) { 68 | addColor(name, colors[name]); 69 | } else { 70 | addColor(name, `rgba(${r}, ${g}, ${b}, 0.4)`); 71 | } 72 | } catch (_) { 73 | addColor(name, colors[name]); 74 | } 75 | } 76 | 77 | addUtilities(stripes, ["responsive"]); 78 | 79 | const fullBleed = { 80 | ".bleed-grid": { 81 | display: "grid", 82 | gridTemplateColumns: "1fr min(65ch, 100%) 1fr", 83 | "& > *": { 84 | gridColumn: "2", 85 | }, 86 | }, 87 | ".bleed": { 88 | width: "100%", 89 | gridColumn: "1/4", 90 | }, 91 | }; 92 | 93 | addComponents(fullBleed, ["responsive"]); 94 | 95 | addUtilities({ 96 | "@keyframes bg-warp": { 97 | from: { "background-position": "right" }, 98 | to: { "background-position": "left" }, 99 | }, 100 | }); 101 | addUtilities( 102 | { 103 | ".bg-skinny": { 104 | backgroundSize: "0% 100%", 105 | }, 106 | ".bg-flat": { 107 | backgroundSize: "100% 0%", 108 | }, 109 | ".bg-full": { 110 | backgroundSize: "100% 100%", 111 | }, 112 | ".bg-fat": { 113 | backgroundSize: "200% 200%", 114 | }, 115 | ".bg-jumbo": { 116 | backgroundSize: "400% 400%", 117 | }, 118 | }, 119 | ["responsive"] 120 | ); 121 | 122 | addUtilities({ 123 | ".w-128": { width: "32rem" }, 124 | ".h-128": { height: "32rem" }, 125 | ".break-keep-all": { 126 | "word-break": "keep-all", 127 | }, 128 | }); 129 | 130 | addVariant("touch", ({ container, separator }) => { 131 | const supportsRule = postcss.atRule({ 132 | name: "media", 133 | params: "(hover: none)", 134 | }); 135 | supportsRule.append(container.nodes); 136 | container.append(supportsRule); 137 | supportsRule.walkRules((rule) => { 138 | rule.selector = `.${e(`touch${separator}${rule.selector.slice(1)}`)}`; 139 | }); 140 | }); 141 | addVariant("not-touch", ({ container, separator }) => { 142 | const supportsRule = postcss.atRule({ 143 | name: "media", 144 | params: "(hover: hover)", 145 | }); 146 | supportsRule.append(container.nodes); 147 | container.append(supportsRule); 148 | supportsRule.walkRules((rule) => { 149 | rule.selector = `.${e( 150 | `not-touch${separator}${rule.selector.slice(1)}` 151 | )}`; 152 | }); 153 | }); 154 | } 155 | ); 156 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import plugin from "tailwindcss/plugin"; 2 | import future from "./future"; 3 | import scrollbars from "./scrollbars"; 4 | import animate from "./animate"; 5 | 6 | const plugins = [future, scrollbars, animate]; 7 | 8 | module.exports = plugin( 9 | (helpers: any) => { 10 | plugins.forEach((plugin) => plugin.handler(helpers)); 11 | }, 12 | { 13 | variants: { 14 | ...animate.config.variants, 15 | }, 16 | theme: { 17 | extend: { 18 | transitionDelay: { 19 | 2000: "2000ms", 20 | 3000: "3000ms", 21 | 4000: "4000ms", 22 | 5000: "5000ms", 23 | }, 24 | transitionDuration: { 25 | 2000: "2000ms", 26 | 3000: "3000ms", 27 | 4000: "4000ms", 28 | 5000: "5000ms", 29 | }, 30 | minWidth: (theme) => theme("width"), 31 | maxWidth: (theme) => theme("width"), 32 | minHeight: (theme) => theme("height"), 33 | maxHeight: (theme) => theme("height"), 34 | ...animate.config.theme, 35 | }, 36 | }, 37 | } 38 | ); 39 | -------------------------------------------------------------------------------- /src/scrollbars.ts: -------------------------------------------------------------------------------- 1 | import plugin from "tailwindcss/plugin"; 2 | 3 | export default plugin(({ addUtilities, theme }) => { 4 | const scrollbarColors = {}; 5 | 6 | for (const [key, v] of Object.entries(theme("colors"))) { 7 | if (typeof v === "string") { 8 | scrollbarColors[`.scrollbar-track-${key}`] = { 9 | "--scrollbar-track": v, 10 | }; 11 | 12 | scrollbarColors[`.scrollbar-thumb-${key}`] = { 13 | "--scrollbar-thumb": v, 14 | }; 15 | } else { 16 | for (const [k, newV] of Object.entries(theme("colors")[key])) { 17 | scrollbarColors[`.scrollbar-track-${key}-${k}`] = { 18 | "--scrollbar-track": newV, 19 | }; 20 | 21 | scrollbarColors[`.scrollbar-thumb-${key}-${k}`] = { 22 | "--scrollbar-thumb": newV, 23 | }; 24 | } 25 | } 26 | } 27 | 28 | addUtilities({ 29 | ".scrollbar": { 30 | "--scrollbar-thumb": "#b5b5b5", 31 | "--scrollbar-track": "#f9f9f9", 32 | "--scrollbar-width": "auto", 33 | "--scrollbar-width-webkit": "16px", 34 | "--scrollbar-height-webkit": "16px", 35 | 36 | scrollbarWidth: "var(--scrollbar-width, initial)", 37 | scrollbarColor: 38 | "var(--scrollbar-thumb, initial) var(--scrollbar-track, initial)", 39 | 40 | "&::-webkit-scrollbar": { 41 | width: "var(--scrollbar-width-webkit)", 42 | height: "var(--scrollbar-height-webkit)", 43 | }, 44 | 45 | "&::-webkit-scrollbar-track": { 46 | backgroundColor: "var(--scrollbar-track)", 47 | }, 48 | 49 | "&::-webkit-scrollbar-thumb": { 50 | backgroundColor: "var(--scrollbar-thumb)", 51 | border: "3px solid var(--scrollbar-track)", 52 | borderRadius: "8px", 53 | }, 54 | }, 55 | ".scrollbar-auto": { 56 | scrollbarWidth: "auto", 57 | "&::-webkit-scrollbar": { 58 | display: "initial", 59 | }, 60 | "--scrollbar-width-webkit": "16px", 61 | "--scrollbar-height-webkit": "16px", 62 | }, 63 | ".scrollbar-thin": { 64 | scrollbarWidth: "thin", 65 | "--scrollbar-width-webkit": "11px", 66 | "--scrollbar-height-webkit": "11px", 67 | }, 68 | ".scrollbar-none": { 69 | scrollbarWidth: "none", 70 | "&::-webkit-scrollbar": { 71 | display: "none", 72 | }, 73 | }, 74 | ".scrollbar-y-none": { 75 | scrollbarWidth: "none", 76 | "--scrollbar-width-webkit": "0px", 77 | }, 78 | ".scrollbar-x-none": { 79 | scrollbarWidth: "none", 80 | "--scrollbar-height-webkit": "0px", 81 | }, 82 | ...scrollbarColors, 83 | }); 84 | }, {}); 85 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ["./index.html"], 3 | mode: "jit", 4 | darkMode: false, // or 'media' or 'class' 5 | theme: { 6 | extend: {}, 7 | }, 8 | variants: { 9 | extend: {}, 10 | }, 11 | plugins: [require(".")], 12 | }; 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": false, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true 34 | } 35 | } 36 | --------------------------------------------------------------------------------