tailwind-clamp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Nicolas Cusan
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.
package/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # Tailwind clamp
2
+
3
+ Tailwind CSS utilities & plugin to use CSS `clamp` in your project. Enabling fluid interfaces using Tailwind syntax.
4
+
5
+ The plugin is based on the formula presented in this [article](https://chriskirknielsen.com/blog/modern-fluid-typography-with-clamp/)
6
+
7
+ ## Features
8
+
9
+ - Clamp values between a min and max viewport width, making it grow / shrink with the viewport.
10
+ - Possibility to use small to large, large to small, negative to positive, positive to negative and negative to negative values. (Negative values only work on properties that allow them, e.g. `margin`)
11
+ - Helper functions to simplify the definition of clamped values in your config.
12
+ - Tailwind plugin to allow the usage of arbitrary values using the `clamp-[...]` syntax.
13
+ - Values are interpreted as pixels and output as `rem`
14
+
15
+ ## Installation
16
+
17
+ Install the plugin from npm:
18
+
19
+ ```sh
20
+ npm install nicolas-cusan/tailwind-clamp
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### Predefine values in your config
26
+
27
+ The package provides two helper functions to help you define "clamped" values in your config:
28
+
29
+ #### `clamp(start, end, [minViewportWidth=375, maxViewportWidth=1440])`
30
+
31
+ ##### Arguments
32
+
33
+ - `start` `{number}`: Value at `minViewportWidth` viewport size. The value is interpreted as pixels and outputted as `rem` in the generated CSS.
34
+ - `end` `{number}`: Value at `maxViewportWidth` viewport size. The value is interpreted as pixels and outputted as `rem` in the generated CSS.
35
+ - `[minViewportWidth=375]` `{number}`: Viewport size, where the clamp starts, defaults to `375`. The value is interpreted as pixels. Value should be smaller than `maxViewportWidth`.
36
+ - `[maxViewportWidth=1440]` `{number}`: Viewport size, where the clamp stops, defaults to `1440` The value is interpreted as pixels. Value should be smaller than `minViewportWidth`.
37
+
38
+ #### `clampFs(start, end, [tracking=null, minViewportWidth=375, maxViewportWidth=1440])`
39
+
40
+ ##### Arguments
41
+
42
+ - `start` `{[fontSize: number, lineHeight: number]}`: Array of two numbers: `font-size` and `line-height` respectively at `minViewportWidth` viewport size. Both values are interpreted as pixels and outputted as `rem` in the generated CSS.
43
+ - `end` `{[fontSize: number, lineHeight: number]}`: Array of two numbers: `font-size` and `line-height` respectively at `maxViewportWidth` viewport size. Both values are interpreted as pixels and outputted as `rem` in the generated CSS.
44
+ - `[tracking=null]` `{string|null}`: `letter-spacing` setting, it is recommended to use the `em` unit as it proportional to the font size, e.g. `-0.01em`
45
+ - `[minViewportWidth=375]` `{number}`: Viewport size, where the clamp starts, defaults to `375`. The value is interpreted as pixels. Value should be smaller than `maxViewportWidth`.
46
+ - `[maxViewportWidth=1440]` `{number}`: Viewport size, where the clamp stops, defaults to `1440` The value is interpreted as pixels. Value should be smaller than `minViewportWidth`.
47
+
48
+ ```js
49
+ // tailwind.config.js
50
+ const { setupClamp } = require('../src/utils.js');
51
+
52
+ const clampOptions = {
53
+ minViewportWidth: 375,
54
+ maxViewportWidth: 1440,
55
+ };
56
+
57
+ // Setup the clamp helper functions with the default min and max viewport sizes you want to use
58
+ const { clamp, clampFs } = setupClamp(options);
59
+
60
+ module.exports = {
61
+ theme: {
62
+ // ...
63
+ extend: {
64
+ spacing: {
65
+ // Use
66
+ grid: clamp(10, 20),
67
+ },
68
+
69
+ fontSize: {
70
+ base: clampFs([16, 20], [24, 28], '-0.01em'),
71
+ },
72
+ },
73
+ },
74
+ // ...
75
+ };
76
+ ```
77
+
78
+ ### Use the plugin with `clamp-[...]`
79
+
80
+ The package also provides a plugin to use arbitrary values via the `clamp-[...]` syntax.
81
+
82
+ ```js
83
+ // tailwind.config.js
84
+ const clampOptions = {
85
+ minViewportWidth: 375,
86
+ maxViewportWidth: 1440,
87
+ };
88
+
89
+ module.exports = {
90
+ theme: {
91
+ // ...
92
+ },
93
+ plugins: [
94
+ require('tailwind-clamp')(clampOptions),
95
+ // ...
96
+ ],
97
+ };
98
+ ```
99
+
100
+ #### Configuration
101
+
102
+ This plugin allows two configuration options:
103
+
104
+ | Name | Description | Default value |
105
+ | ------------------ | ------------------------------------ | ------------- |
106
+ | `minViewportWidth` | Viewport size where the clamp starts | `375` |
107
+ | `maxViewportWidth` | Viewport size where the clamp end | `1440` |
108
+
109
+ #### Using the plugin
110
+
111
+ The arbitrary values syntax for clamp requires at least three arguments separated by commas without whitespace:
112
+
113
+ #### `clamp-[<property>,<start>,<end>,[minViewportWidth,maxViewportWidth]]`
114
+
115
+ ##### Arguments
116
+
117
+ - `property` `{string}`: Property that the value should be applied to. See a list of all supported properties below.
118
+ - `start` `{number}`: Value at `minViewportWidth` viewport size. The value is interpreted as pixels and output as `rem` in the generated CSS.
119
+ - `end` `{number}`: Value at `maxViewportWidth` viewport size. The value is interpreted as pixels and output as `rem` in the generated CSS.
120
+ - `[minViewportWidth=375]` `{number}`: Viewport size, where the clamp starts, defaults to `375`. The value is interpreted as pixels. Value should be smaller than `maxViewportWidth`.
121
+ - `[maxViewportWidth=1440]` `{number}`: Viewport size, where the clamp stops, defaults to `1440` The value is interpreted as pixels. Value should be smaller than `minViewportWidth`.
122
+
123
+ ##### Example
124
+
125
+ ```html
126
+ <div class="clamp-[px,20,40] clamp-[py,10,18]">
127
+ Add some fluid padding here.
128
+ </div>
129
+ ```
130
+
131
+ ##### Supported properties
132
+
133
+ - `p` including `pt`, `pb`, `pl`, `pr`, `px`, `py`.
134
+ - `m` including `mt`, `mb`, `ml`, `mr`, `mx`, `my`.
135
+ - `inset`
136
+ - `top`
137
+ - `left`
138
+ - `right`
139
+ - `bottom`
140
+ - `text` applied to `font-size`.
141
+ - `gap` including `gap-x`, `gap-y`.
142
+ - `w`
143
+ - `h`
144
+ - `size`
145
+ - `min-w`
146
+ - `min-h`
147
+ - `max-w`
148
+ - `max-h`
149
+ - `rounded` including `rounded-t`, `rounded-r`, `rounded-b`, `rounded-l`, `rounded-tl`, `rounded-tr`, `rounded-bl`, `rounded-br`.
150
+ - `translate-x`
151
+ - `translate-y`
152
+ - `text-stroke`
153
+ - `stroke`
154
+ - `leading`
155
+ - `border` including `border-t`, `border-b`, `border-l`, `border-r`, `border-x`, `border-y`.
156
+ - `scroll-m`
157
+
158
+ ## Roadmap
159
+
160
+ - Support other units e.g `%`
161
+ - Support directional properties e.g. `ps`
162
+ - Add showcase
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "tailwind-clamp",
3
+ "version": "1.0.0",
4
+ "description": "Tailwind CSS plugin to use CSS clamp in your projects",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "dev": "next dev demo",
8
+ "build": "next build demo",
9
+ "start": "next start demo",
10
+ "lint": "next lint demo"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/nicolas-cusan/tailwind-clamp.git"
15
+ },
16
+ "keywords": [
17
+ "tailwind",
18
+ "css",
19
+ "clamp"
20
+ ],
21
+ "author": "Nicolas Cusan",
22
+ "license": "MIT",
23
+ "bugs": {
24
+ "url": "https://github.com/nicolas-cusan/tailwind-clamp/issues"
25
+ },
26
+ "homepage": "https://github.com/nicolas-cusan/tailwind-clamp#readme",
27
+ "dependencies": {
28
+ "react": "^18",
29
+ "react-dom": "^18",
30
+ "next": "14.1.4"
31
+ },
32
+ "devDependencies": {
33
+ "autoprefixer": "^10.0.1",
34
+ "postcss": "^8",
35
+ "tailwindcss": "^3.3.0"
36
+ }
37
+ }
package/src/index.js ADDED
@@ -0,0 +1,291 @@
1
+ const plugin = require('tailwindcss/plugin');
2
+ const { clamp } = require('./utils.js');
3
+
4
+ let cssTransformValue = [
5
+ 'translate(var(--tw-translate-x), var(--tw-translate-y))',
6
+ 'rotate(var(--tw-rotate))',
7
+ 'skewX(var(--tw-skew-x))',
8
+ 'skewY(var(--tw-skew-y))',
9
+ 'scaleX(var(--tw-scale-x))',
10
+ 'scaleY(var(--tw-scale-y))',
11
+ ].join(' ');
12
+
13
+ function resolveProperty(property, value) {
14
+ switch (property) {
15
+ case 'p':
16
+ return {
17
+ padding: value,
18
+ };
19
+ case 'pt':
20
+ return {
21
+ paddingTop: value,
22
+ };
23
+ case 'pb':
24
+ return {
25
+ paddingBottom: value,
26
+ };
27
+ case 'pl':
28
+ return {
29
+ paddingLeft: value,
30
+ };
31
+ case 'pr':
32
+ return {
33
+ paddingRight: value,
34
+ };
35
+ case 'px':
36
+ return {
37
+ paddingLeft: value,
38
+ paddingRight: value,
39
+ };
40
+ case 'py':
41
+ return {
42
+ paddingTop: value,
43
+ paddingBottom: value,
44
+ };
45
+ case 'm':
46
+ return {
47
+ margin: value,
48
+ };
49
+ case 'mt':
50
+ return {
51
+ marginTop: value,
52
+ };
53
+ case 'mb':
54
+ return {
55
+ marginBottom: value,
56
+ };
57
+ case 'ml':
58
+ return {
59
+ marginLeft: value,
60
+ };
61
+ case 'mr':
62
+ return {
63
+ marginRight: value,
64
+ };
65
+ case 'mx':
66
+ return {
67
+ marginLeft: value,
68
+ marginRight: value,
69
+ };
70
+ case 'my':
71
+ return {
72
+ marginTop: value,
73
+ marginBottom: value,
74
+ };
75
+ case 'inset':
76
+ return {
77
+ top: value,
78
+ left: value,
79
+ right: value,
80
+ bottom: value,
81
+ };
82
+ case 'top':
83
+ return {
84
+ top: value,
85
+ };
86
+ case 'left':
87
+ return {
88
+ left: value,
89
+ };
90
+ case 'right':
91
+ return {
92
+ right: value,
93
+ };
94
+ case 'bottom':
95
+ return {
96
+ bottom: value,
97
+ };
98
+ case 'text':
99
+ return {
100
+ fontSize: value,
101
+ };
102
+ case 'gap':
103
+ return {
104
+ gap: value,
105
+ };
106
+ case 'gap-x':
107
+ return {
108
+ columnGap: value,
109
+ };
110
+ case 'gap-y':
111
+ return {
112
+ rowGap: value,
113
+ };
114
+ case 'w':
115
+ return {
116
+ width: value,
117
+ };
118
+ case 'h':
119
+ return {
120
+ height: value,
121
+ };
122
+ case 'size':
123
+ return {
124
+ width: value,
125
+ height: value,
126
+ };
127
+ case 'min-w':
128
+ return {
129
+ minWidth: value,
130
+ };
131
+ case 'min-h':
132
+ return {
133
+ minHeight: value,
134
+ };
135
+ case 'max-w':
136
+ return {
137
+ maxWidth: value,
138
+ };
139
+ case 'max-h':
140
+ return {
141
+ maxHeight: value,
142
+ };
143
+ case 'rounded':
144
+ return {
145
+ borderRadius: value,
146
+ };
147
+ case 'rounded-t':
148
+ return {
149
+ borderTopLeftRadius: value,
150
+ borderTopRightRadius: value,
151
+ };
152
+ case 'rounded-r':
153
+ return {
154
+ borderTopRightRadius: value,
155
+ borderBottomRightRadius: value,
156
+ };
157
+ case 'rounded-b':
158
+ return {
159
+ borderBottomLeftRadius: value,
160
+ borderBottomRightRadius: value,
161
+ };
162
+ case 'rounded-l':
163
+ return {
164
+ borderTopLeftRadius: value,
165
+ borderBottomLeftRadius: value,
166
+ };
167
+ case 'rounded-tl':
168
+ return {
169
+ borderTopLeftRadius: value,
170
+ };
171
+ case 'rounded-tr':
172
+ return {
173
+ borderTopRightRadius: value,
174
+ };
175
+ case 'rounded-bl':
176
+ return {
177
+ borderBottomLeftRadius: value,
178
+ };
179
+ case 'rounded-br':
180
+ return {
181
+ borderBottomRightRadius: value,
182
+ };
183
+ case 'translate-x':
184
+ return {
185
+ '--tw-translate-x': value,
186
+ '@defaults transform': {},
187
+ transform: cssTransformValue,
188
+ };
189
+ case 'translate-y':
190
+ return {
191
+ '--tw-translate-y': value,
192
+ '@defaults transform': {},
193
+ transform: cssTransformValue,
194
+ };
195
+ case 'text-stroke':
196
+ return {
197
+ '-webkit-text-stroke': value,
198
+ textStroke: value,
199
+ };
200
+ case 'stroke':
201
+ return {
202
+ strokeWidth: value,
203
+ };
204
+ case 'leading':
205
+ return {
206
+ lineHeight: value,
207
+ };
208
+ case 'border':
209
+ return {
210
+ borderWidth: value,
211
+ };
212
+ case 'border-t':
213
+ return {
214
+ borderTopWidth: value,
215
+ };
216
+ case 'border-b':
217
+ return {
218
+ borderBottomWidth: value,
219
+ };
220
+ case 'border-l':
221
+ return {
222
+ borderLeftWidth: value,
223
+ };
224
+ case 'border-r':
225
+ return {
226
+ borderRightWidth: value,
227
+ };
228
+ case 'border-x':
229
+ return {
230
+ borderLeftWidth: value,
231
+ borderRightWidth: value,
232
+ };
233
+ case 'border-y':
234
+ return {
235
+ borderTopWidth: value,
236
+ borderBottomWidth: value,
237
+ };
238
+ case 'scroll-m':
239
+ return {
240
+ scrollMargin: value,
241
+ };
242
+ default:
243
+ return null;
244
+ }
245
+ }
246
+
247
+ module.exports = plugin.withOptions(function (
248
+ options = {
249
+ minViewportWidth: 375,
250
+ maxViewportWidth: 1440,
251
+ }
252
+ ) {
253
+ return function ({ matchUtilities, theme }) {
254
+ matchUtilities(
255
+ {
256
+ clamp: (value) => {
257
+ const props = value.split(',');
258
+ props[1] = parseFloat(props[1]);
259
+ props[2] = parseFloat(props[2]);
260
+
261
+ if (props.length < 3) {
262
+ throw new Error('The clamp utility requires at least 3 arguments.');
263
+ }
264
+
265
+ if (typeof props[1] !== 'number' || typeof props[2] !== 'number') {
266
+ throw new Error(
267
+ 'The clamp utility requires that the second and third arguments are numbers representing a pixel value.'
268
+ );
269
+ }
270
+
271
+ const prop = resolveProperty(
272
+ props[0],
273
+ clamp(
274
+ props[1],
275
+ props[2],
276
+ props[3] || options.minViewportWidth,
277
+ props[4] || options.maxViewportWidth
278
+ )
279
+ );
280
+
281
+ if (prop === null) {
282
+ throw new Error(`Property "${props[0]}" is not supported.`);
283
+ }
284
+
285
+ return prop;
286
+ },
287
+ },
288
+ { values: theme('clamp') }
289
+ );
290
+ };
291
+ });
package/src/utils.js ADDED
@@ -0,0 +1,82 @@
1
+ // https://chriskirknielsen.com/blog/modern-fluid-typography-with-clamp/
2
+
3
+ const clamp = (_start, _end, minvw = 375, maxvw = 1440) => {
4
+ let start = _start;
5
+ let end = _end;
6
+ let negative = false;
7
+
8
+ if (_end < _start && _start < 0 && _end < 0) {
9
+ start = Math.abs(_start);
10
+ end = Math.abs(_end);
11
+ negative = true;
12
+ } else if (_end < _start && _start > 0 && _end > 0) {
13
+ start = _start * -1;
14
+ end = _end * -1;
15
+ negative = true;
16
+ } else if (_end < _start) {
17
+ start = Math.abs(_start) * -1;
18
+ end = Math.abs(_end);
19
+ negative = true;
20
+ }
21
+
22
+ const rem = (px) => `${px / 16}rem`;
23
+ const factor = (1 / (maxvw - minvw)) * (end - start);
24
+ const calc = `${rem(start - minvw * factor)} + ${100 * factor}vw`;
25
+
26
+ const value = `clamp(${rem(start)}, ${calc}, ${rem(end)})`;
27
+
28
+ return negative ? `calc(${value} * -1)` : value;
29
+ };
30
+
31
+ const clampFs = (start, end, tracking = null, minvw = 375, maxvw = 1440) => {
32
+ const [startFs, startLh] = start;
33
+ const [endFs, endLh] = end;
34
+
35
+ const sameLh =
36
+ (startFs == startLh && endFs === endLh) ||
37
+ startLh / startFs === endLh / endFs;
38
+
39
+ const settings = [
40
+ clamp(startFs, endFs, minvw, maxvw),
41
+ {
42
+ lineHeight: sameLh
43
+ ? startLh / startFs
44
+ : clamp(startLh, endLh, minvw, maxvw),
45
+ },
46
+ ];
47
+
48
+ if (tracking) {
49
+ settings[1].letterSpacing = tracking;
50
+ }
51
+
52
+ return settings;
53
+ };
54
+
55
+ const setupClamp = (
56
+ options = {
57
+ minViewportWidth: 375,
58
+ maxViewportWidth: 1440,
59
+ }
60
+ ) => {
61
+ return {
62
+ clamp: (
63
+ start,
64
+ end,
65
+ minvw = options.minViewportWidth,
66
+ maxvw = options.maxViewportWidth
67
+ ) => clamp(start, end, minvw, maxvw),
68
+ clampFs: (
69
+ start,
70
+ end,
71
+ tracking = null,
72
+ minvw = options.minViewportWidth,
73
+ maxvw = options.maxViewportWidth
74
+ ) => clampFs(start, end, tracking, minvw, maxvw),
75
+ };
76
+ };
77
+
78
+ module.exports = {
79
+ clamp,
80
+ clampFs,
81
+ setupClamp,
82
+ };