@spark-ui/tailwind-plugins 2.0.0 → 2.1.1
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/CHANGELOG.md +14 -0
- package/animations/index.stories.mdx +3 -3
- package/index.js +2 -0
- package/index.stories.mdx +1 -0
- package/package.json +5 -2
- package/sizings/index.stories.mdx +4 -4
- package/spark-theme/constants.js +15 -0
- package/spark-theme/getCSSVariableDeclarations.js +53 -0
- package/spark-theme/getCSSVariableReferences.js +111 -0
- package/spark-theme/hexRgb.js +51 -0
- package/spark-theme/index.js +98 -0
- package/spark-theme/index.stories.mdx +98 -0
- package/spark-theme/utils.js +83 -0
package/CHANGELOG.md
CHANGED
@@ -3,6 +3,20 @@
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
5
5
|
|
6
|
+
## [2.1.1](https://github.com/adevinta/spark/compare/@spark-ui/tailwind-plugins@2.1.0...@spark-ui/tailwind-plugins@2.1.1) (2023-03-19)
|
7
|
+
|
8
|
+
**Note:** Version bump only for package @spark-ui/tailwind-plugins
|
9
|
+
|
10
|
+
# [2.1.0](https://github.com/adevinta/spark/compare/@spark-ui/tailwind-plugins@2.0.0...@spark-ui/tailwind-plugins@2.1.0) (2023-03-17)
|
11
|
+
|
12
|
+
### Bug Fixes
|
13
|
+
|
14
|
+
- add early return ([3cc0916](https://github.com/adevinta/spark/commit/3cc0916d4fe1aafd76814646b804be45b8870f34)), closes [#411](https://github.com/adevinta/spark/issues/411)
|
15
|
+
|
16
|
+
### Features
|
17
|
+
|
18
|
+
- **tailwind-plugins:** add spark theme plugin ([7009c51](https://github.com/adevinta/spark/commit/7009c513ab37b1118b89d7bada365156fb85f362)), closes [#411](https://github.com/adevinta/spark/issues/411)
|
19
|
+
|
6
20
|
# [2.0.0](https://github.com/adevinta/spark/compare/@spark-ui/tailwind-plugins@1.1.2...@spark-ui/tailwind-plugins@2.0.0) (2023-03-15)
|
7
21
|
|
8
22
|
### Features
|
@@ -6,7 +6,7 @@ import { Alert } from '@docs/helpers/Alert'
|
|
6
6
|
|
7
7
|
# Tailwind animation plugin
|
8
8
|
|
9
|
-
|
9
|
+
<StoryHeading label="Installation" as="h2" />
|
10
10
|
|
11
11
|
To use our Tailwind animation plugin, simply install the `@spark-ui/tailwind-plugins` package and add the animation plugin to your `tailwind.config.js` file
|
12
12
|
|
@@ -26,7 +26,7 @@ module.exports = {
|
|
26
26
|
|
27
27
|
```
|
28
28
|
|
29
|
-
|
29
|
+
<StoryHeading label="Usage" as="h2" />
|
30
30
|
|
31
31
|
This plugin provides the following **animation-related** declaration properties that are missing by default in **Tailwind**:
|
32
32
|
|
@@ -47,7 +47,7 @@ This plugin also includes four animation keyframes:
|
|
47
47
|
- **animate-slide-bottom**: `animate-slide-bottom`
|
48
48
|
- **animate-slide-left**: `animate-slide-left`
|
49
49
|
|
50
|
-
<StoryHeading label="Configuration" as="
|
50
|
+
<StoryHeading label="Configuration" as="h2" />
|
51
51
|
|
52
52
|
You can customize the **CSS class prefix** using the `prefixVariant` option.
|
53
53
|
|
package/index.js
CHANGED
package/index.stories.mdx
CHANGED
@@ -8,5 +8,6 @@ import { Alert } from '@docs/helpers/Alert'
|
|
8
8
|
|
9
9
|
Below is a list of our Tailwind plugins:
|
10
10
|
|
11
|
+
- [spark theme](?path=/docs/utils-tailwind-plugins-spark-theme--docs)
|
11
12
|
- [animations](?path=/docs/utils-tailwind-plugins-animations--docs)
|
12
13
|
- [sizings](?path=/docs/utils-tailwind-plugins-sizings--docs)
|
package/package.json
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
{
|
2
2
|
"name": "@spark-ui/tailwind-plugins",
|
3
|
-
"version": "2.
|
3
|
+
"version": "2.1.1",
|
4
4
|
"description": "Spark Tailwind plugins",
|
5
5
|
"publishConfig": {
|
6
6
|
"access": "public"
|
7
7
|
},
|
8
8
|
"main": "index.js",
|
9
|
+
"dependencies": {
|
10
|
+
"@spark-ui/theme-utils": "^2.9.1"
|
11
|
+
},
|
9
12
|
"peerDependencies": {
|
10
13
|
"tailwindcss": "4.0.0"
|
11
14
|
},
|
@@ -14,5 +17,5 @@
|
|
14
17
|
"url": "git@github.com:adevinta/spark.git",
|
15
18
|
"directory": "packages/utils/tailwind-plugins"
|
16
19
|
},
|
17
|
-
"gitHead": "
|
20
|
+
"gitHead": "d74864d0a900c8bab3d505cb796fb2cb51527a61"
|
18
21
|
}
|
@@ -6,7 +6,7 @@ import { Alert } from '@docs/helpers/Alert'
|
|
6
6
|
|
7
7
|
# Tailwind sizing plugin
|
8
8
|
|
9
|
-
|
9
|
+
<StoryHeading label="Installation" as="h2" />
|
10
10
|
|
11
11
|
To use our Tailwind sizing plugin, simply install the `@spark-ui/tailwind-plugins` package and add the animation plugin to your `tailwind.config.js` file
|
12
12
|
|
@@ -27,7 +27,7 @@ module.exports = {
|
|
27
27
|
|
28
28
|
```
|
29
29
|
|
30
|
-
|
30
|
+
<StoryHeading label="Usage" as="h2" />
|
31
31
|
|
32
32
|
This plugin predefines a sizing range with the following pixel values:
|
33
33
|
|
@@ -43,7 +43,7 @@ These values are converted into rem units based on the specified **HTML font siz
|
|
43
43
|
|
44
44
|
The plugin generates CSS variables for each size value, which can then be used for `width`, `height`, `max-width`, `max-height`, and `translate` properties.
|
45
45
|
|
46
|
-
<StoryHeading label="Example" as="
|
46
|
+
<StoryHeading label="Example" as="h2" />
|
47
47
|
|
48
48
|
To set the `width` of an element using the sizing plugin, use the `w-sz-{size}` class, where`{size}` is the desired pixel value from the predefined range:
|
49
49
|
|
@@ -53,7 +53,7 @@ To set the `width` of an element using the sizing plugin, use the `w-sz-{size}`
|
|
53
53
|
|
54
54
|
This will set the width of the element to **10px** (0.625rem, if `htmlFontSize` is 16) using the custom sizing scale provided by the plugin.
|
55
55
|
|
56
|
-
<StoryHeading label="Configuration" as="
|
56
|
+
<StoryHeading label="Configuration" as="h2" />
|
57
57
|
|
58
58
|
You can customize the **base html font size** using the `htmlFontSize` option.
|
59
59
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
const tailwindCategoryKeys = {
|
2
|
+
colors: 'colors',
|
3
|
+
fontSize: 'fontSize',
|
4
|
+
screens: 'screens',
|
5
|
+
}
|
6
|
+
|
7
|
+
const unassignedColors = {
|
8
|
+
inherit: 'inherit',
|
9
|
+
current: 'currentColor',
|
10
|
+
transparent: 'transparent',
|
11
|
+
}
|
12
|
+
|
13
|
+
const DEFAULT_KEY = 'DEFAULT'
|
14
|
+
|
15
|
+
module.exports = { tailwindCategoryKeys, unassignedColors, DEFAULT_KEY }
|
@@ -0,0 +1,53 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
2
|
+
const { DEFAULT_KEY } = require('./constants')
|
3
|
+
const { hexRgb } = require('./hexRgb')
|
4
|
+
|
5
|
+
const {
|
6
|
+
doubleHyphensRegex,
|
7
|
+
getRemEquivalentValue,
|
8
|
+
isHex,
|
9
|
+
isObject,
|
10
|
+
isStringOrNumber,
|
11
|
+
toKebabCase,
|
12
|
+
} = require('./utils')
|
13
|
+
|
14
|
+
function getCSSVariableDeclarations(_theme, htmlFontSize) {
|
15
|
+
const CSSVariableObj = {}
|
16
|
+
|
17
|
+
function traverse(theme, paths = []) {
|
18
|
+
Object.entries(theme).forEach(([key, value]) => {
|
19
|
+
if (isObject(value)) {
|
20
|
+
return traverse(value, paths.concat(key))
|
21
|
+
}
|
22
|
+
|
23
|
+
if (isStringOrNumber(value)) {
|
24
|
+
const getFormattedValue = () => {
|
25
|
+
if (isHex(value)) {
|
26
|
+
const { red, green, blue } = hexRgb(value)
|
27
|
+
|
28
|
+
return `${red} ${green} ${blue}`
|
29
|
+
}
|
30
|
+
|
31
|
+
if (/rem$/gi.test(value)) {
|
32
|
+
return getRemEquivalentValue(value, htmlFontSize)
|
33
|
+
}
|
34
|
+
|
35
|
+
return value
|
36
|
+
}
|
37
|
+
|
38
|
+
CSSVariableObj[
|
39
|
+
`--${[...paths, key === DEFAULT_KEY ? key.toLowerCase() : key]
|
40
|
+
.map(toKebabCase)
|
41
|
+
.join('-')
|
42
|
+
.replace(doubleHyphensRegex, '-')}`
|
43
|
+
] = getFormattedValue()
|
44
|
+
}
|
45
|
+
})
|
46
|
+
}
|
47
|
+
|
48
|
+
traverse(_theme)
|
49
|
+
|
50
|
+
return CSSVariableObj
|
51
|
+
}
|
52
|
+
|
53
|
+
module.exports = { getCSSVariableDeclarations }
|
@@ -0,0 +1,111 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
2
|
+
const { DEFAULT_KEY, tailwindCategoryKeys, unassignedColors } = require('./constants')
|
3
|
+
const {
|
4
|
+
doubleHyphensRegex,
|
5
|
+
hasNumber,
|
6
|
+
isAlphanumericWithLeadingLetter,
|
7
|
+
isCamelCase,
|
8
|
+
isHex,
|
9
|
+
isObject,
|
10
|
+
isStringOrNumber,
|
11
|
+
toKebabCase,
|
12
|
+
} = require('./utils')
|
13
|
+
|
14
|
+
function getCSSVariableReferences(_theme) {
|
15
|
+
const themeCpy = JSON.parse(JSON.stringify(_theme))
|
16
|
+
|
17
|
+
const { fontSize, colors, screens } = tailwindCategoryKeys
|
18
|
+
|
19
|
+
/* eslint-disable complexity */
|
20
|
+
function traverse(theme, paths = []) {
|
21
|
+
Object.entries(theme).forEach(([key, value]) => {
|
22
|
+
// 👀 see: https://tailwindcss.com/docs/font-size#providing-a-default-line-height
|
23
|
+
if (isObject(value) && !paths.length && key === fontSize) {
|
24
|
+
Object.keys(value).forEach(k => {
|
25
|
+
const prefix = toKebabCase(fontSize)
|
26
|
+
if (isStringOrNumber(value[k])) {
|
27
|
+
theme[key][k] = `var(--${prefix}-${k})`
|
28
|
+
|
29
|
+
return
|
30
|
+
}
|
31
|
+
|
32
|
+
const kebabedKey = isCamelCase(k) || hasNumber(k) ? toKebabCase(k) : k
|
33
|
+
|
34
|
+
if (kebabedKey !== k) {
|
35
|
+
const tmp = theme[key][k]
|
36
|
+
delete theme[key][k]
|
37
|
+
theme[key][kebabedKey] = tmp
|
38
|
+
}
|
39
|
+
|
40
|
+
theme[key][kebabedKey] = [
|
41
|
+
`var(--${prefix}-${kebabedKey}-font-size)`,
|
42
|
+
{
|
43
|
+
...(value[kebabedKey].lineHeight && {
|
44
|
+
lineHeight: `var(--${prefix}-${kebabedKey}-line-height)`,
|
45
|
+
}),
|
46
|
+
...(value[kebabedKey].letterSpacing && {
|
47
|
+
letterSpacing: `var(--${prefix}-${kebabedKey}-letter-spacing)`,
|
48
|
+
}),
|
49
|
+
...(value[kebabedKey].fontWeight && {
|
50
|
+
fontWeight: `var(--${prefix}-${kebabedKey}-font-weight)`,
|
51
|
+
}),
|
52
|
+
},
|
53
|
+
]
|
54
|
+
})
|
55
|
+
|
56
|
+
return
|
57
|
+
}
|
58
|
+
|
59
|
+
if (isObject(value)) {
|
60
|
+
Object.keys(value).forEach(k => {
|
61
|
+
if (k === DEFAULT_KEY) {
|
62
|
+
return
|
63
|
+
}
|
64
|
+
|
65
|
+
if (!isObject(value[k]) && !isCamelCase(k)) {
|
66
|
+
return
|
67
|
+
}
|
68
|
+
|
69
|
+
const tmp = value[k]
|
70
|
+
delete value[k]
|
71
|
+
value[toKebabCase(k)] = tmp
|
72
|
+
})
|
73
|
+
|
74
|
+
return traverse(value, paths.concat(key))
|
75
|
+
}
|
76
|
+
|
77
|
+
if (isStringOrNumber(value)) {
|
78
|
+
const rootPath = paths[0] || ''
|
79
|
+
const isScreenValue = rootPath.includes(screens)
|
80
|
+
const isColorValue = rootPath.includes(colors)
|
81
|
+
|
82
|
+
const formattedValue = (() => {
|
83
|
+
if (isColorValue && isHex(value)) {
|
84
|
+
return `rgb(var(--${paths.join('-')}-${key}) / <alpha-value>)`
|
85
|
+
}
|
86
|
+
if (isScreenValue) {
|
87
|
+
return String(value).toLowerCase()
|
88
|
+
}
|
89
|
+
|
90
|
+
return `var(--${paths.join('-')}-${key.toLowerCase()})`
|
91
|
+
})()
|
92
|
+
|
93
|
+
const formattedKey = isAlphanumericWithLeadingLetter(key) ? toKebabCase(key) : key
|
94
|
+
|
95
|
+
if (formattedKey !== key) {
|
96
|
+
delete theme[key]
|
97
|
+
}
|
98
|
+
|
99
|
+
theme[formattedKey] = isScreenValue
|
100
|
+
? formattedValue
|
101
|
+
: toKebabCase(formattedValue).replace(doubleHyphensRegex, '-')
|
102
|
+
}
|
103
|
+
})
|
104
|
+
}
|
105
|
+
|
106
|
+
traverse(themeCpy)
|
107
|
+
|
108
|
+
return { ...themeCpy, colors: { ...themeCpy.colors, ...unassignedColors } }
|
109
|
+
}
|
110
|
+
|
111
|
+
module.exports = { getCSSVariableReferences }
|
@@ -0,0 +1,51 @@
|
|
1
|
+
// see 👀: https://github.com/sindresorhus/hex-rgb/blob/main/index.js
|
2
|
+
|
3
|
+
const hexCharacters = 'a-f\\d'
|
4
|
+
const match3or4Hex = `#?[${hexCharacters}]{3}[${hexCharacters}]?`
|
5
|
+
const match6or8Hex = `#?[${hexCharacters}]{6}([${hexCharacters}]{2})?`
|
6
|
+
const nonHexChars = new RegExp(`[^#${hexCharacters}]`, 'gi')
|
7
|
+
const validHexSize = new RegExp(`^${match3or4Hex}$|^${match6or8Hex}$`, 'i')
|
8
|
+
|
9
|
+
/* eslint-disable complexity */
|
10
|
+
function hexRgb(hex, options = {}) {
|
11
|
+
if (typeof hex !== 'string' || nonHexChars.test(hex) || !validHexSize.test(hex)) {
|
12
|
+
throw new TypeError('Expected a valid hex string')
|
13
|
+
}
|
14
|
+
|
15
|
+
hex = hex.replace(/^#/, '')
|
16
|
+
let alphaFromHex = 1
|
17
|
+
|
18
|
+
if (hex.length === 8) {
|
19
|
+
alphaFromHex = Number.parseInt(hex.slice(6, 8), 16) / 255
|
20
|
+
hex = hex.slice(0, 6)
|
21
|
+
}
|
22
|
+
|
23
|
+
if (hex.length === 4) {
|
24
|
+
alphaFromHex = Number.parseInt(hex.slice(3, 4).repeat(2), 16) / 255
|
25
|
+
hex = hex.slice(0, 3)
|
26
|
+
}
|
27
|
+
|
28
|
+
if (hex.length === 3) {
|
29
|
+
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
|
30
|
+
}
|
31
|
+
|
32
|
+
const number = Number.parseInt(hex, 16)
|
33
|
+
const red = number >> 16
|
34
|
+
const green = (number >> 8) & 255
|
35
|
+
const blue = number & 255
|
36
|
+
const alpha = typeof options.alpha === 'number' ? options.alpha : alphaFromHex
|
37
|
+
|
38
|
+
if (options.format === 'array') {
|
39
|
+
return [red, green, blue, alpha]
|
40
|
+
}
|
41
|
+
|
42
|
+
if (options.format === 'css') {
|
43
|
+
const alphaString = alpha === 1 ? '' : ` / ${Number((alpha * 100).toFixed(2))}%`
|
44
|
+
|
45
|
+
return `rgb(${red} ${green} ${blue}${alphaString})`
|
46
|
+
}
|
47
|
+
|
48
|
+
return { red, green, blue, alpha }
|
49
|
+
}
|
50
|
+
|
51
|
+
module.exports = { hexRgb }
|
@@ -0,0 +1,98 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
2
|
+
const { getCSSVariableDeclarations } = require('./getCSSVariableDeclarations')
|
3
|
+
const { getCSSVariableReferences } = require('./getCSSVariableReferences')
|
4
|
+
const { retrieveArrayDifferences, getAllObjectKeys } = require('./utils')
|
5
|
+
|
6
|
+
const themeUtils = require('@spark-ui/theme-utils')
|
7
|
+
const plugin = require('tailwindcss/plugin')
|
8
|
+
|
9
|
+
const missingDefaultThemeErrorMsg =
|
10
|
+
'A default theme is required. Please ensure that the "themes" object passed to this plugin includes a "default" key containing your default theme.'
|
11
|
+
|
12
|
+
const additionalItemsErrorMsg = (themeLabel, keys) =>
|
13
|
+
`The following keys: ${JSON.stringify(
|
14
|
+
keys
|
15
|
+
)} do not adhere to our Spark Theme interface and should be removed from the ${themeLabel} theme`
|
16
|
+
|
17
|
+
const missingItemsErrorMsg = (themeLabel, keys) =>
|
18
|
+
`The following keys: ${JSON.stringify(
|
19
|
+
keys
|
20
|
+
)} are missing from the ${themeLabel} theme, but required to comply with our Spark Theme interface`
|
21
|
+
|
22
|
+
module.exports = plugin.withOptions(
|
23
|
+
/**
|
24
|
+
* @typedef {Object} Options
|
25
|
+
* @property {Object} options.themes - An object containing your themes where each key corresponds to a data-theme attribute value.
|
26
|
+
* @property {number} htmlFontSize The base font size of your app.
|
27
|
+
*/
|
28
|
+
|
29
|
+
/**
|
30
|
+
* @param {Object} options The options for the plugin.
|
31
|
+
* @param {Object} [options.themes={}] An object containing your themes where each key corresponds to a data-theme attribute value.
|
32
|
+
* @param {string} [options.htmlFontSize=16] The base font size to use to properly compute rem values.
|
33
|
+
* @returns {Function} The PostCSS plugin function.
|
34
|
+
*/
|
35
|
+
options =>
|
36
|
+
({ addBase }) => {
|
37
|
+
const opts = options || {
|
38
|
+
themes: {},
|
39
|
+
}
|
40
|
+
|
41
|
+
const { htmlFontSize = 16, themes } = opts
|
42
|
+
|
43
|
+
if (!themes.default) {
|
44
|
+
throw new Error(missingDefaultThemeErrorMsg)
|
45
|
+
}
|
46
|
+
|
47
|
+
const { missingItems, additionalItems } = retrieveArrayDifferences({
|
48
|
+
ref: getAllObjectKeys(themeUtils.defaultTheme),
|
49
|
+
comp: getAllObjectKeys(themes.default),
|
50
|
+
})
|
51
|
+
|
52
|
+
if (missingItems.length) {
|
53
|
+
throw new Error(missingItemsErrorMsg('default', missingItems))
|
54
|
+
}
|
55
|
+
if (additionalItems.length) {
|
56
|
+
throw new Error(additionalItemsErrorMsg('default', additionalItems))
|
57
|
+
}
|
58
|
+
|
59
|
+
addBase({
|
60
|
+
':root': getCSSVariableDeclarations(themes.default, htmlFontSize),
|
61
|
+
})
|
62
|
+
|
63
|
+
Object.entries(themes).forEach(([key, value]) => {
|
64
|
+
if (key === 'default') {
|
65
|
+
return
|
66
|
+
}
|
67
|
+
|
68
|
+
const { missingItems, additionalItems } = retrieveArrayDifferences({
|
69
|
+
ref: getAllObjectKeys(themeUtils.defaultTheme),
|
70
|
+
comp: getAllObjectKeys(value),
|
71
|
+
})
|
72
|
+
|
73
|
+
if (missingItems.length) {
|
74
|
+
throw new Error(missingItemsErrorMsg(key, missingItems))
|
75
|
+
}
|
76
|
+
if (additionalItems.length) {
|
77
|
+
throw new Error(additionalItemsErrorMsg(key, additionalItems))
|
78
|
+
}
|
79
|
+
|
80
|
+
addBase({
|
81
|
+
[`[data-theme="${key}"]`]: getCSSVariableDeclarations(value, htmlFontSize),
|
82
|
+
})
|
83
|
+
})
|
84
|
+
},
|
85
|
+
options => {
|
86
|
+
const opts = options || {
|
87
|
+
themes: {},
|
88
|
+
}
|
89
|
+
|
90
|
+
const { themes } = opts
|
91
|
+
|
92
|
+
if (!themes.default) {
|
93
|
+
throw new Error(missingDefaultThemeErrorMsg)
|
94
|
+
}
|
95
|
+
|
96
|
+
return { theme: getCSSVariableReferences(themes.default) }
|
97
|
+
}
|
98
|
+
)
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import { Meta } from '@storybook/blocks'
|
2
|
+
import { StoryHeading } from '@docs/helpers/StoryHeading'
|
3
|
+
import { Alert } from '@docs/helpers/Alert'
|
4
|
+
|
5
|
+
<Meta title="utils/Tailwind plugins/spark theme" />
|
6
|
+
|
7
|
+
# Tailwind spark theme plugin
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
To use our Tailwind spark theme plugin, simply install the `@spark-ui/tailwind-plugins` package and add the spark theme plugin to your `tailwind.config.js` file
|
12
|
+
|
13
|
+
```bash
|
14
|
+
$ npm i @spark-ui/tailwind-plugins
|
15
|
+
```
|
16
|
+
|
17
|
+
```js
|
18
|
+
// tailwind.config.js
|
19
|
+
|
20
|
+
const sparkPlugins = require('@spark-ui/tailwind-plugins')
|
21
|
+
|
22
|
+
module.exports = {
|
23
|
+
...,
|
24
|
+
plugins: [sparkPlugins.sparkTheme({
|
25
|
+
htmlFontSize: 16 // The base font size for your application (default: 16),
|
26
|
+
themes: {
|
27
|
+
default: yourDefaultTheme,
|
28
|
+
dark: yourDarkTheme,
|
29
|
+
foo: someOtherTheme,
|
30
|
+
...rest
|
31
|
+
} //
|
32
|
+
})],
|
33
|
+
}
|
34
|
+
|
35
|
+
```
|
36
|
+
|
37
|
+
<StoryHeading label="Usage" as="h2" />
|
38
|
+
|
39
|
+
The plugin performs two main tasks:
|
40
|
+
|
41
|
+
- first, it converts every value within the objects inside the themes object into **CSS variables**, which is helpful for managing **multiple themes**.
|
42
|
+
|
43
|
+
- Second, it provides Tailwind with an internal mapping to retrieve these CSS variables This is necessary because, by default, Tailwind does not use CSS variables, but rather direct values.
|
44
|
+
|
45
|
+
Once the plugin is configured, all Spark custom classes become available for use.
|
46
|
+
|
47
|
+
```tsx
|
48
|
+
<div className="bg-primary text-primary...">...</div>
|
49
|
+
```
|
50
|
+
|
51
|
+
<StoryHeading label="Configuration" as="h2" />
|
52
|
+
|
53
|
+
This plugin can be customized by providing a configuration object with two fields, `themes` and `htmlFontSize`.
|
54
|
+
|
55
|
+
**themes**:
|
56
|
+
|
57
|
+
An object containing your themes. Each key in the object will be extracted to a `[data-theme]` attribute, and the values will be converted to CSS custom properties inside the `css` file generated by Tailwing. **Note that this object requires a key with a value of default**, which specifies the default theme for your project.
|
58
|
+
|
59
|
+
For example, if your `themes` object looks like this:
|
60
|
+
|
61
|
+
```js
|
62
|
+
|
63
|
+
sparkPlugins.sparkTheme({
|
64
|
+
themes: {
|
65
|
+
default: yourDefaultTheme,
|
66
|
+
alternative: someAlternativeTheme,
|
67
|
+
},
|
68
|
+
}),
|
69
|
+
```
|
70
|
+
|
71
|
+
Tailwind will generate the following CSS:
|
72
|
+
|
73
|
+
```css
|
74
|
+
:root {
|
75
|
+
/* ...tokens from yourDefaultTheme */
|
76
|
+
}
|
77
|
+
|
78
|
+
[data-theme='alternative'] {
|
79
|
+
/* ...tokens from someAlternativeTheme */
|
80
|
+
}
|
81
|
+
```
|
82
|
+
|
83
|
+
You can populate the `themes` object with as many themes as desired, with the only requirement being that they conform to our `Theme` interface. The Theme interface can be accessed through the [**@spark-ui/theme-utils**](?path=/docs/utils-theme--docs#theme) package.
|
84
|
+
|
85
|
+
**htmlFontSize (optional)**:
|
86
|
+
|
87
|
+
Spark uses a base font size of **16 pixels** as a standard value for calculating the sizes of all other font-related properties defined in the theme. For example, our `spacing.lg` value is defined as **1rem**, which is equal to 16 pixels (based on the default base font size of 16 pixels).
|
88
|
+
|
89
|
+
However, if your application uses a different base font size, such as **10 pixels** for example, this can cause issues with the sizing of elements in the application. To account for this, you can set the `htmlFontSize` to the appropriate value for your application's base font size. This will adjust the `rem` based values in Spark to be relative to the new base font size.
|
90
|
+
|
91
|
+
This will ensure that the `rem`-based values are accurately calculated according to your application's HTML font size.
|
92
|
+
|
93
|
+
<Alert>
|
94
|
+
To better understand what happens when you adjust `htmlFontSize`, it's useful to know that we use CSS custom properties to define the values of our design tokens, such as `spacing.lg`.
|
95
|
+
|
96
|
+
By default, the value for this token is expressed as `--spacing-lg: 1rem;`. However, when you define a `htmlFontSize` of 10, for example, this value will be updated to `--spacing-lg: 1.6rem;`
|
97
|
+
|
98
|
+
</Alert>
|
@@ -0,0 +1,83 @@
|
|
1
|
+
function isObject(x) {
|
2
|
+
return !!x && x.constructor === Object
|
3
|
+
}
|
4
|
+
|
5
|
+
function isStringOrNumber(value) {
|
6
|
+
return typeof value === 'string' || typeof value === 'number'
|
7
|
+
}
|
8
|
+
|
9
|
+
function isHex(value) {
|
10
|
+
if (typeof value === 'number') {
|
11
|
+
return false
|
12
|
+
}
|
13
|
+
|
14
|
+
const regexp = /^#[0-9a-fA-F]+$/
|
15
|
+
|
16
|
+
return regexp.test(value)
|
17
|
+
}
|
18
|
+
|
19
|
+
function isAlphanumericWithLeadingLetter(v) {
|
20
|
+
return /^[a-zA-Z](?=.*\d)[a-zA-Z\d]*$/.test(v)
|
21
|
+
}
|
22
|
+
|
23
|
+
function isCamelCase(value) {
|
24
|
+
return /[A-Z]/.test(value.slice(1))
|
25
|
+
}
|
26
|
+
|
27
|
+
function hasNumber(value) {
|
28
|
+
return /\d/.test(value)
|
29
|
+
}
|
30
|
+
|
31
|
+
function getRemEquivalentValue(remValue, htmlFontSize) {
|
32
|
+
const defaultBrowserBase = 16
|
33
|
+
const pxValue = parseFloat(remValue) * defaultBrowserBase
|
34
|
+
|
35
|
+
return `${pxValue / htmlFontSize}rem`
|
36
|
+
}
|
37
|
+
|
38
|
+
function toKebabCase(v) {
|
39
|
+
return v.replace(/[A-Z]+(?=[a-z0-9])|\d+/g, match => '-' + match.toLowerCase())
|
40
|
+
}
|
41
|
+
|
42
|
+
const doubleHyphensRegex = /(?<!var\()--+/g
|
43
|
+
|
44
|
+
function getAllObjectKeys(obj, path = '') {
|
45
|
+
return Object.keys(obj).flatMap(key => {
|
46
|
+
const newPath = path ? `${path}.${key}` : key
|
47
|
+
if (isObject(obj[key])) {
|
48
|
+
return getAllObjectKeys(obj[key], newPath)
|
49
|
+
}
|
50
|
+
|
51
|
+
return newPath
|
52
|
+
})
|
53
|
+
}
|
54
|
+
|
55
|
+
function difference(as, bs) {
|
56
|
+
const set = new Set(bs)
|
57
|
+
|
58
|
+
return as.filter(a => !set.has(a))
|
59
|
+
}
|
60
|
+
|
61
|
+
function retrieveArrayDifferences({ ref, comp }) {
|
62
|
+
const additionalItems = difference(comp, ref)
|
63
|
+
const missingItems = difference(ref, comp)
|
64
|
+
|
65
|
+
return {
|
66
|
+
additionalItems,
|
67
|
+
missingItems,
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
module.exports = {
|
72
|
+
isObject,
|
73
|
+
isStringOrNumber,
|
74
|
+
isHex,
|
75
|
+
isAlphanumericWithLeadingLetter,
|
76
|
+
isCamelCase,
|
77
|
+
hasNumber,
|
78
|
+
getRemEquivalentValue,
|
79
|
+
toKebabCase,
|
80
|
+
doubleHyphensRegex,
|
81
|
+
getAllObjectKeys,
|
82
|
+
retrieveArrayDifferences,
|
83
|
+
}
|