├── package.json ├── README.md └── index.js /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tailwindcss-padding-safe", 3 | "version": "1.0.6", 4 | "description": "Tailwind CSS plugin to generate padding utilities with safe-area-inset.", 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/desaintflorent/tailwindcss-padding-safe.git" 12 | }, 13 | "keywords": [ 14 | "tailwindcss", 15 | "padding", 16 | "safe-area-inset", 17 | "tailwind", 18 | "notch" 19 | ], 20 | "author": "Thibaut De Saint Florent ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/desaintflorent/tailwindcss-padding-safe/issues" 24 | }, 25 | "homepage": "https://github.com/desaintflorent/tailwindcss-padding-safe#readme", 26 | "dependencies": { 27 | "lodash": "^4.17.21" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tailwind CSS Padding Safe Plugin 2 | 3 | This plugin is based on the [tailwindcss](https://github.com/tailwindcss/tailwindcss/tree/v1.0.0-beta.4) framework. Usage is similar to the core padding plugin, but outputs `px-[value]-safe` padding, instead of `px-[value]`. 4 | This is my first package so don't hesitate to point me on any improvement or issue. 5 | 6 | ## Installation 7 | 8 | ```bash 9 | npm install -D tailwindcss-padding-safe 10 | ``` 11 | 12 | ## Usage 13 | 14 | ```js 15 | // In your Tailwind JS config 16 | { 17 | plugins: [require("tailwindcss-padding-safe")] 18 | } 19 | ``` 20 | 21 | This plugin generates the following utilities: 22 | + the same with `m` prefix for margin 23 | 24 | ```css 25 | /* Default rules for browser without max() support same as core padding generated rules */ 26 | 27 | .p-[value]-safe { 28 | padding: [value]rem; 29 | } 30 | .py-[value]-safe { 31 | padding-top: [value]rem; 32 | padding-bottom: [value]rem; 33 | } 34 | .px-[value]-safe { 35 | padding-left: [value]rem; 36 | padding-right: [value]rem; 37 | } 38 | .pt-[value]-safe { 39 | padding-top: [value]rem; 40 | } 41 | .pb-[value]-safe { 42 | padding-bottom: [value]rem; 43 | } 44 | .pl-[value]-safe { 45 | padding-left: [value]rem; 46 | } 47 | .pr-[value]-safe { 48 | padding-right: [value]rem; 49 | } 50 | 51 | /* Safe area rules for browser with max() support */ 52 | 53 | @supports (padding: max(0px)) { 54 | .p-[value]-safe { 55 | padding-top: max([value]rem, env(safe-area-inset-top)); 56 | padding-bottom: max([value]rem, env(safe-area-inset-bottom)); 57 | padding-left: max([value]rem, env(safe-area-inset-left)); 58 | padding-right: max([value]rem, env(safe-area-inset-right)); 59 | } 60 | .py-[value]-safe { 61 | padding-top: max([value]rem, env(safe-area-inset-top)); 62 | padding-bottom: max([value]rem, env(safe-area-inset-bottom)); 63 | } 64 | .px-[value]-safe { 65 | padding-left: max([value]rem, env(safe-area-inset-left)); 66 | padding-right: max([value]rem, env(safe-area-inset-right)); 67 | } 68 | .pt-[value]-safe { 69 | padding-top: max([value]rem, env(safe-area-inset-top)); 70 | } 71 | .pb-[value]-safe { 72 | padding-bottom: max([value]rem, env(safe-area-inset-bottom)); 73 | } 74 | .pl-[value]-safe { 75 | padding-left: max([value]rem, env(safe-area-inset-left)); 76 | } 77 | .pr-[value]-safe { 78 | padding-right: max([value]rem, env(safe-area-inset-right)); 79 | } 80 | } 81 | ``` 82 | 83 | ## Options 84 | 85 | It's working out of the box with your current padding options ! ( Pro tip : use purgecss ) 86 | But if you need, you can set options in your tailwindcss.js like this : 87 | 88 | ```js 89 | // In your Tailwind CSS config 90 | theme: { 91 | paddingSafe:{ 92 | padding: { 93 | '1': '1rem', 94 | }, 95 | suffix: { 96 | 'notch' 97 | }, 98 | onlySupportsRules : true 99 | }, 100 | }, 101 | variants: { 102 | paddingSafe: [ 'responsive' ], 103 | }, 104 | ``` 105 | 106 | `theme.paddingSafe.padding` is optional and it defaults to theme.padding 107 | `theme.paddingSafe.suffix` is optional and it defaults to "safe" 108 | `theme.paddingSafe.onlySupportsRules` is optional and it defaults to false 109 | `variants.paddingSafe` is optional and it defaults to variants.padding 110 | 111 | Use it in your html like this : 112 | 113 | ```html 114 |
Example of block
115 | ``` 116 | 117 | If you don't want to generate default rules for browser without support for `max()`, you can set the `onlySupportsRules` option to `true`. But then, you will not get any padding for browser without support, so you should add default core padding too : 118 | 119 | ```html 120 |
Example of block
121 | ``` 122 | 123 | ## Warning 124 | 125 | Unitless values inside max() are considered invalid. So if you define custom value in your theme option, keep in mind that you need to use a unit with your value like this : 126 | 127 | ``` 128 | '1': '1rem', //not '1': '1' 129 | ``` 130 | 131 | Or 132 | 133 | ``` 134 | '0': '0px', //not '0': '0' 135 | ``` 136 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const plugin = require('tailwindcss/plugin'); 2 | const { flatMap } = require('lodash'); 3 | 4 | module.exports = plugin(function({ addUtilities, e, config }) { 5 | // Paddings 6 | const paddings = config('theme.paddingSafe.padding', config('theme.padding', {})); 7 | const paddingVariants = config('variants.paddingSafe', config('variants.padding', {})); 8 | const paddingSuffix = config('theme.paddingSafe.suffix', 'safe'); 9 | const paddingOnlySupportsRules = config('theme.paddingSafe.onlySupportsRules', false); 10 | 11 | const paddingGenerators = [ 12 | (size, modifier) => (paddingOnlySupportsRules ? null : { 13 | [`.${e(`p-${modifier}-${paddingSuffix}`)}`]: { 'padding': `${size}` }, 14 | }), 15 | (size, modifier) => (paddingOnlySupportsRules ? null : { 16 | [`.${e(`py-${modifier}-${paddingSuffix}`)}`]: { 'padding-top': `${size}`, 'padding-bottom': `${size}` }, 17 | [`.${e(`px-${modifier}-${paddingSuffix}`)}`]: { 'padding-left': `${size}`, 'padding-right': `${size}` }, 18 | }), 19 | (size, modifier) => (paddingOnlySupportsRules ? null : { 20 | [`.${e(`pt-${modifier}-${paddingSuffix}`)}`]: { 'padding-top': `${size}` }, 21 | [`.${e(`pr-${modifier}-${paddingSuffix}`)}`]: { 'padding-right': `${size}` }, 22 | [`.${e(`pb-${modifier}-${paddingSuffix}`)}`]: { 'padding-bottom': `${size}` }, 23 | [`.${e(`pl-${modifier}-${paddingSuffix}`)}`]: { 'padding-left': `${size}` }, 24 | }), 25 | (size, modifier) => ({ 26 | '@supports(padding: max(0px))': { 27 | [`.${e(`p-${modifier}-${paddingSuffix}`)}`]: { 'padding-top': `max(${size}, env(safe-area-inset-top))`, 'padding-bottom': `max(${size}, env(safe-area-inset-bottom))` , 'padding-left': `max(${size}, env(safe-area-inset-left))` , 'padding-right': `max(${size}, env(safe-area-inset-right))` }, 28 | 29 | [`.${e(`py-${modifier}-${paddingSuffix}`)}`]: { 'padding-top': `max(${size}, env(safe-area-inset-top))`, 'padding-bottom': `max(${size}, env(safe-area-inset-bottom))` }, 30 | [`.${e(`px-${modifier}-${paddingSuffix}`)}`]: { 'padding-left': `max(${size}, env(safe-area-inset-left))`, 'padding-right': `max(${size}, env(safe-area-inset-right))` }, 31 | 32 | [`.${e(`pt-${modifier}-${paddingSuffix}`)}`]: { 'padding-top': `max(${size}, env(safe-area-inset-top))` }, 33 | [`.${e(`pr-${modifier}-${paddingSuffix}`)}`]: { 'padding-right': `max(${size}, env(safe-area-inset-right))` }, 34 | [`.${e(`pb-${modifier}-${paddingSuffix}`)}`]: { 'padding-bottom': `max(${size}, env(safe-area-inset-bottom))` }, 35 | [`.${e(`pl-${modifier}-${paddingSuffix}`)}`]: { 'padding-left': `max(${size}, env(safe-area-inset-left))` }, 36 | } 37 | }) 38 | ]; 39 | 40 | const paddingUtilities = flatMap(paddingGenerators, generator => { 41 | return flatMap(paddings, generator) 42 | }); 43 | 44 | addUtilities(paddingUtilities, paddingVariants); 45 | 46 | // Margins 47 | const margins = config('theme.marginSafe.margin', config('theme.margin', {})); 48 | const marginVariants = config('variants.marginSafe', config('variants.margin', {})); 49 | const marginSuffix = config('theme.marginSafe.suffix', 'safe'); 50 | const marginOnlySupportsRules = config('theme.marginSafe.onlySupportsRules', false); 51 | 52 | const marginGenerators = [ 53 | (size, modifier) => (marginOnlySupportsRules ? null : { 54 | [`.${e(`m-${modifier}-${marginSuffix}`)}`]: { 'margin': `${size}` }, 55 | }), 56 | (size, modifier) => (marginOnlySupportsRules ? null : { 57 | [`.${e(`my-${modifier}-${marginSuffix}`)}`]: { 'margin-top': `${size}`, 'margin-bottom': `${size}` }, 58 | [`.${e(`mx-${modifier}-${marginSuffix}`)}`]: { 'margin-left': `${size}`, 'margin-right': `${size}` }, 59 | }), 60 | (size, modifier) => (marginOnlySupportsRules ? null : { 61 | [`.${e(`mt-${modifier}-${marginSuffix}`)}`]: { 'margin-top': `${size}` }, 62 | [`.${e(`mr-${modifier}-${marginSuffix}`)}`]: { 'margin-right': `${size}` }, 63 | [`.${e(`mb-${modifier}-${marginSuffix}`)}`]: { 'margin-bottom': `${size}` }, 64 | [`.${e(`ml-${modifier}-${marginSuffix}`)}`]: { 'margin-left': `${size}` }, 65 | }), 66 | (size, modifier) => ({ 67 | '@supports(margin: max(0px))': { 68 | [`.${e(`m-${modifier}-${marginSuffix}`)}`]: { 'margin-top': `max(${size}, env(safe-area-inset-top))`, 'margin-bottom': `max(${size}, env(safe-area-inset-bottom))` , 'margin-left': `max(${size}, env(safe-area-inset-left))` , 'margin-right': `max(${size}, env(safe-area-inset-right))` }, 69 | 70 | [`.${e(`my-${modifier}-${marginSuffix}`)}`]: { 'margin-top': `max(${size}, env(safe-area-inset-top))`, 'margin-bottom': `max(${size}, env(safe-area-inset-bottom))` }, 71 | [`.${e(`mx-${modifier}-${marginSuffix}`)}`]: { 'margin-left': `max(${size}, env(safe-area-inset-left))`, 'margin-right': `max(${size}, env(safe-area-inset-right))` }, 72 | 73 | [`.${e(`mt-${modifier}-${marginSuffix}`)}`]: { 'margin-top': `max(${size}, env(safe-area-inset-top))` }, 74 | [`.${e(`mr-${modifier}-${marginSuffix}`)}`]: { 'margin-right': `max(${size}, env(safe-area-inset-right))` }, 75 | [`.${e(`mb-${modifier}-${marginSuffix}`)}`]: { 'margin-bottom': `max(${size}, env(safe-area-inset-bottom))` }, 76 | [`.${e(`ml-${modifier}-${marginSuffix}`)}`]: { 'margin-left': `max(${size}, env(safe-area-inset-left))` }, 77 | } 78 | }) 79 | ] 80 | 81 | const marginUtilities = flatMap(marginGenerators, generator => { 82 | return flatMap(margins, generator) 83 | }); 84 | 85 | addUtilities(marginUtilities, marginVariants); 86 | 87 | // Safe area only classes 88 | const utilities = [{ 89 | '.p-safe': { 'padding-top': 'env(safe-area-inset-top)', 'padding-bottom': 'env(safe-area-inset-bottom)', 'padding-left': 'env(safe-area-inset-left)', 'padding-right': 'env(safe-area-inset-right)' }, 90 | '.py-safe': { 'padding-top': 'env(safe-area-inset-top)', 'padding-bottom': 'env(safe-area-inset-bottom)' }, 91 | '.px-safe': { 'padding-left': 'env(safe-area-inset-left)', 'padding-right': 'env(safe-area-inset-right)' }, 92 | '.pt-safe': { 'padding-top': 'env(safe-area-inset-top)' }, 93 | '.pr-safe': { 'padding-right': 'env(safe-area-inset-right)' }, 94 | '.pb-safe': { 'padding-bottom': 'env(safe-area-inset-bottom)'}, 95 | '.pl-safe': { 'padding-left': 'env(safe-area-inset-left)' }, 96 | }, { 97 | '.m-safe': { 'margin-top': 'env(safe-area-inset-top)', 'margin-bottom': 'env(safe-area-inset-bottom)', 'margin-left': 'env(safe-area-inset-left)', 'margin-right': 'env(safe-area-inset-right)' }, 98 | '.my-safe': { 'margin-top': 'env(safe-area-inset-top)', 'margin-bottom': 'env(safe-area-inset-bottom)' }, 99 | '.mx-safe': { 'margin-left': 'env(safe-area-inset-left)', 'margin-right': 'env(safe-area-inset-right)' }, 100 | '.mt-safe': { 'margin-top': 'env(safe-area-inset-top)' }, 101 | '.mr-safe': { 'margin-right': 'env(safe-area-inset-right)' }, 102 | '.mb-safe': { 'margin-bottom': 'env(safe-area-inset-bottom)'}, 103 | '.ml-safe': { 'margin-left': 'env(safe-area-inset-left)' }, 104 | }]; 105 | 106 | addUtilities(utilities); 107 | }) 108 | --------------------------------------------------------------------------------