diff --git a/.eslintrc.json b/.eslintrc.json index e527cc9..61350b1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -19,5 +19,10 @@ "ignoreComments": true } ] - } + }, + "ignorePatterns": [ + "**/*.md", + "docs/*", + "__test/*" + ] } diff --git a/.github/workflows/node-tests.yml b/.github/workflows/node-tests.yml new file mode 100644 index 0000000..3dc4f24 --- /dev/null +++ b/.github/workflows/node-tests.yml @@ -0,0 +1,22 @@ +name: github-ci + +on: + push: + branches: [ master, develop ] + pull_request: + branches: [ master, develop ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14.x, 16.x, 18.x] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm test diff --git a/.npmignore b/.npmignore index 3e668a0..3044cb4 100644 --- a/.npmignore +++ b/.npmignore @@ -6,4 +6,4 @@ node_modules/ npm-debug.log *.test.js -.travis.yml +.github diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f7d347b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: node_js -cache: yarn -node_js: - - node - - "10" - - "8" -install: - - YARN_IGNORE_ENGINES=true yarn diff --git a/CHANGELOG.md b/CHANGELOG.md index 467e830..1690605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,39 @@ # Change Log This project adheres to [Semantic Versioning](http://semver.org/). +## 1.0.0 + +**Added** + +- Support for reducing the `calc()` output using [postcss-calc](https://github.com/postcss/postcss-calc/) (#70, #79) +- Support for updating the fluid base value's units – `vw` or `%` (#79) +- Support for disabling an option by setting it to `false` (#80) +- Option validation using `schema-utils` (#87) + +**Changed** + +- Drops support for PostCSS < 8 (#73) +- Updates dependencies (#72, #86) +- Updates option name: `site-max` and `siteMax` are now `max` (#79) +- Outputs a single value using a `min()` function, rather than adding media queries (#79) +- Support for using a `var()` function as the `max` option's value (#79) +- The `debug` option will now print a warning, rather than preserve the original declaration as a comment (#83) + +**Deprecated** + +- `tidy-*` properties will be removed in a future version (#81) + - `tidy-column` + - `tidy-offset` + - `tidy-span` + - `tidy-offset-left` + - `tidy-offset-right` + +**Removed** + +- The `breakpoints` option is no longer supported (#79) +- `tidy-span-full()` and `tidy-offset-full()` functions are no longer needed, so removed (#80) +- The plugin will no longer create a full-width media query, since it now outputs a single value (#80) + ## 0.4.0 **Added** diff --git a/Columns.js b/Columns.js deleted file mode 100644 index 618bc36..0000000 --- a/Columns.js +++ /dev/null @@ -1,200 +0,0 @@ -const { CUSTOM_PROP_REGEX } = require('./src/collectTidyRuleParams'); - -/** - * Columns class - * Calculate column and offset values based on processed options. - * - * @param {Object} options The options for the current rule. - */ -class Columns { - /** - * Round the given number to the specified number of decimal places. - * - * @param {Number} toRound The number to round. - * @param {Number} decimalPlaces The number of decimal places to round `toRound` to. - * - * @return {Number} - */ - static roundToPrecision(toRound, decimalPlaces) { - const precision = `1${'0'.repeat(decimalPlaces)}`; - - return (0 === toRound) ? 0 : Math.round((toRound + 0.00001) * precision) / precision; - } - - /** - * Separate a CSS length value's number from its units. - * - * @param {String} value A CSS length value. - * - * @return {Array} - */ - static splitCssUnit(value) { - return ('string' === typeof value) - ? [parseFloat(value), value.replace(/[\d.]/g, '')] - : value; - } - - constructor(options = {}) { - this.options = options; - - // Collect siteMaxValues to be used in column and offset calc() functions. - this.siteMaxValues = ['100vw']; - if (undefined !== this.options.siteMax) { - this.siteMaxValues.push(this.options.siteMax); - } - - this.fullWidthRule = null; - this.nonValues = [undefined, 0]; - - // Bind class methods. - this.getSharedGap = this.getSharedGap.bind(this); - this.getEdges = this.getEdges.bind(this); - this.getSingleColumn = this.getSingleColumn.bind(this); - this.buildCalcFunction = this.buildCalcFunction.bind(this); - this.spanCalc = this.spanCalc.bind(this); - this.offsetCalc = this.offsetCalc.bind(this); - - this.edges = this.getEdges(); - this.sharedGap = this.getSharedGap(); - - /** - * Suppress the `calc` string in the column output. - * - * @type {Boolean} - */ - this.suppressCalc = false; - } - - /** - * Calculate the shared gap amount to be removed from each column. - * - * @return {String|Number} - */ - getSharedGap() { - const { gap, columns } = this.options; - - if (CUSTOM_PROP_REGEX.test(gap)) { - return `(${gap} / ${columns} * (${columns} - 1))`; - } - - if (!this.nonValues.includes(gap)) { - const [value, units] = this.constructor.splitCssUnit(gap); - const sharedGap = (value / columns) * (columns - 1); - - return `${this.constructor.roundToPrecision(sharedGap, 4)}${units}`; - } - - return 0; - } - - /** - * Calculate the total edge spacing. - * - * @return {String|Number} - */ - getEdges() { - const { edge } = this.options; - - return this.nonValues.includes(edge) ? 0 : `${edge} * 2`; - } - - /** - * Build the column division for the appropriate siteMax and gaps. - * - * @param {String} siteMax The current siteMax size. - * - * @return {String} - */ - getSingleColumn(siteMax) { - const { columns } = this.options; - // 100vw : (100vw - 10px * 2) - const siteMaxSize = this.nonValues.includes(this.edges) - ? siteMax - : `(${siteMax} - ${this.edges})`; - - // 12 - 9.1667px : 12 - const columnReduction = (this.sharedGap) - ? `${columns} - ${this.sharedGap}` - : `${columns}`; - - return `${siteMaxSize} / ${columnReduction}`; - } - - /** - * Complete the calc() function. - * - * @param {String} siteMax The current siteMax size. - * @param {Number} colSpan The number of columns to span. - * @param {Number} gapSpan The number of gaps to span. - * - * @return {String} - */ - buildCalcFunction(siteMax, colSpan, gapSpan) { - const { gap } = this.options; - - // The base calc() equation. - let cssCalcEquation = this.getSingleColumn(siteMax); - - // Only multiply columns if there are more than one. - if (1 !== colSpan) { - cssCalcEquation = `(${cssCalcEquation}) * ${colSpan}`; - } - - /** - * Check for gaps before adding the math for them. - * Only multiply gaps if there are more than one. - */ - if (!this.nonValues.includes(gap) && !this.nonValues.includes(gapSpan)) { - const gapSpanCalc = (1 === gapSpan) ? gap : `${gap} * ${gapSpan}`; - - cssCalcEquation = `(${cssCalcEquation}) + ${gapSpanCalc}`; - } - - return `${this.suppressCalc ? '' : 'calc'}(${cssCalcEquation})`; - } - - /** - * Create the column `calc()` function declaration for each siteMax. - * - * @param {String|Number} colSpan The number of columns to span. - * - * @return {Object} - */ - spanCalc(colSpan) { - const columnSpan = parseFloat(colSpan, 10); - const [fluid, full] = this.siteMaxValues.map((siteMax) => { - /** - * Subtract from columnSpan, then round up to account for fractional columns. - * We are *always* spanning one more column than gap. - * Ensure we maintain the sign of the columnSpan value. - */ - const gapSpan = Math.ceil(columnSpan + (Math.sign(columnSpan) * -1)); - - return this.buildCalcFunction(siteMax, columnSpan, gapSpan); - }); - - return { fluid, full }; - } - - /** - * Create the offset `calc()` function declaration for each siteMax. - * - * @param {String|Number} colSpan The number of columns to offset. - * @param {Boolean} suppressCalc Suppress the `calc` string in the output. - * - * @return {Object} - */ - offsetCalc(colSpan) { - const columnSpan = parseFloat(colSpan, 10); - const [fluid, full] = this.siteMaxValues.map((siteMax) => { - // Round columnSpan down to account for fractional columns. - const gapSpan = Math.floor(columnSpan); - - return this.buildCalcFunction(siteMax, columnSpan, gapSpan); - }); - - return { fluid, full }; - } -} - -module.exports = Columns; diff --git a/README.md b/README.md index 269fa4a..3ef178b 100644 --- a/README.md +++ b/README.md @@ -1,302 +1,234 @@ -# Tidy Columns [![Build Status][ci-img]][ci] [![npm version][npmjs-img]][npmjs] +# Tidy Columns [![github-ci][gh-badge]][gh-ci] [![npm version][npmjs-img]][npmjs] [PostCSS] plugin to manage column alignment. -Tidy Columns sets widths and margins, based on a user-defined configuration, with the goal that any elements along the vertical axis that should be aligned, are. +Tidy Columns creates a global grid, allowing authors to set widths and margins while maintaining alignment along the y-axis. -**This plugin will not set layout for you. Layout is *your* job**. +## Get Started -See the [demo page][demo] to see it in action, and check out the [Wiki][wiki] for more about configuring the plugin. +See the [demo page][demo] to see it in action. -## Install +### Install ```shell -npm install postcss-tidy-columns +npm install --save-dev postcss postcss-tidy-columns ``` -## Example +### Usage ```js -require('postcss-tidy-columns')({ - columns: 12, - gap: '1.25rem', - edge: '2rem', - siteMax: '90rem', -}); +postcss([ require('postcss-tidy-columns') ]); ``` -```css -/* Input example, using the above plugins options */ -div { - tidy-span: 3; - tidy-offset-left: 2; -} -``` +See [PostCSS] docs for examples for your environment. -```css -/* Output example */ -div { - width: calc((((100vw - 2rem * 2) / 12 - 1.1458rem) * 3) + 1.25rem * 2); - max-width: calc((((90rem - 2rem * 2) / 12 - 1.1458rem) * 3) + 1.25rem * 2); - margin-left: calc((((100vw - 2rem * 2) / 12 - 1.1458rem) * 2) + 1.25rem * 2); +### Example + +```scss +/* Input example */ +@tidy columns var(--site-columns); +@tidy gap 1.25rem; +@tidy edge var(--site-edge); +@tidy max 80rem; + +:root { + --site-columns: 6; + --site-edge: 1.25rem; } -@media (min-width: 90rem) { - div { - margin-left: calc((((90rem - 2rem * 2) / 12 - 1.1458rem) * 2) + 1.25rem * 2); +@media (min-width: 48rem) { + :root { + --site-columns: 12; + --site-edge: 1.875rem; } } -``` - -## Usage -```js -postcss([ require('postcss-tidy-columns') ]); +div { + width: tidy-span(3); + margin-left: tidy-offset(2); +} ``` -See [PostCSS] docs for examples for your environment. - -## Contents - -* [Tidy Properties](#tidy-properties) -* [Tidy Functions](#tidy-functions) -* [Options](#options) -* [Options Cascade](#options-cascade) -* [Using CSS Custom Properties in setting values](#using-css-custom-properties-in-setting-values) +```scss +/* Output example */ +div { + width: calc((((min(100vw, 80rem) - var(--site-edge) * 2) / var(--site-columns) - (1.25rem / var(--site-columns) * (var(--site-columns) - 1))) * 3) + 2.5rem); + margin-left: calc((((min(100vw, 80rem) - var(--site-edge) * 2) / var(--site-columns) - (1.25rem / var(--site-columns) * (var(--site-columns) - 1))) * 2) + 2.5rem); +} +``` -See the [Wiki][wiki] for additional documentation and tips. +## API -## Tidy Properties +These functions output `calc()` and `min()` functions, so can be used anywhere those functions can be used. -### Span +### Span Function -The `tidy-span` property specifies the number of columns and adjacent column gaps the element should span. Supports positive integer and decimal values. +The `tidy-span()` function returns a `calc()` declaration for use in assigning widths. +> **Supports**: positive integer and decimal values. +> > #### Syntax > +> ```scss +> tidy-span() > ``` -> tidy-span: ; -> ``` - -### Offsets -The `tidy-offset-left` and `tidy-offset-right` properties specify the number of columns and adjacent column gaps the element's margin should span. Supports positive and negative integer and decimal values +### Offset Function -Offsets use a [`siteMax`](#sitemax) breakpoint, since there's no `max-margin` CSS property. +The `tidy-offset()` function returns a `calc()` declaration for use in positioning. +> **Supports**: positive and negative integer and decimal values +> > #### Syntax > -> ``` -> tidy-offset-left: ; -> tidy-offset-right: ; +> ```scss +> tidy-offset() > ``` -### Column Shorthand - -`tidy-column` is a shorthand property for setting `tidy-offset-left`, `tidy-span`, and `tidy-offset-right` in one declaration. +### Var Function -Use `none` to bypass a required value. A single offset value applies to both `left` and `right`. +`tidy-var()` functions are replaced by the specified option's value. > #### Syntax > -> ``` -> [ | none ] / span && [ / ]? +> ```scss +> tidy-var() > ``` > +> #### Examples +> +> ```scss +> tidy-var(max) // => 80rem +> tidy-var(edge) // => var(--site-edge) > ``` -> tidy-column: 3 / span 2 / 4; -> tidy-column: none / span 4 / 1; -> tidy-column: 1 / span 4; -> ``` - -### Offset Shorthand -`tidy-offset` is a shorthand property for setting `tidy-offset-left` and `tidy-offset-right` in one declaration. +See the [Examples Wiki page](https://github.com/goodguyry/postcss-tidy-columns/wiki/Examples) for more. -Use `none` to bypass a required value. A single value applies to both `left` and `right`. +## Options -> #### Syntax -> -> ``` -> [ | none ] [ / ]? */ -> ``` -> -> ``` -> tidy-offset: 3 / 4; -> tidy-offset: none / 1; -> tidy-offset: 1; -> ``` +Tidy Columns can be configured with `@tidy` at-rules in the CSS or via the plugin options object. -## Tidy Functions +⚠️ Depending on how your build tool processes files, a loader like [sass-resources-loader](https://www.npmjs.com/package/sass-resources-loader) may be required to include `@tidy` at-rules in every Sass partial. The PostCSS JavaScript API is the best way to ensure options are globally available. -These functions are provided for incorporating the `tidy-` properties' output without using the properties themselves. These can be used on their own or nested inside a `calc()` function, and allow for more control over the declarations added by the plugin. +```js +require('postcss-tidy-columns')({ + columns: 'var(--site-columns)', + gap: '1.25rem', + edge: 'var(--site-edge)', + max: '80rem', +}); +``` -When using these functions, **the `siteMax`-based static value will not be output**. Use the `tidy-span-full()` and `tidy-offset-full()` functions to set the static `span` and `offset` widths, respectively. +Options can be set to either a static value or a `var()` function: -### Span Function +* **Static Values** will reduce the size of the output, but should only be used if the option's value **should not** change. +* **CSS Custom Properties** offer much more flexibility, as they'll dynamically update during runtime. -`tidy-span()` and `tidy-span-full()` functions return the `span` property's `calc()` declaration for use in assigning widths. +Override an option by redeclaring it in a rule; disable it altogether with `@tidy