├── .circleci
└── config.yml
├── .gitignore
├── .nvmrc
├── .prettierrc
├── .tool-versions
├── CHANGELOG.md
├── CONTRIBUTING.md
├── README.md
├── examples
├── rect
│ ├── README.md
│ └── index.js
└── resp-headings
│ ├── README.md
│ └── index.js
├── index.js
├── package.json
├── plugins
├── animate
│ ├── README.md
│ ├── index.js
│ └── test.js
├── parent-expanded
│ ├── README.md
│ ├── index.js
│ └── test.js
├── parent-open
│ ├── README.md
│ ├── index.js
│ └── test.js
├── rect
│ ├── README.md
│ ├── index.js
│ └── test.js
├── sr
│ ├── README.md
│ ├── index.js
│ └── test.js
└── text-underline-position
│ ├── README.md
│ ├── index.js
│ └── test.js
├── testing
├── config.js
└── run.js
└── utilities
├── alpha
├── README.md
└── index.js
└── fns
├── README.md
└── index.js
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: cimg/node:20.10.0
6 | steps:
7 | - checkout
8 | - run:
9 | name: Install JS
10 | command: |
11 | yarn install --frozen-lockfile
12 | - run:
13 | name: Test JS
14 | command: |
15 | yarn test
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | yarn.lock
3 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v20.10.0
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true
4 | }
5 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | nodejs 20.10.0
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased]
8 | ### Added
9 | - n/a
10 |
11 | ### Removed
12 | - n/a
13 |
14 | ### Changed
15 | - n/a
16 |
17 | ### Deprecated
18 | - Deprecate rect plugin
19 | - Deprecate sr plugin
20 |
21 | ## [3.0.0] - 06-13-2022
22 | ### Added
23 | - Added the text-underline-position plugin
24 |
25 | ### Removed
26 | - Removed animation plugin
27 | - Removed background plugin
28 | - Removed blend plugin
29 | - Removed filter plugin
30 | - Removed flex-basis plugin
31 | - Removed gradient plugin
32 |
33 | ### Changed
34 | - Updated Tailwind to v3
35 | - Updated tests for Tailwind v3
36 | - Updated dependencies
37 |
38 | ### Deprecated
39 | - n/a
40 |
41 | ## [0.0.11] - 09-15-2020
42 | ### Fixed
43 | - Fixed missing export, missing README update
44 |
45 | ## [0.0.10] - 09-15-2020
46 | ### Added
47 | - Added the animate plugin
48 |
49 | ## [0.0.9] - 08-25-2020
50 | ### Added
51 | - Added .sr-undo-absolute utility
52 | - Added testing via CircleCI
53 |
54 | ## [0.0.8] - 05-06-2020
55 | ### Fixed
56 | - Fixed gradients plugin
57 |
58 | ## [0.0.7] - 05-05-2020
59 | ### Added
60 | - Added utility functions
61 |
62 | ### Fixed
63 | - Fixed missing top-level exports
64 |
65 | ## [0.0.6] - 05-04-2020
66 | ### Added
67 | - Added gradients plugin
68 |
69 | ## [0.0.5] - 04-28-2020
70 | ### Fixed
71 | - Fixed filters plugin
72 |
73 | ## [0.0.4] - 04-27-2020
74 | ### Added
75 | - Added prettier and formatted project
76 | - Added animation, background and filter plugins
77 | - Added alpha.js as requireable utility
78 |
79 | ## [0.0.3] - 04-14-2020
80 | ### Fixed
81 | - Fixed flex basis plugin
82 |
83 | ## [0.0.2] - 04-14-2020
84 | ### Changed
85 | - Modified index.js exports to allow importing from top level
86 |
87 | ## [0.0.1] - 04-14-2020
88 | Initial release
89 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Please review this document before submitting a pull request.
4 |
5 | ## Setup
6 |
7 | To get started with local development,
8 |
9 | 1. Install the correct version of Node.
10 |
11 | ```bash
12 | asdf install
13 | ```
14 |
15 | 1. Install `package.json` dependencies. Versions are locked in `yarn.lock`, so installing them with Yarn is recommended.
16 |
17 | ```bash
18 | yarn
19 | ````
20 |
21 | See our [How to use local Node packages as project dependencies](https://www.viget.com/articles/how-to-use-local-unpublished-node-packages-as-project-dependencies/) article to use your development version in a project.
22 |
23 | ## Coding standards
24 |
25 | Our code formatting rules are defined in .prettierrc. You can format your code against these standards using [Prettier](https://prettier.io/) via the `format` script in `package.json`.
26 |
27 | ```bash
28 | yarn format
29 | ```
30 |
31 | ## Running tests
32 |
33 | To test all plugins, run the `test` script in `package.json`.
34 |
35 | ```bash
36 | yarn test
37 | ```
38 |
39 | To test a specific plugin, run the `test` script in `package.json` passing it the test file’s path.
40 |
41 | ```bash
42 | yarn test plugins/my-plugin/test
43 | ```
44 |
45 | ## Writing tests
46 |
47 | Each plugin is tested using [Jest](https://jestjs.io/). The easiest way to get started is by looking at existing tests.
48 |
49 | ### Structure
50 |
51 | - **Output**: Define what the generated CSS should look like.
52 | - **Config**: Define the content string to use for generating the CSS. Most plugins require a theme passed as well.
53 |
54 | ## Documenting changes and releases
55 |
56 | - Add notes to the unreleased section in the CHANGELOG, with summaries of any additions, fixes, updates, or deprecations.
57 | - When a new version is released, a maintainer will move those notes to the appropriate version number in the CHANGELOG.
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tailwind CSS Plugins
2 |
3 | This plugin wraps up a collection of other plugins we've written for [Tailwind CSS](https://tailwindcss.com/).
4 |
5 | Plugins include:
6 | * [animate](/plugins/animate/)
7 | * [parent-expanded](/plugins/parent-expanded/)
8 | * [parent-open](/plugins/parent-open/)
9 | * [rect](/plugins/rect/)
10 | * [sr](/plugins/sr/)
11 | * [text-underline-position](/plugins/text-underline-position/)
12 |
13 | Utilities include:
14 | * [hex alpha reference](/utilities/alpha/)
15 | * [helper functions](/utilities/fns/)
16 |
17 | ## Installation
18 |
19 | Add `@viget/tailwindcss-plugins` to `devDependencies` in `package.json`.
20 |
21 | ```bash
22 | yarn add -D @viget/tailwindcss-plugins
23 | ```
24 |
25 | ## Usage
26 |
27 | Simply require the plugins or utilities in your `tailwindcss.config.js` file, and follow the usage instructions in each plugin’s README.
28 |
29 | ```js
30 | // utilities
31 | const a = require('@viget/tailwindcss-plugins/utilities/alpha')
32 | const { em, rem, remPair, pxPair } = require('@viget/tailwindcss-plugins/utilities/fns')
33 | const plugins = require('@viget/tailwindcss-plugins')
34 |
35 | // plugins
36 | plugins: [
37 | plugins.rect,
38 | plugins.sr,
39 | // ...
40 | ],
41 | ```
42 |
43 | ## Notes
44 |
45 | There are some additional plugin examples included in this repository’s **examples** directory. These are not registerable in your Tailwind config. The reasons for this are that these represent plugins which:
46 |
47 | * could have a number of different outputs that require varying approaches
48 | * have a highly specific project use case
49 | * are an alternate approach to one of the registerable plugins
50 |
51 | For example, heading components could be done any number of ways. They could be simple or complex, fixed or responsive, wrapped for rich text or classnames only. For this reason, we've included several examples of these. If you need one of these plugins, it is recommended to copy and paste the plugin code into a custom plugin for your project.
52 |
53 | ---
54 |
55 |
56 |
57 |
58 |
59 | Visit [code.viget.com](http://code.viget.com) to see more projects from [Viget.](https://viget.com)
60 |
--------------------------------------------------------------------------------
/examples/rect/README.md:
--------------------------------------------------------------------------------
1 | # rect
2 |
3 | _This example has been **deprecated**. Consider using new [size utilities](https://tailwindcss.com/blog/tailwindcss-v3-4#new-size-utilities) in Tailwind 3.4 instead._
4 |
5 | This plugin adds rect (width + height) sizing utilities to Tailwind. It is different from the [installable version](plugins/rect) in that it removes the dependancy on the rem function and allows for edge cases requiring other units like px for sizing.
6 |
7 | ## Usage
8 |
9 | ```js
10 | theme: {
11 | rect: {
12 | target: ['44px', '44px'],
13 | '24': ['24px', '24px'],
14 | },
15 | }
16 | plugins: [
17 | require('./path/to/your/plugin'),
18 | // ...
19 | ],
20 | ```
21 |
22 | The above configuration would create the following css, as well as their responsive variants:
23 |
24 | ```css
25 | .rect-target {
26 | width: 44px;
27 | height: 44px;
28 | }
29 | .rect-24 {
30 | width: 24px;
31 | height: 24px;
32 | }
33 | ```
34 |
--------------------------------------------------------------------------------
/examples/rect/index.js:
--------------------------------------------------------------------------------
1 | const plugin = require('tailwindcss/plugin')
2 |
3 | module.exports = plugin(({ matchUtilities, theme }) => {
4 | matchUtilities(
5 | {
6 | rect: (value) => ({
7 | width: value[0],
8 | height: value[1],
9 | }),
10 | },
11 | { values: theme('rect') }
12 | )
13 | })
14 |
--------------------------------------------------------------------------------
/examples/resp-headings/README.md:
--------------------------------------------------------------------------------
1 | # resp-headings
2 |
3 | This plugin adds responsive heading components to Tailwind.
4 |
5 | The limitation to this plugin is that you cannot use `@apply .heading-key`. Tailwind explicitly prevents the use of `@apply` with "complex" components with media queries.
6 |
7 | ## Usage
8 |
9 | ```js
10 | theme: {
11 | headings: theme => ({
12 | '24': {
13 | sizes: {
14 | default: theme('fontSize.20'),
15 | md: theme('fontSize.24')
16 | },
17 | lineHeight: theme('lineHeight.snug'),
18 | weight: theme('fontWeight.bold')
19 | },
20 | })
21 | },
22 | plugins: [
23 | require('./config/tailwind/plugins/resp-headings'),
24 | ],
25 | ```
26 |
27 | Note: With the exception of `default`, the keys on the `sizes` object should match the keys in your `screens` Tailwind configuration.
28 |
29 | The above configuration would create the following css:
30 |
31 | ```css
32 | .heading-24 {
33 | font-size: 20px;
34 | font-weight: bold;
35 | letter-spacing: 2px;
36 | line-height: 1.4;
37 | }
38 |
39 | @media (min-width: 768px) {
40 | .heading-24 {
41 | font-size: 24px;
42 | }
43 | }
44 | ```
45 |
--------------------------------------------------------------------------------
/examples/resp-headings/index.js:
--------------------------------------------------------------------------------
1 | const plugin = require('tailwindcss/plugin')
2 |
3 | module.exports = plugin(({ addComponents, e, theme }) => {
4 | const screens = theme('screens')
5 | const pluginConfig = theme('headings', {})
6 |
7 | const component = Object.entries(pluginConfig).map(([name, props]) => {
8 | const { sizes, letterSpacing = '0', lineHeight, weight } = props
9 | const mediaQueries = []
10 |
11 | for (const key in screens) {
12 | if (key in sizes) {
13 | mediaQueries.push({
14 | [`@media (min-width: ${screens[key]})`]: {
15 | [`.${e(`heading-${name}`)}`]: {
16 | 'font-size': sizes[key]
17 | }
18 | }
19 | })
20 | }
21 | }
22 |
23 | return [
24 | {
25 | [`.${e(`heading-${name}`)}`]: {
26 | 'font-size': sizes['default'],
27 | 'font-weight': weight,
28 | 'letter-spacing': letterSpacing,
29 | 'line-height': lineHeight
30 | }
31 | },
32 | ...mediaQueries
33 | ]
34 | })
35 |
36 | addComponents(component)
37 | })
38 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | animate: require('./plugins/animate'),
3 | parentExpanded: require('./plugins/parent-expanded'),
4 | parentOpen: require('./plugins/parent-open'),
5 | rect: require('./plugins/rect'),
6 | sr: require('./plugins/sr'),
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@viget/tailwindcss-plugins",
3 | "version": "3.0.0",
4 | "scripts": {
5 | "format": "prettier --write './**/*.js' --ignore-path .gitignore",
6 | "test": "jest"
7 | },
8 | "description": "A collection of custom plugins for Tailwind CSS.",
9 | "main": "index.js",
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/vigetlabs/tailwindcss-plugins.git"
13 | },
14 | "keywords": [
15 | "tailwind",
16 | "tailwindcss",
17 | "tailwindcss-plugin"
18 | ],
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/vigetlabs/tailwindcss-plugins/issues"
22 | },
23 | "homepage": "https://github.com/vigetlabs/tailwindcss-plugins#readme",
24 | "devDependencies": {
25 | "jest": "^27.5.1",
26 | "jest-matcher-css": "^1.1.0",
27 | "lodash": "^4.17.21",
28 | "postcss": "^8.4.12",
29 | "prettier": "^2.6.2",
30 | "tailwindcss": "^3.0.24"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/plugins/animate/README.md:
--------------------------------------------------------------------------------
1 | # animate
2 |
3 | This plugin adds utilities for animating elements based on a dynamically added class. The main use case this addresses is animating elements in during scrolling. By adding a class via [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) or some other scroll listener, these utilities allow you to easily define the animations and apply staggered transitions. This has also been designed to work with the existing Tailwind utilities that control transition properties.
4 |
5 | ## Usage
6 |
7 | ### Config Example
8 |
9 | ```js
10 | theme: {
11 | animate: (theme) => ({
12 | triggerClass: '-observed',
13 | staggerDelay: {
14 | '100': '100ms',
15 | '200': '200ms',
16 | ...theme('transitionDelay'),
17 | },
18 | staggerInterval: {
19 | default: '100ms',
20 | '200': '200ms',
21 | ...theme('transitionDelay'),
22 | },
23 | maxItemIntervalSupport: 9,
24 | animations: {
25 | 'fade-up': {
26 | from: {
27 | transform: 'translateY(20px)',
28 | opacity: 0,
29 | },
30 | to: {
31 | transform: 'translateY(0)',
32 | opacity: 1,
33 | },
34 | },
35 | 'zoom-in': {
36 | from: {
37 | transform: 'scale(0.8)',
38 | opacity: 0,
39 | },
40 | // "to" is optional
41 | },
42 | },
43 | }),
44 | },
45 | plugins: [
46 | require('@viget/tailwindcss-plugins/animate'),
47 | ],
48 | ```
49 |
50 | ### Markup Examples
51 |
52 | #### Animate a single element (pending addition of `triggerClass`)
53 |
54 | ```html
55 |
Hello!
56 | ```
57 |
58 | ---
59 |
60 | #### Animate a single element after a delay
61 | ```html
62 |
Hello
63 | ```
64 | > N.b. `delay-` is a first-party Tailwind utility
65 |
66 | ---
67 |
68 | #### Stagger the animation of multiple elements, using the specified default interval
69 | ```html
70 |
71 |
1
72 |
2
73 |
3
74 |
75 | ```
76 | > N.b. `duration-` is a first-party Tailwind utility and **is required** on the child element unless a `transition-duration` is otherwise specified. This plugin doesn not apply a default duration in order to preserve customizability using the `duration-` utilities.
77 |
78 | ---
79 |
80 | #### Stagger the animation of multiple elements, overriding default interval
81 | ```html
82 |
83 |
1
84 |
2
85 |
3
86 |
87 | ```
88 |
89 | ---
90 |
91 | #### Stagger the animation of multiple elements, using specified interval, but delay the start
92 | ```html
93 |
94 |
1
95 |
2
96 |
3
97 |
98 | ```
99 |
100 | ## Configuration
101 |
102 | ### `triggerClass`
103 |
104 | Specify the class name that will be dynamically added to the element. Typically this class is added to indicate that the element has entered the viewport.
105 |
106 | ### `staggerDelay`
107 |
108 | Specify the delays for starting the staggered animations. It probably makes the most sense to simply set this to `theme('transitionDelay')`.
109 |
110 | ### `staggerInterval`
111 |
112 | Specify the amount of time in between the staggered animations. While optional, it is recommended to add a `default` entry to this object. Doing so allows you to use the `stagger-[animation]` class without specifying any `stagger-interval-[time]`.
113 |
114 | ### `maxItemIntervalSupport`
115 |
116 | The `transition-delay` used to power the staggered animation are calculated via a custom property on the child elements called `--animate-index`. This setting allows you to decide how many `nth-child` selectors should be automatically output with this custom property so you do not need to add it by hand.
117 |
118 | ### `animations`
119 |
120 | Specify the animation styles. The first-level object names the animation, which in turn **must** specify a `from` object and optionally a `to` object. These objects accept the same [CSS-in-JS syntax as Tailwind](https://tailwindcss.com/docs/plugins#css-in-js-syntax).
--------------------------------------------------------------------------------
/plugins/animate/index.js:
--------------------------------------------------------------------------------
1 | const plugin = require('tailwindcss/plugin')
2 |
3 | module.exports = plugin(({ addUtilities, e, theme }) => {
4 | const pluginConfig = theme('animate', {})
5 | const {
6 | triggerClass,
7 | staggerDelay,
8 | staggerInterval,
9 | maxItemIntervalSupport,
10 | animations,
11 | } = pluginConfig
12 |
13 | const animationUtilities = Object.entries(animations).map(
14 | ([name, config]) => {
15 | const from = {
16 | [`.${e(`animate-${name}`)}:not(.${e(triggerClass)}), .${e(
17 | `stagger-${name}`,
18 | )}:not(.${e(triggerClass)}) > *`]: config.from,
19 | }
20 |
21 | const to = config.to
22 | ? {
23 | [`.${e(`animate-${name}`)}.${e(triggerClass)},.${e(
24 | `stagger-${name}`,
25 | )}.${triggerClass} > *`]: config.to,
26 | }
27 | : {}
28 |
29 | return {
30 | ...from,
31 | ...to,
32 | }
33 | },
34 | )
35 |
36 | const staggerDefaultUtility = staggerInterval.default
37 | ? [
38 | {
39 | '[class*="stagger-"] > *': {
40 | '--stagger-delay': '0s',
41 | 'transition-delay': `calc(var(--animate-index) * ${staggerInterval.default} + var(--stagger-delay))`,
42 | },
43 | },
44 | ]
45 | : []
46 |
47 | const staggerIntervalUtilities = Object.entries(staggerInterval)
48 | .filter(([name]) => name !== 'default')
49 | .map(([name, value]) => ({
50 | [`.${e(`stagger-interval-${name}`)} > *`]: {
51 | '--stagger-delay': '0s',
52 | 'transition-delay': `calc(var(--animate-index) * ${value} + var(--stagger-delay))`,
53 | },
54 | }))
55 |
56 | const staggerDelayUtilities = Object.entries(staggerDelay).map(
57 | ([name, value]) => ({
58 | [`.${e(`stagger-delay-${name}`)} > *`]: {
59 | '--stagger-delay': value,
60 | },
61 | }),
62 | )
63 |
64 | const nthChildUtilities = [...Array(maxItemIntervalSupport)].map((_, i) => ({
65 | [`[class*="stagger"] > *:nth-child(${i + 1})`]: {
66 | '--animate-index': `${i + 1}`,
67 | },
68 | }))
69 |
70 | addUtilities([
71 | ...animationUtilities,
72 | ...staggerDefaultUtility,
73 | ...staggerIntervalUtilities,
74 | ...staggerDelayUtilities,
75 | ...nthChildUtilities,
76 | ])
77 | })
78 |
--------------------------------------------------------------------------------
/plugins/animate/test.js:
--------------------------------------------------------------------------------
1 | const cssMatcher = require('jest-matcher-css')
2 | const plugin = require('./index')
3 | const { run } = require('../../testing/run')
4 |
5 | expect.extend({
6 | toMatchCss: cssMatcher,
7 | })
8 |
9 | it('should generate the animate classes', () => {
10 | const config = {
11 | content: [
12 | {
13 | raw: String.raw`
14 |
43 |
44 | ```
45 |
46 | ## Alternate Approaches
47 |
48 | This can also be acheived with some more verbose approaches with Tailwind. The `open` [modifier](https://tailwindcss.com/docs/hover-focus-and-other-states#open-closed-state) is intended for use with `` and `