├── .eslintrc.json ├── .gitattributes ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── jestconfig.coverage.json ├── package.json ├── src ├── plugin.js └── util │ └── extractGridLineNames.js └── test ├── plugin.test.js └── util └── extractGridLineNames.test.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "parserOptions": { 6 | "ecmaVersion": 2020, 7 | "sourceType": "module" 8 | }, 9 | "extends": ["prettier"], 10 | "plugins": ["prettier"], 11 | "rules": { 12 | "camelcase": ["error", { "allow": ["^unstable_"] }], 13 | "no-unused-vars": [2, { "args": "all", "argsIgnorePattern": "^_" }], 14 | "no-warning-comments": 0, 15 | "prettier/prettier": "error" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ${{ matrix.operating-system }} 12 | 13 | strategy: 14 | matrix: 15 | operating-system: [ubuntu-latest, windows-latest, macos-latest] 16 | node-version: [ 12, 14, 16 ] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - run: npm install 26 | - run: npm test 27 | env: 28 | CI: true 29 | 30 | coverage: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v2 34 | 35 | - name: Publish coverage report 36 | uses: actions/setup-node@v1 37 | with: 38 | node-version: 12 39 | - run: npm install 40 | - run: npm run test:coverage 41 | - run: bash <(curl -s https://codecov.io/bash) 42 | env: 43 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | coverage 3 | node_modules 4 | 5 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | coverage/ 3 | test/ 4 | .*.json 5 | jestconfig.coverage.json 6 | .idea/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.1 - 2022-02-04 4 | * Fixed misspelling of Tailwind CSS in documentation 5 | 6 | ## 2.0.0 - 2021-12-14 7 | * Tested against Tailwind CSS v3 8 | * Updated minimum node requirements (v12) 9 | * Updated documentation 10 | 11 | ## 1.5.0 - 2021-02-23 12 | * Fixed dependency for lodash in package.json. 13 | * Minor formatting improvements in README. 14 | * Remove .idea from npm package. 15 | 16 | ## 1.4.1 - 2020-12-11 17 | * Fixed links for bugs and homepage in package.json. 18 | 19 | ## 1.4.0 - 2020-12-11 20 | * Added npmignore to lighten the package size. 21 | 22 | ## 1.3.0 - 2020-11-27 23 | * Updated to work with Tailwind CSS version 2. 24 | * Added CHANGELOG. 25 | 26 | ## 1.2.2 - 2020-10-26 27 | * Back out malformed repeats gracefully. 28 | 29 | ## 1.2.1 - 2020-10-25 30 | * Linting. 31 | 32 | ## 1.2.0 - 2020-10-25 33 | * Support for repeat(). 34 | 35 | ## 1.1.6 - 2020-10-21 36 | * Fixed CSS output for repeated name labels. 37 | 38 | ## 1.1.5 - 2020-10-18 39 | * GitHub workflow and code coverage reports. 40 | 41 | ## 1.1.4 - 2020-10-17 42 | * Fixed links to homepage and bug reporting in package information. 43 | 44 | ## 1.1.3 - 2020-10-17 45 | * Fixed CSS property names for grid-column-start and grid-column-end. 46 | * Tidied up examples in the README. 47 | 48 | ## 1.1.2 - 2020-10-15 49 | * Fixed name of LICENSE file. 50 | 51 | ## 1.1.1 - 2020-10-15 52 | * Fixed missing column end utilities. 53 | * Fixed usage examples in README. 54 | * Fixed LICENCE link in README. 55 | 56 | ## 1.1.0 - 2020-10-15 57 | * Streamlined loops and logic. 58 | * Updated package information. 59 | 60 | ## 1.0.0 - 2020-10-14 61 | * Initial release. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tailwind CSS Grid Names 2 | 3 | [![Latest Version on NPM](https://img.shields.io/npm/v/@savvywombat/tailwindcss-grid-named-lines)](https://www.npmjs.com/package/@savvywombat/tailwindcss-grid-named-lines) 4 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/SavvyWombat/tailwindcss-grid-named-lines/blob/main/LICENSE) 5 | [![Build](https://img.shields.io/github/workflow/status/SavvyWombat/tailwindcss-grid-named-lines/Test?label=build)](https://github.com/SavvyWombat/tailwindcss-grid-named-lines/actions) 6 | [![Code Coverage](https://codecov.io/gh/SavvyWombat/tailwindcss-grid-named-lines/branch/main/graph/badge.svg)](https://codecov.io/gh/SavvyWombat/tailwindcss-grid-named-lines) 7 | 8 | A plugin to provide Tailwind CSS utilities for named grid lines. 9 | 10 | ## Installation 11 | 12 | ``` 13 | # npm 14 | npm install --save-dev @savvywombat/tailwindcss-grid-named-lines 15 | 16 | # yarn 17 | yarn add --dev @savvywombat/tailwindcss-grid-named-lines 18 | ``` 19 | 20 | ## Usage 21 | 22 | See the documentation at https://savvywombat.com.au/tailwind-css/named-grid-lines/ 23 | 24 | ## Changelog 25 | 26 | [Changelog](https://github.com/SavvyWombat/tailwindcss-grid-named-lines/blob/main/CHANGELOG.md) 27 | 28 | ## Related packages 29 | 30 | ### [Tailwind CSS Grid Areas](https://github.com/SavvyWombat/tailwindcss-grid-areas) 31 | 32 | A plugin to provide Tailwind CSS utilities for grid areas. 33 | 34 | ## Licence 35 | 36 | [MIT](https://github.com/SavvyWombat/tailwindcss-grid-named-lines/blob/main/LICENSE) 37 | -------------------------------------------------------------------------------- /jestconfig.coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectCoverage": true 3 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@savvywombat/tailwindcss-grid-named-lines", 3 | "version": "2.0.1", 4 | "description": "A plugin to provide Tailwind CSS utilities for named grid lines.", 5 | "keywords": [ 6 | "tailwind", 7 | "tailwindcss", 8 | "css", 9 | "css grid", 10 | "css grid lines" 11 | ], 12 | "author": "Stuart Jones ", 13 | "license": "MIT", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/savvywombat/tailwindcss-grid-named-lines.git" 17 | }, 18 | "bugs": "https://github.com/savvywombat/tailwindcss-grid-named-lines/issues", 19 | "homepage": "https://github.com/savvywombat/tailwindcss-grid-named-lines#readme", 20 | "main": "src/plugin.js", 21 | "scripts": { 22 | "style": "eslint .", 23 | "test": "jest && eslint .", 24 | "test:coverage": "jest --config jestconfig.coverage.json" 25 | }, 26 | "dependencies": { 27 | "lodash": "^4.17.21" 28 | }, 29 | "devDependencies": { 30 | "@babel/cli": "^7.16.0", 31 | "@babel/core": "^7.16.0", 32 | "@babel/node": "^7.16.0", 33 | "@babel/preset-env": "^7.16.4", 34 | "babel-jest": "^27.4.4", 35 | "clean-css": "^5.2.2", 36 | "eslint": "^8.4.1", 37 | "eslint-config-prettier": "^8.3.0", 38 | "eslint-plugin-prettier": "^4.0.0", 39 | "jest": "^27.4.4", 40 | "jest-matcher-css": "^1.1.0", 41 | "postcss": "^8.4.5", 42 | "prettier": "^2.5.1", 43 | "tailwindcss": "^3.0.1" 44 | }, 45 | "babel": { 46 | "presets": [ 47 | [ 48 | "@babel/preset-env", 49 | { 50 | "targets": { 51 | "node": "12.22.0" 52 | } 53 | } 54 | ] 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/plugin.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | const extractGridLineNames = require("./util/extractGridLineNames"); 3 | 4 | module.exports = function ({ addUtilities, theme }) { 5 | const namedGridRows = extractGridLineNames(theme("gridTemplateRows")); 6 | const namedGridColumns = extractGridLineNames(theme("gridTemplateColumns")); 7 | 8 | const prefixes = [ 9 | { 10 | utility: "row-start", 11 | class: "grid-row-start", 12 | lines: namedGridRows, 13 | }, 14 | { 15 | utility: "row-end", 16 | class: "grid-row-end", 17 | lines: namedGridRows, 18 | }, 19 | { 20 | utility: "col-start", 21 | class: "grid-column-start", 22 | lines: namedGridColumns, 23 | }, 24 | { 25 | utility: "col-end", 26 | class: "grid-column-end", 27 | lines: namedGridColumns, 28 | }, 29 | ]; 30 | 31 | const namedLines = prefixes.reduce((lines, prefix) => { 32 | return { 33 | ...lines, 34 | ..._.fromPairs( 35 | _.map(prefix.lines, (name) => [ 36 | `.${prefix.utility}-${name.replace(" ", "-")}`, 37 | { 38 | [prefix.class]: name, 39 | }, 40 | ]) 41 | ), 42 | }; 43 | }, {}); 44 | 45 | addUtilities({ ...namedLines }, []); 46 | }; 47 | -------------------------------------------------------------------------------- /src/util/extractGridLineNames.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | 3 | module.exports = function (gridTemplate) { 4 | return _.uniq( 5 | _.flatMap(gridTemplate, (value) => { 6 | if (!value.match(/\[([^\]]*)\]/g)) { 7 | return []; 8 | } 9 | 10 | const matches = [ 11 | // extract grid line names from the gridTemplate (including names used in repeat(n, def) 12 | ..._.flatMap(value.match(/\[([^\]]*)\]/g), (match) => { 13 | return match.substring(1, match.length - 1).split(/\s+/); 14 | }), 15 | // extract repeat(n, def) 16 | ..._.flatMap(value.match(/repeat\([^\)]*\)/g), (repeat) => { 17 | const found = repeat.match( 18 | /\((?[0-9]+),\s*(\[(?[^\]]+)\])?[^\[]+(\[(?[^\]]+)\])?/ 19 | ); 20 | 21 | if (found === null) { 22 | return []; 23 | } 24 | 25 | let result = []; 26 | // start at 1 here reduce the number of repeated names by one 27 | // because the first match above will include the names from repeat(n, def) 28 | for (let i = 1; i < found.groups.count; i++) { 29 | if (typeof found.groups.first !== "undefined") { 30 | result.push(found.groups.first); 31 | } 32 | if (typeof found.groups.second !== "undefined") { 33 | result.push(found.groups.second); 34 | } 35 | } 36 | 37 | return result; 38 | }), 39 | ]; 40 | // create a unique list of names, including counts of how many times that name is used 41 | const counts = _.fromPairs( 42 | matches 43 | .filter((v, i, a) => a.indexOf(v) === i) 44 | .map((match) => { 45 | return [ 46 | match, 47 | { 48 | count: 1, 49 | total: matches.reduce((count, m) => { 50 | return match === m ? ++count : count; 51 | }, 0), 52 | }, 53 | ]; 54 | }) 55 | ); 56 | 57 | // create a list of grid line names for this template, indexing repeated names 58 | return matches.map((match) => { 59 | if (counts[match].total === 1) { 60 | return match; 61 | } 62 | 63 | return `${match} ${counts[match].count++}`; 64 | }); 65 | }) 66 | ); 67 | }; 68 | -------------------------------------------------------------------------------- /test/plugin.test.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import escapeClassName from "tailwindcss/lib/util/escapeClassName"; 3 | import plugin from "../src/plugin"; 4 | 5 | test("multiple templates with different names", () => { 6 | const addedUtilities = []; 7 | 8 | const config = { 9 | theme: { 10 | gridTemplateRows: { 11 | layout: "1fr [top] 1fr [bottom] 1fr", 12 | multi: "1fr [fold] 1fr [fold] 1fr", 13 | }, 14 | gridTemplateColumns: { 15 | layout: "1fr [left] 1fr [right] 1fr", 16 | }, 17 | }, 18 | variants: {}, 19 | }; 20 | 21 | const getConfigValue = (path, defaultValue) => 22 | _.get(config, path, defaultValue); 23 | const pluginApi = { 24 | config: getConfigValue, 25 | e: escapeClassName, 26 | theme: (path, defaultValue) => 27 | getConfigValue(`theme.${path}`, defaultValue), 28 | variants: (path, defaultValue) => { 29 | if (_.isArray(config.variants)) { 30 | return config.variants; 31 | } 32 | 33 | return getConfigValue(`variants.${path}`, defaultValue); 34 | }, 35 | addUtilities(utilities, variants) { 36 | addedUtilities.push({ 37 | utilities, 38 | variants, 39 | }); 40 | }, 41 | }; 42 | 43 | plugin(pluginApi); 44 | 45 | expect(addedUtilities).toEqual([ 46 | { 47 | utilities: { 48 | ".row-start-top": { 49 | "grid-row-start": "top", 50 | }, 51 | ".row-start-bottom": { 52 | "grid-row-start": "bottom", 53 | }, 54 | ".row-start-fold-1": { 55 | "grid-row-start": "fold 1", 56 | }, 57 | ".row-start-fold-2": { 58 | "grid-row-start": "fold 2", 59 | }, 60 | ".row-end-top": { 61 | "grid-row-end": "top", 62 | }, 63 | ".row-end-bottom": { 64 | "grid-row-end": "bottom", 65 | }, 66 | ".row-end-fold-1": { 67 | "grid-row-end": "fold 1", 68 | }, 69 | ".row-end-fold-2": { 70 | "grid-row-end": "fold 2", 71 | }, 72 | ".col-start-left": { 73 | "grid-column-start": "left", 74 | }, 75 | ".col-start-right": { 76 | "grid-column-start": "right", 77 | }, 78 | ".col-end-left": { 79 | "grid-column-end": "left", 80 | }, 81 | ".col-end-right": { 82 | "grid-column-end": "right", 83 | }, 84 | }, 85 | variants: [], 86 | }, 87 | ]); 88 | }); 89 | -------------------------------------------------------------------------------- /test/util/extractGridLineNames.test.js: -------------------------------------------------------------------------------- 1 | import extractGridLineNames from "../../src/util/extractGridLineNames"; 2 | 3 | test("passing nothing gives you an empty list", () => { 4 | expect(extractGridLineNames()).toEqual([]); 5 | }); 6 | 7 | test("no names in the gridTemplate definition gives an empty list", () => { 8 | const gridTemplateRows = { 9 | layout: "1fr 1fr 1fr", 10 | }; 11 | 12 | expect(extractGridLineNames(gridTemplateRows)).toEqual([]); 13 | }); 14 | 15 | test("lists the named grid lines", () => { 16 | const gridTemplateRows = { 17 | layout: "1fr [left] 1fr [right] 1fr", 18 | }; 19 | 20 | expect(extractGridLineNames(gridTemplateRows)).toEqual(["left", "right"]); 21 | }); 22 | 23 | test("grid lines with the same name are indexed", () => { 24 | const gridTemplateRows = { 25 | layout: "1fr [column] 1fr [column] 1fr", 26 | }; 27 | 28 | expect(extractGridLineNames(gridTemplateRows)).toEqual([ 29 | "column 1", 30 | "column 2", 31 | ]); 32 | }); 33 | 34 | test("multiple names on the same grid line are valid", () => { 35 | const gridTemplateRows = { 36 | layout: "1fr [left middle] 1fr [right] 1fr", 37 | }; 38 | 39 | expect(extractGridLineNames(gridTemplateRows)).toEqual([ 40 | "left", 41 | "middle", 42 | "right", 43 | ]); 44 | }); 45 | 46 | test("spaces between multiple names are not important", () => { 47 | const gridTemplateRows = { 48 | layout: "1fr [left middle] 1fr [right] 1fr", 49 | }; 50 | 51 | expect(extractGridLineNames(gridTemplateRows)).toEqual([ 52 | "left", 53 | "middle", 54 | "right", 55 | ]); 56 | }); 57 | 58 | test("multiple gridTemplates can use the same grid line names", () => { 59 | const gridTemplateRows = { 60 | layout: "1fr [left] 1fr [right] 1fr", 61 | other: "[left] 1fr [right] 1fr", 62 | multi: "1fr [column] 1fr [column] 1fr", 63 | four: "[column] 1fr [column] 1fr [column]", 64 | }; 65 | 66 | expect(extractGridLineNames(gridTemplateRows)).toEqual([ 67 | "left", 68 | "right", 69 | "column 1", 70 | "column 2", 71 | "column 3", 72 | ]); 73 | }); 74 | 75 | test("supports repeat", () => { 76 | const gridTemplateRows = { 77 | layout: "repeat(2, [line] 1fr)", 78 | }; 79 | 80 | expect(extractGridLineNames(gridTemplateRows)).toEqual(["line 1", "line 2"]); 81 | }); 82 | 83 | test("ignores badly formatted repeats", () => { 84 | const gridTemplateRows = { 85 | layout: "repeat([column] 1fr)", 86 | other: "repeat(, [column] 1fr)", 87 | }; 88 | 89 | expect(extractGridLineNames(gridTemplateRows)).toEqual(["column"]); 90 | }); 91 | 92 | test("ignores repeat with no lines", () => { 93 | const gridTemplateRows = { 94 | layout: "[outstart] repeat(2, 1fr) [outend]", 95 | }; 96 | 97 | expect(extractGridLineNames(gridTemplateRows)).toEqual([ 98 | "outstart", 99 | "outend", 100 | ]); 101 | }); 102 | 103 | test("includes start and end from repeat", () => { 104 | const gridTemplateRows = { 105 | layout: "repeat(2, [instart] 1fr [inend])", 106 | }; 107 | 108 | expect(extractGridLineNames(gridTemplateRows)).toEqual( 109 | expect.arrayContaining(["instart 1", "instart 2", "inend 1", "inend 2"]) 110 | ); 111 | }); 112 | 113 | test("supports multiple repeats", () => { 114 | const gridTemplateRows = { 115 | layout: "repeat(2, [line] 1fr) repeat(2, [more] 2fr)", 116 | }; 117 | 118 | expect(extractGridLineNames(gridTemplateRows)).toEqual( 119 | expect.arrayContaining(["line 1", "line 2", "more 1", "more 2"]) 120 | ); 121 | }); 122 | --------------------------------------------------------------------------------