@rolemodel/optics 2.5.1 → 2.6.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/.github/workflows/linting.yml +2 -0
- package/.prettierignore +3 -0
- package/.yarn/install-state.gz +0 -0
- package/CHANGELOG.md +41 -0
- package/README.md +16 -0
- package/build_token_json.js +2 -1
- package/dist/README.md +16 -0
- package/dist/css/core/tokens/base_color_tokens.css +74 -0
- package/dist/css/core/tokens/base_tokens.css +3 -345
- package/dist/css/core/tokens/index.css +2 -1
- package/dist/css/core/tokens/oklch/base_color_tokens.css +74 -0
- package/dist/css/core/tokens/oklch/index.css +3 -0
- package/dist/css/core/tokens/oklch/scale_color_tokens.css +1498 -0
- package/dist/css/core/tokens/shared_tokens.css +272 -0
- package/dist/css/optics+feather_icons.css +74 -73
- package/dist/css/optics+feather_icons.min.css +1 -1
- package/dist/css/optics+lucide_icons.css +74 -73
- package/dist/css/optics+lucide_icons.min.css +1 -1
- package/dist/css/optics+material_outlined_variable.css +74 -73
- package/dist/css/optics+material_outlined_variable.min.css +1 -1
- package/dist/css/optics+no_icons.css +74 -73
- package/dist/css/optics+no_icons.min.css +1 -1
- package/dist/css/optics+phosphor_icons.css +74 -73
- package/dist/css/optics+phosphor_icons.min.css +1 -1
- package/dist/css/optics+tabler_icons.css +74 -73
- package/dist/css/optics+tabler_icons.min.css +1 -1
- package/dist/css/optics-oklch+feather_icons.css +6566 -0
- package/dist/css/optics-oklch+feather_icons.min.css +1 -0
- package/dist/css/optics-oklch+lucide_icons.css +13598 -0
- package/dist/css/optics-oklch+lucide_icons.min.css +1 -0
- package/dist/css/optics-oklch+material_outlined_variable.css +5636 -0
- package/dist/css/optics-oklch+material_outlined_variable.min.css +1 -0
- package/dist/css/optics-oklch+no_icons.css +5627 -0
- package/dist/css/optics-oklch+no_icons.min.css +1 -0
- package/dist/css/optics-oklch+phosphor_icons.css +5671 -0
- package/dist/css/optics-oklch+phosphor_icons.min.css +1 -0
- package/dist/css/optics-oklch+tabler_icons.css +5638 -0
- package/dist/css/optics-oklch+tabler_icons.min.css +1 -0
- package/dist/css/optics-oklch.css +5640 -0
- package/dist/css/optics-oklch.min.css +1 -0
- package/dist/css/optics.css +74 -73
- package/dist/css/optics.min.css +1 -1
- package/dist/package.json +10 -6
- package/dist/tokens/tokens.json +16 -16
- package/dist/tools/check-contrast.cjs +47 -0
- package/dist/tools/contrast/builds/build.cjs +44 -0
- package/dist/tools/contrast/builds/hsl-build.cjs +11 -0
- package/dist/tools/contrast/builds/oklch-build.cjs +11 -0
- package/dist/tools/contrast/calc-expression.cjs +68 -0
- package/dist/tools/contrast/check-error.cjs +4 -0
- package/dist/tools/contrast/color-pair.cjs +35 -0
- package/dist/tools/contrast/colors/color.cjs +41 -0
- package/dist/tools/contrast/colors/function-color.cjs +46 -0
- package/dist/tools/contrast/colors/hex-color.cjs +19 -0
- package/dist/tools/contrast/colors/hsl-color.cjs +25 -0
- package/dist/tools/contrast/colors/oklch-color.cjs +36 -0
- package/dist/tools/contrast/colors/themed-color.cjs +37 -0
- package/dist/tools/contrast/contrast-check.cjs +37 -0
- package/dist/tools/contrast/contrast-reporter.cjs +44 -0
- package/dist/tools/contrast/contrast-result.cjs +15 -0
- package/dist/tools/contrast/css-syntax.cjs +23 -0
- package/dist/tools/contrast/stylesheet.cjs +174 -0
- package/dist/tools/contrast/theme.cjs +84 -0
- package/dist/tools/contrast/tool-options.cjs +50 -0
- package/package.json +10 -6
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
const fs = require('node:fs')
|
|
2
|
+
const path = require('node:path')
|
|
3
|
+
const CheckError = require('./check-error.cjs')
|
|
4
|
+
const { findClosingParen } = require('./css-syntax.cjs')
|
|
5
|
+
|
|
6
|
+
// All custom properties, not just --op-*, so theme vars like var(--brand-hue) resolve.
|
|
7
|
+
const DECLARATION = /(--[\w-]+)\s*:\s*([^;}]*)/g
|
|
8
|
+
const IMPORTANT = /\s*!\s*important$/i
|
|
9
|
+
// Blocks that change whether or how much a declaration applies, which the cascade here doesn't follow.
|
|
10
|
+
const SCOPED_RULE = /^@(media|supports|container|layer)\b/i
|
|
11
|
+
// Optics itself is already loaded, and a remote URL (https:, data:, or //host) is usually a font, not a theme.
|
|
12
|
+
const SKIPPED_IMPORT = /^(?:[a-z][a-z0-9+.-]*:|\/\/)|(^|\/)@rolemodel\/optics(\/|@|$)/i
|
|
13
|
+
|
|
14
|
+
// The text of one or more CSS files, with their imports and without comments. Selectors are ignored.
|
|
15
|
+
class Stylesheet {
|
|
16
|
+
#css
|
|
17
|
+
|
|
18
|
+
// Reads the files in order, as if they were one file.
|
|
19
|
+
static read(paths) {
|
|
20
|
+
const missing = paths.find((file) => !fs.existsSync(file))
|
|
21
|
+
if (missing) throw new CheckError(`File not found: ${missing}`)
|
|
22
|
+
return new Stylesheet(paths.map((file) => readWithImports(file, [path.resolve(file)])).join('\n'))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Comments are stripped first, so commented-out tokens can't sway build detection or the cascade.
|
|
26
|
+
constructor(css) {
|
|
27
|
+
this.#css = stripComments(css)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get isEmpty() {
|
|
31
|
+
return this.#css === ''
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
mentions(pattern) {
|
|
35
|
+
return pattern.test(this.#css)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Every custom property declaration, in source order, with duplicates. `rule` is the innermost
|
|
39
|
+
// @media, @supports, @container or @layer rule around it, if any.
|
|
40
|
+
*declarations() {
|
|
41
|
+
const scopedBlocks = this.#scopedBlocks()
|
|
42
|
+
for (const match of this.#css.matchAll(DECLARATION)) {
|
|
43
|
+
const [, name, rawValue] = match
|
|
44
|
+
const block = scopedBlocks.find(({ start, end }) => match.index > start && match.index < end)
|
|
45
|
+
const value = rawValue.trim()
|
|
46
|
+
yield { name, value: value.replace(IMPORTANT, ''), important: IMPORTANT.test(value), rule: block?.rule }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// The start and end of each scoped block, innermost first, with its rule, like "@media (prefers-color-scheme: dark)".
|
|
51
|
+
#scopedBlocks() {
|
|
52
|
+
const blocks = []
|
|
53
|
+
const open = []
|
|
54
|
+
let ruleStart = 0
|
|
55
|
+
for (let index = 0; index < this.#css.length; index++) {
|
|
56
|
+
const char = this.#css[index]
|
|
57
|
+
if (char === '{') {
|
|
58
|
+
const rule = this.#css.slice(ruleStart, index).trim()
|
|
59
|
+
open.push(SCOPED_RULE.test(rule) ? { rule, start: index } : undefined)
|
|
60
|
+
} else if (char === '}') {
|
|
61
|
+
const block = open.pop()
|
|
62
|
+
if (block) blocks.push({ ...block, end: index })
|
|
63
|
+
}
|
|
64
|
+
if (char === '{' || char === '}' || char === ';') ruleStart = index + 1
|
|
65
|
+
}
|
|
66
|
+
// A block left open runs to the end of the file, as in CSS.
|
|
67
|
+
for (const block of open) if (block) blocks.push({ ...block, end: this.#css.length })
|
|
68
|
+
return blocks
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Replaces each @import with the imported file, found relative to the importing file, as CSS does.
|
|
73
|
+
// An import that can't be read or found stops the check, because the theme would be checked without its tokens.
|
|
74
|
+
function readWithImports(file, chain) {
|
|
75
|
+
const css = stripComments(fs.readFileSync(file, 'utf8'))
|
|
76
|
+
const where = path.relative('.', file)
|
|
77
|
+
let output = ''
|
|
78
|
+
let index = 0
|
|
79
|
+
for (const { index: start } of css.matchAll(/@import\b/gi)) {
|
|
80
|
+
if (start < index) continue
|
|
81
|
+
const statement = parseImport(css, start)
|
|
82
|
+
if (!statement) throw new CheckError(`Can't read an @import in ${where}. Pass your compiled CSS instead.`)
|
|
83
|
+
output += css.slice(index, start) + importedCss(statement, file, where, chain)
|
|
84
|
+
index = statement.end
|
|
85
|
+
}
|
|
86
|
+
return output + css.slice(index)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function importedCss({ url, conditions }, file, where, chain) {
|
|
90
|
+
if (SKIPPED_IMPORT.test(url)) return ''
|
|
91
|
+
|
|
92
|
+
const imported = [url, `${url}.css`]
|
|
93
|
+
.map((candidate) => path.resolve(path.dirname(file), candidate))
|
|
94
|
+
.find((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isFile())
|
|
95
|
+
if (!imported)
|
|
96
|
+
throw new CheckError(`Can't find @import '${url}' in ${where}. Fix the path, or pass your compiled CSS instead.`)
|
|
97
|
+
// CSS skips an import that would loop back to a file already being read.
|
|
98
|
+
if (chain.includes(imported)) return ''
|
|
99
|
+
return withConditions(readWithImports(imported, [...chain, imported]), conditions)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Reads `@import "url" conditions;`, `@import 'url' ...;` or `@import url(...) ...;` starting at `start`.
|
|
103
|
+
// Quoted URLs can hold spaces and semicolons. Returns undefined when the statement can't be read.
|
|
104
|
+
function parseImport(css, start) {
|
|
105
|
+
let index = start + '@import'.length
|
|
106
|
+
const skipSpaces = () => {
|
|
107
|
+
while (/\s/.test(css[index] ?? '')) index++
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
skipSpaces()
|
|
111
|
+
const isUrlFunction = css.slice(index, index + 4).toLowerCase() === 'url('
|
|
112
|
+
if (isUrlFunction) {
|
|
113
|
+
index += 4
|
|
114
|
+
skipSpaces()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let url
|
|
118
|
+
const quote = css[index]
|
|
119
|
+
if (quote === '"' || quote === "'") {
|
|
120
|
+
const close = css.indexOf(quote, index + 1)
|
|
121
|
+
if (close === -1) return undefined
|
|
122
|
+
url = css.slice(index + 1, close)
|
|
123
|
+
index = close + 1
|
|
124
|
+
} else if (isUrlFunction) {
|
|
125
|
+
const close = css.indexOf(')', index)
|
|
126
|
+
if (close === -1) return undefined
|
|
127
|
+
url = css.slice(index, close).trim()
|
|
128
|
+
index = close
|
|
129
|
+
} else {
|
|
130
|
+
return undefined
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (isUrlFunction) {
|
|
134
|
+
skipSpaces()
|
|
135
|
+
if (css[index] !== ')') return undefined
|
|
136
|
+
index++
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// The last statement in a file may end without a semicolon.
|
|
140
|
+
const semicolon = css.indexOf(';', index)
|
|
141
|
+
const end = semicolon === -1 ? css.length : semicolon
|
|
142
|
+
return { url, conditions: css.slice(index, end).trim(), end: end + 1 }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Wraps imported CSS in the import's media, supports() and layer() parts, so its tokens count as scoped.
|
|
146
|
+
function withConditions(css, conditions) {
|
|
147
|
+
let rest = conditions
|
|
148
|
+
let layer
|
|
149
|
+
const layerMatch = rest.match(/^layer(?:\(([^)]*)\))?\s*/i)
|
|
150
|
+
if (layerMatch) {
|
|
151
|
+
layer = layerMatch[1]?.trim() ?? ''
|
|
152
|
+
rest = rest.slice(layerMatch[0].length)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let supports
|
|
156
|
+
if (/^supports\(/i.test(rest)) {
|
|
157
|
+
const end = findClosingParen(rest, 'supports'.length)
|
|
158
|
+
if (end !== -1) {
|
|
159
|
+
supports = rest.slice('supports('.length, end)
|
|
160
|
+
rest = rest.slice(end + 1).trim()
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let wrapped = css
|
|
165
|
+
if (layer !== undefined) wrapped = `@layer ${layer} {\n${wrapped}\n}`
|
|
166
|
+
if (supports !== undefined) wrapped = `@supports (${supports}) {\n${wrapped}\n}`
|
|
167
|
+
return rest ? `@media ${rest} {\n${wrapped}\n}` : wrapped
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function stripComments(css) {
|
|
171
|
+
return css.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = Stylesheet
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const CalcExpression = require('./calc-expression.cjs')
|
|
2
|
+
const CheckError = require('./check-error.cjs')
|
|
3
|
+
const { findClosingParen, splitAtFirstComma } = require('./css-syntax.cjs')
|
|
4
|
+
|
|
5
|
+
const VAR_NAME = /^--[\w-]+$/
|
|
6
|
+
|
|
7
|
+
// The user's theme on top of the Optics tokens: one final value per custom property, like getComputedStyle(:root).
|
|
8
|
+
class Theme {
|
|
9
|
+
#values = new Map()
|
|
10
|
+
// The @media, @supports, @container or @layer rule a property's final value was set inside, if any.
|
|
11
|
+
#rules = new Map()
|
|
12
|
+
|
|
13
|
+
// Applies the stylesheets in order. The last value wins, except a normal value can't replace an !important one, as in CSS.
|
|
14
|
+
static cascade(...stylesheets) {
|
|
15
|
+
const theme = new Theme()
|
|
16
|
+
const important = new Set()
|
|
17
|
+
for (const stylesheet of stylesheets) {
|
|
18
|
+
for (const { name, value, important: isImportant, rule } of stylesheet.declarations()) {
|
|
19
|
+
if (important.has(name) && !isImportant) continue
|
|
20
|
+
if (isImportant) important.add(name)
|
|
21
|
+
theme.#values.set(name, value)
|
|
22
|
+
if (rule) theme.#rules.set(name, rule)
|
|
23
|
+
else theme.#rules.delete(name)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return theme
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
names() {
|
|
30
|
+
return [...this.#values.keys()]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
has(name) {
|
|
34
|
+
return this.#values.has(name)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The token's value with every var() substituted and every plain-number calc() worked out.
|
|
38
|
+
resolve(name) {
|
|
39
|
+
this.#assertUnscoped(name, name)
|
|
40
|
+
return CalcExpression.resolveAll(this.#substituteVars(this.#values.get(name), [], name))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Walks each var() with balanced parentheses, so fallbacks can hold their own var(),
|
|
44
|
+
// and a fallback is only resolved when its variable is not defined.
|
|
45
|
+
#substituteVars(value, seen, token) {
|
|
46
|
+
let output = ''
|
|
47
|
+
let index = 0
|
|
48
|
+
for (let start; (start = value.indexOf('var(', index)) !== -1;) {
|
|
49
|
+
const end = findClosingParen(value, start + 'var'.length)
|
|
50
|
+
if (end === -1) throw new CheckError(`Can't resolve: ${value}`)
|
|
51
|
+
|
|
52
|
+
const [name, fallback] = splitAtFirstComma(value.slice(start + 'var('.length, end)).map((part) => part.trim())
|
|
53
|
+
output += value.slice(index, start) + this.#substituteVar(name, fallback, seen, value, token)
|
|
54
|
+
index = end + 1
|
|
55
|
+
}
|
|
56
|
+
return output + value.slice(index)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#substituteVar(name, fallback, seen, value, token) {
|
|
60
|
+
if (!VAR_NAME.test(name)) throw new CheckError(`Can't resolve: ${value}`)
|
|
61
|
+
if (seen.includes(name)) throw new CheckError(`Circular reference: ${[...seen, name].join(' -> ')}`)
|
|
62
|
+
if (this.#values.has(name)) {
|
|
63
|
+
this.#assertUnscoped(token, name)
|
|
64
|
+
return this.#substituteVars(this.#values.get(name), [...seen, name], token)
|
|
65
|
+
}
|
|
66
|
+
if (fallback !== undefined) return this.#substituteVars(fallback, seen, token)
|
|
67
|
+
throw new CheckError(`${name} is not defined`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// A value set inside @media, @supports or @container applies only under that condition, and one inside @layer
|
|
71
|
+
// loses to any unlayered value. The cascade here follows neither, so a color that depends on one could pass a
|
|
72
|
+
// theme that fails.
|
|
73
|
+
#assertUnscoped(token, name) {
|
|
74
|
+
const rule = this.#rules.get(name)
|
|
75
|
+
if (!rule) return
|
|
76
|
+
const reason = name === token ? 'it is set' : `it uses ${name}, which is set`
|
|
77
|
+
const advice = /^@layer/i.test(rule)
|
|
78
|
+
? 'Layers change which value wins, so move color tokens out of @layer.'
|
|
79
|
+
: 'Set mode colors with light-dark(), or check that theme in its own file.'
|
|
80
|
+
throw new CheckError(`Can't check ${token}: ${reason} inside ${rule}. ${advice}`)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = Theme
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const CheckError = require('./check-error.cjs')
|
|
2
|
+
const ThemedColor = require('./colors/themed-color.cjs')
|
|
3
|
+
|
|
4
|
+
const DEFAULT_MIN = 4.5
|
|
5
|
+
const FLAG = /^--([a-z-]+)(?:=(.*))?$/
|
|
6
|
+
|
|
7
|
+
// The command-line arguments: optics-check-contrast [files...] [--hsl | --oklch] [--mode=light | --mode=dark] [--min=4.5] [--alt-min=4.5] [--report]
|
|
8
|
+
class ToolOptions {
|
|
9
|
+
static parse(args) {
|
|
10
|
+
const flags = {}
|
|
11
|
+
const files = []
|
|
12
|
+
for (const arg of args) {
|
|
13
|
+
const flag = arg.match(FLAG)
|
|
14
|
+
if (flag) flags[flag[1]] = flag[2] ?? true
|
|
15
|
+
else files.push(arg)
|
|
16
|
+
}
|
|
17
|
+
return new ToolOptions(files, flags)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
constructor(files, flags) {
|
|
21
|
+
this.files = files
|
|
22
|
+
this.buildName = forcedBuildName(flags)
|
|
23
|
+
this.mode = mode(flags.mode)
|
|
24
|
+
this.min = threshold(flags.min)
|
|
25
|
+
this.altMin = threshold(flags['alt-min'])
|
|
26
|
+
this.report = Boolean(flags.report)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function forcedBuildName(flags) {
|
|
31
|
+
if (flags.hsl) return 'hsl'
|
|
32
|
+
if (flags.oklch) return 'oklch'
|
|
33
|
+
return undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function mode(value) {
|
|
37
|
+
if (value === undefined || ThemedColor.MODES.includes(value)) return value
|
|
38
|
+
throw new CheckError('--mode must be light or dark')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function threshold(value) {
|
|
42
|
+
if (value === undefined) return DEFAULT_MIN
|
|
43
|
+
// A bare --min (true) or empty --min= ('') would become 1 or 0 with Number(), so require a numeric string.
|
|
44
|
+
const number = typeof value === 'string' && value.trim() !== '' ? Number(value) : NaN
|
|
45
|
+
// A NaN threshold would make every ratio < min check false, so every pair would pass.
|
|
46
|
+
if (!Number.isFinite(number)) throw new CheckError('--min and --alt-min must be numbers, like 4.5')
|
|
47
|
+
return number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = ToolOptions
|
package/package.json
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rolemodel/optics",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"packageManager": "yarn@4.8.1",
|
|
5
5
|
"description": "Optics is a css package that provides base styles and components that can be integrated and customized in a variety of projects.",
|
|
6
6
|
"main": "dist/css/optics.css",
|
|
7
|
+
"bin": {
|
|
8
|
+
"optics-check-contrast": "dist/tools/check-contrast.cjs"
|
|
9
|
+
},
|
|
7
10
|
"scripts": {
|
|
8
|
-
"test": "
|
|
11
|
+
"test": "node --test 'test/**/*.test.cjs'",
|
|
9
12
|
"build": "yarn build:css && yarn build:css-min && yarn build:tokens && yarn build:files",
|
|
10
13
|
"build:css": "postcss 'src/optics*.css' -d dist/css",
|
|
11
14
|
"build:css-min": "postcss 'src/optics*.css' -d dist/css --ext .min.css --env=minify",
|
|
12
15
|
"build:tokens": "node build_token_json --source=src/core/tokens --output=dist/tokens/tokens.json",
|
|
13
|
-
"build:files": "mkdir -p dist/css/addons; cp LICENSE README.md package.json dist/; cp -rL src/addons src/core src/components dist/css",
|
|
16
|
+
"build:files": "mkdir -p dist/css/addons; cp LICENSE README.md package.json dist/; cp -rL src/addons src/core src/components dist/css; cp -r src/tools dist",
|
|
14
17
|
"build-docs": "yarn build-docs:llms && yarn build-docs:storybook",
|
|
15
18
|
"build-docs:llms": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON tools/build-llms-docs.mjs",
|
|
16
19
|
"build-docs:storybook": "storybook build --docs",
|
|
17
|
-
"storybook": "storybook dev -p 6006 --docs",
|
|
20
|
+
"storybook": "yarn build-docs:llms && storybook dev -p 6006 --docs",
|
|
18
21
|
"lint": "yarn lint:js && yarn lint:css",
|
|
19
22
|
"lint-fix": "yarn lint:js --fix && yarn lint:css --fix",
|
|
20
23
|
"lint:js": "eslint 'src/stories/**/*.js'",
|
|
21
24
|
"lint:css": "stylelint 'src/**/*.css'",
|
|
22
25
|
"prettier": "prettier -w .",
|
|
23
26
|
"prettier-check": "prettier -c .",
|
|
24
|
-
"sanity-check": "yarn lint && yarn prettier && yarn build && yarn build-docs && rm -rf ./dist && rm -rf ./storybook-static && rm -rf ./llms-docs",
|
|
25
|
-
"generate": "node ./tools/generate.js"
|
|
27
|
+
"sanity-check": "yarn lint && yarn prettier && yarn test && yarn build && yarn check-contrast && yarn build-docs && rm -rf ./dist && rm -rf ./storybook-static && rm -rf ./llms-docs",
|
|
28
|
+
"generate": "node ./tools/generate.js",
|
|
29
|
+
"check-contrast": "node dist/tools/check-contrast.cjs"
|
|
26
30
|
},
|
|
27
31
|
"repository": {
|
|
28
32
|
"type": "git",
|