@spark-ui/tailwind-plugins 1.1.2 → 2.1.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/CHANGELOG.md CHANGED
@@ -3,6 +3,27 @@
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.0](https://github.com/adevinta/spark/compare/@spark-ui/tailwind-plugins@2.0.0...@spark-ui/tailwind-plugins@2.1.0) (2023-03-17)
7
+
8
+ ### Bug Fixes
9
+
10
+ - add early return ([3cc0916](https://github.com/adevinta/spark/commit/3cc0916d4fe1aafd76814646b804be45b8870f34)), closes [#411](https://github.com/adevinta/spark/issues/411)
11
+
12
+ ### Features
13
+
14
+ - **tailwind-plugins:** add spark theme plugin ([7009c51](https://github.com/adevinta/spark/commit/7009c513ab37b1118b89d7bada365156fb85f362)), closes [#411](https://github.com/adevinta/spark/issues/411)
15
+
16
+ # [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)
17
+
18
+ ### Features
19
+
20
+ - **tailwind-plugins:** add sizings plugin ([25f76ac](https://github.com/adevinta/spark/commit/25f76ac8ccdece9db718e80eb7fd54a951b4fc22)), closes [#401](https://github.com/adevinta/spark/issues/401)
21
+ - use new Tailwind sizings plugin ([4cbb16a](https://github.com/adevinta/spark/commit/4cbb16a535ffc4c473c729337278dce0cfab3f51)), closes [#401](https://github.com/adevinta/spark/issues/401)
22
+
23
+ ### BREAKING CHANGES
24
+
25
+ - **tailwind-plugins:** Add sizings plugin
26
+
6
27
  ## [1.1.2](https://github.com/adevinta/spark/compare/@spark-ui/tailwind-plugins@1.1.1...@spark-ui/tailwind-plugins@1.1.2) (2023-03-13)
7
28
 
8
29
  **Note:** Version bump only for package @spark-ui/tailwind-plugins
package/README.md ADDED
@@ -0,0 +1 @@
1
+ The documentation for this package is located within our Storybook at https://sparkui.vercel.app/?path=/docs/utils-tailwind-plugins--docs.
@@ -6,7 +6,9 @@ import { Alert } from '@docs/helpers/Alert'
6
6
 
7
7
  # Tailwind animation plugin
8
8
 
9
- To use our Tailwind animation plugins, simply install the `@spark-ui/tailwind-plugins` package and add the animation plugin to your `tailwind.config.js` file
9
+ ## Installation
10
+
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
10
12
 
11
13
  ```bash
12
14
  $ npm i @spark-ui/tailwind-plugins
@@ -19,11 +21,13 @@ const sparkPlugins = require('@spark-ui/tailwind-plugins')
19
21
 
20
22
  module.exports = {
21
23
  ...,
22
- plugins: [sparkPlugins.animation()],
24
+ plugins: [sparkPlugins.animations()],
23
25
  }
24
26
 
25
27
  ```
26
28
 
29
+ ## Usage
30
+
27
31
  This plugin provides the following **animation-related** declaration properties that are missing by default in **Tailwind**:
28
32
 
29
33
  - **animation-fill-mode**: `sp-anime-fill-{value}` (where `value` can be `forwards`, `backwards`, or `none`)
@@ -43,7 +47,7 @@ This plugin also includes four animation keyframes:
43
47
  - **animate-slide-bottom**: `animate-slide-bottom`
44
48
  - **animate-slide-left**: `animate-slide-left`
45
49
 
46
- <StoryHeading label="plugin options" as="h3" />
50
+ <StoryHeading label="Configuration" as="h3" />
47
51
 
48
52
  You can customize the **CSS class prefix** using the `prefixVariant` option.
49
53
 
@@ -56,7 +60,7 @@ const sparkPlugins = require('@spark-ui/tailwind-plugins')
56
60
 
57
61
  module.exports = {
58
62
  ...,
59
- plugins: [sparkPlugins.animation({ prefixVariant: 'bar' })],
63
+ plugins: [sparkPlugins.animations({ prefixVariant: 'bar' })],
60
64
  }
61
65
 
62
66
  ```
package/index.js CHANGED
@@ -1,6 +1,10 @@
1
- /* eslint-disable-next-line @typescript-eslint/no-var-requires */
1
+ /* eslint-disable @typescript-eslint/no-var-requires */
2
2
  const animations = require('./animations')
3
+ const sizings = require('./sizings')
4
+ const sparkTheme = require('./spark-theme')
3
5
 
4
6
  module.exports = {
5
7
  animations,
8
+ sizings,
9
+ sparkTheme,
6
10
  }
@@ -0,0 +1,12 @@
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" />
6
+
7
+ # Tailwind plugins
8
+
9
+ Below is a list of our Tailwind plugins:
10
+
11
+ - [animations](?path=/docs/utils-tailwind-plugins-animations--docs)
12
+ - [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": "1.1.2",
3
+ "version": "2.1.0",
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.0"
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": "a1686f8fd79eee57d1515c6926d84c8e7c7f90a2"
20
+ "gitHead": "b02d8de6793827cb8783dcf1029c7345c7629974"
18
21
  }
@@ -0,0 +1,70 @@
1
+ /* eslint-disable-next-line @typescript-eslint/no-var-requires */
2
+ const plugin = require('tailwindcss/plugin')
3
+
4
+ const sizingRange = [
5
+ 0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48, 56, 64, 80, 96, 112, 128, 144,
6
+ 160, 176, 192, 208, 224, 240, 256, 288, 320, 384, 400, 416, 432, 448, 464, 480, 512, 544, 576,
7
+ 608, 640, 672, 704, 736, 768, 800, 832, 864,
8
+ ]
9
+
10
+ function pxToRem(pxValue, htmlFontSize) {
11
+ const remValue = pxValue / htmlFontSize
12
+
13
+ return `${remValue}rem`
14
+ }
15
+
16
+ function getCSSVariableDeclarations(htmlFontSize) {
17
+ const CSSVariableDeclarations = sizingRange.reduce((acc, size) => {
18
+ acc[`--sz-${size}`] = pxToRem(size, htmlFontSize)
19
+
20
+ return acc
21
+ }, {})
22
+
23
+ return CSSVariableDeclarations
24
+ }
25
+
26
+ function getCSSVariableReferences() {
27
+ const CSSVariableReferences = sizingRange.reduce((acc, size) => {
28
+ acc[`sz-${size}`] = `var(--sz-${size})`
29
+
30
+ return acc
31
+ }, {})
32
+
33
+ return CSSVariableReferences
34
+ }
35
+
36
+ module.exports = plugin.withOptions(
37
+ /**
38
+ * @typedef {Object} Options
39
+ * @property {number} htmlFontSize The base font size of your app.
40
+ */
41
+
42
+ /**
43
+ * @param {Object} options The options for the plugin.
44
+ * @param {string} [options.htmlFontSize=16] The base font size to use to properly compute rem values.
45
+ * @returns {Function} The PostCSS plugin function.
46
+ */
47
+ options =>
48
+ ({ addBase }) => {
49
+ const opts = options || {
50
+ htmlFontSize: 16,
51
+ }
52
+
53
+ const { htmlFontSize } = opts
54
+
55
+ addBase({
56
+ ':root': getCSSVariableDeclarations(htmlFontSize),
57
+ })
58
+ },
59
+ () => ({
60
+ theme: {
61
+ extend: {
62
+ width: getCSSVariableReferences(),
63
+ maxWidth: getCSSVariableReferences(),
64
+ height: getCSSVariableReferences(),
65
+ maxHeight: getCSSVariableReferences(),
66
+ translate: getCSSVariableReferences(),
67
+ },
68
+ },
69
+ })
70
+ )
@@ -0,0 +1,74 @@
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/sizings" />
6
+
7
+ # Tailwind sizing plugin
8
+
9
+ ## Installation
10
+
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
+
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.sizings({ htmlFontSize: 16 // The base font size for your application (default: 16)
25
+ })],
26
+ }
27
+
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ This plugin predefines a sizing range with the following pixel values:
33
+
34
+ ```ts
35
+ const sizingRange = [
36
+ 0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48, 56, 64, 80, 96, 112, 128, 144,
37
+ 160, 176, 192, 208, 224, 240, 256, 288, 320, 384, 400, 416, 432, 448, 464, 480, 512, 544, 576,
38
+ 608, 640, 672, 704, 736, 768, 800, 832, 864,
39
+ ]
40
+ ```
41
+
42
+ These values are converted into rem units based on the specified **HTML font size** (see [configuration](#configuration)).
43
+
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
+
46
+ <StoryHeading label="Example" as="h3" />
47
+
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
+
50
+ ```tsx
51
+ <div className="w-sz-10">...</div>
52
+ ```
53
+
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
+
56
+ <StoryHeading label="Configuration" as="h3" />
57
+
58
+ You can customize the **base html font size** using the `htmlFontSize` option.
59
+
60
+ By default, the base will be `16`, but you can change it like so:
61
+
62
+ ```js
63
+ // tailwind.config.js
64
+
65
+ const sparkPlugins = require('@spark-ui/tailwind-plugins')
66
+
67
+ module.exports = {
68
+ ...,
69
+ plugins: [sparkPlugins.sizings({ htmlFontSize: 10 })],
70
+ }
71
+
72
+ ```
73
+
74
+ This will ensure that the `rem`-based values are accurately calculated according to your application's HTML font size.
@@ -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,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
+ }
File without changes