@payfit/unity-themes 2.55.22 → 2.56.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/dist/esm/scripts/style-dictionary-config.d.ts +14 -0
- package/package.json +14 -5
- package/skills/unity-themes/SKILL.md +45 -0
- package/src/agent-references/manifest.json +16 -0
- package/src/agent-references/tokens-catalog.json +13891 -0
- package/dist/esm/scripts/agent-references/color-reference.d.ts +0 -37
- package/src/agent-references/README.mdx +0 -25
- package/src/agent-references/primitive-colors.mdx +0 -18
- package/src/agent-references/semantic-border-colors.mdx +0 -16
- package/src/agent-references/semantic-content-colors.mdx +0 -16
- package/src/agent-references/semantic-surface-colors.mdx +0 -16
- package/src/agent-references/semantic-utility-colors.mdx +0 -16
- package/src/components/unity-theme-provider.stories.tsx +0 -532
- package/src/components/unity-theme-provider.test.tsx +0 -150
- package/src/components/unity-theme-provider.tsx +0 -72
- package/src/index.ts +0 -15
- package/src/scripts/actions/append-animations.ts +0 -73
- package/src/scripts/actions/compose-multi-theme.ts +0 -59
- package/src/scripts/agent-references/color-reference.test.ts +0 -140
- package/src/scripts/agent-references/color-reference.ts +0 -348
- package/src/scripts/build.ts +0 -420
- package/src/scripts/file-headers/unity.ts +0 -31
- package/src/scripts/formats/processors/grid-processor.test.ts +0 -378
- package/src/scripts/formats/processors/grid-processor.ts +0 -64
- package/src/scripts/formats/processors/typography-processor.test.ts +0 -111
- package/src/scripts/formats/processors/typography-processor.ts +0 -48
- package/src/scripts/formats/unity-theme.test.ts +0 -329
- package/src/scripts/formats/unity-theme.ts +0 -54
- package/src/scripts/formats/utils.test.ts +0 -264
- package/src/scripts/formats/utils.ts +0 -15
- package/src/scripts/transforms/oklch.test.ts +0 -166
- package/src/scripts/transforms/oklch.ts +0 -19
- package/src/scripts/transforms/tailwind-animation-token.test.ts +0 -108
- package/src/scripts/transforms/tailwind-animation-token.ts +0 -51
- package/src/scripts/transforms/tailwind-color-token.test.ts +0 -304
- package/src/scripts/transforms/tailwind-color-token.ts +0 -17
- package/src/scripts/transforms/tailwind-grid-token.test.ts +0 -114
- package/src/scripts/transforms/tailwind-grid-token.ts +0 -27
- package/src/scripts/transforms/tailwind-spacing-token.test.ts +0 -315
- package/src/scripts/transforms/tailwind-spacing-token.ts +0 -24
- package/src/scripts/transforms/tailwind-text-token.test.ts +0 -328
- package/src/scripts/transforms/tailwind-text-token.ts +0 -30
- package/src/scripts/transforms/tailwind-typography-token.test.ts +0 -55
- package/src/scripts/transforms/tailwind-typography-token.ts +0 -20
- package/src/scripts/types.ts +0 -30
- package/src/scripts/utils/prefix-transform.test.ts +0 -137
- package/src/scripts/utils/prefix-transform.ts +0 -16
- package/src/utils/cn.ts +0 -109
- package/src/utils/merge-config.ts +0 -194
- package/src/utils/tailwind-merge.test.ts +0 -462
- package/src/utils/tailwind-merge.ts +0 -77
- package/src/utils/tailwind-variants.ts +0 -53
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext, useEffect, useState } from 'react'
|
|
2
|
-
|
|
3
|
-
import type { PropsWithChildren, RefObject } from 'react'
|
|
4
|
-
|
|
5
|
-
export type UnityTheme = 'legacy' | 'rebrand'
|
|
6
|
-
|
|
7
|
-
interface UnityThemeContextValue {
|
|
8
|
-
theme: UnityTheme
|
|
9
|
-
setTheme: (theme: UnityTheme) => void
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const UnityThemeContext = createContext<UnityThemeContextValue>({
|
|
13
|
-
// Render legacy by default for backwards compatibility
|
|
14
|
-
theme: 'legacy',
|
|
15
|
-
// Intentional noop. Will be replaced by a set dispatcher from React
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
17
|
-
setTheme: () => {},
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
export interface UnityThemeProviderProps {
|
|
21
|
-
/** Initial theme. Can be changed at runtime via `useUnityTheme().setTheme`. */
|
|
22
|
-
theme?: UnityTheme
|
|
23
|
-
/**
|
|
24
|
-
* Element on which `data-uy-theme` is set.
|
|
25
|
-
* - `undefined` (default): uses `document.documentElement` (`<html>`)
|
|
26
|
-
* - A React ref: uses the referenced element
|
|
27
|
-
* - A CSS selector string: uses `document.querySelector(selector)`
|
|
28
|
-
* @default document.documentElement
|
|
29
|
-
*/
|
|
30
|
-
target?: RefObject<HTMLElement | null> | string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function resolveTarget(
|
|
34
|
-
target: UnityThemeProviderProps['target'],
|
|
35
|
-
): HTMLElement | null {
|
|
36
|
-
if (target === undefined) return document.documentElement
|
|
37
|
-
if (typeof target === 'string') return document.querySelector(target)
|
|
38
|
-
return target.current
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function UnityThemeProvider({
|
|
42
|
-
theme: initialTheme = 'legacy',
|
|
43
|
-
target,
|
|
44
|
-
children,
|
|
45
|
-
}: PropsWithChildren<UnityThemeProviderProps>) {
|
|
46
|
-
const [theme, setTheme] = useState<UnityTheme>(initialTheme)
|
|
47
|
-
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
const el = resolveTarget(target)
|
|
50
|
-
if (!el) return
|
|
51
|
-
|
|
52
|
-
el.dataset.uyTheme = theme
|
|
53
|
-
|
|
54
|
-
return () => {
|
|
55
|
-
delete el.dataset.uyTheme
|
|
56
|
-
}
|
|
57
|
-
}, [theme, target])
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<UnityThemeContext.Provider value={{ theme, setTheme }}>
|
|
61
|
-
{children}
|
|
62
|
-
</UnityThemeContext.Provider>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Returns the current theme and a setter to change it at runtime.
|
|
68
|
-
* Returns `"legacy"` with a no-op setter when used outside a provider.
|
|
69
|
-
*/
|
|
70
|
-
export function useUnityTheme(): UnityThemeContextValue {
|
|
71
|
-
return useContext(UnityThemeContext)
|
|
72
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// Core utilities
|
|
2
|
-
export * from './utils/cn'
|
|
3
|
-
export * from './utils/tailwind-merge'
|
|
4
|
-
export * from './utils/tailwind-variants'
|
|
5
|
-
|
|
6
|
-
// Configuration (for advanced users)
|
|
7
|
-
export { twMergeConfig } from './utils/merge-config'
|
|
8
|
-
|
|
9
|
-
// Theme provider
|
|
10
|
-
export {
|
|
11
|
-
UnityThemeProvider,
|
|
12
|
-
useUnityTheme,
|
|
13
|
-
type UnityTheme,
|
|
14
|
-
type UnityThemeProviderProps,
|
|
15
|
-
} from './components/unity-theme-provider'
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
|
|
4
|
-
import type { Config, Dictionary, PlatformConfig } from 'style-dictionary/types'
|
|
5
|
-
|
|
6
|
-
export function appendAnimations(
|
|
7
|
-
dictionary: Dictionary,
|
|
8
|
-
config: PlatformConfig,
|
|
9
|
-
options: Config,
|
|
10
|
-
) {
|
|
11
|
-
if (!config.animationsPath) {
|
|
12
|
-
console.warn('No animationsPath specified in config')
|
|
13
|
-
return
|
|
14
|
-
}
|
|
15
|
-
if (!config.files || config.files.length === 0) {
|
|
16
|
-
console.warn('No output files specified in config')
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
if (!config.buildPath) {
|
|
20
|
-
console.warn('No buildPath specified in config')
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const animationsDir = config.animationsPath as string
|
|
25
|
-
const outputFile = config.files[0]?.destination ?? ''
|
|
26
|
-
const cssPath = path.join(config.buildPath, outputFile)
|
|
27
|
-
|
|
28
|
-
if (!fs.existsSync(animationsDir)) {
|
|
29
|
-
console.warn(`Animations directory not found: ${animationsDir}`)
|
|
30
|
-
return
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const animationFiles = fs
|
|
34
|
-
.readdirSync(animationsDir)
|
|
35
|
-
.filter(file => file.endsWith('.css'))
|
|
36
|
-
|
|
37
|
-
const animations = animationFiles
|
|
38
|
-
.map(file => fs.readFileSync(path.join(animationsDir, file), 'utf8'))
|
|
39
|
-
.join('\n\n')
|
|
40
|
-
|
|
41
|
-
const css = fs.readFileSync(cssPath, 'utf8')
|
|
42
|
-
const finalCSS = `${css}\n\n${animations}`
|
|
43
|
-
|
|
44
|
-
fs.writeFileSync(cssPath, finalCSS)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function removeAnimations(
|
|
48
|
-
dictionary: Dictionary,
|
|
49
|
-
config: PlatformConfig,
|
|
50
|
-
options: Config,
|
|
51
|
-
) {
|
|
52
|
-
if (!config.animationsPath) {
|
|
53
|
-
console.warn('No animationsPath specified in config')
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
if (!config.files || config.files.length === 0) {
|
|
57
|
-
console.warn('No output files specified in config')
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
if (!config.buildPath) {
|
|
61
|
-
console.warn('No buildPath specified in config')
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const outputFile = config.files[0]?.destination ?? ''
|
|
66
|
-
const cssPath = path.join(config.buildPath, outputFile)
|
|
67
|
-
|
|
68
|
-
// erase animations from the file
|
|
69
|
-
const css = fs.readFileSync(cssPath, 'utf8')
|
|
70
|
-
const finalCSS = css.replace(/@keyframes\s+\w+\s*\{[\s\S]*?\}/g, '')
|
|
71
|
-
|
|
72
|
-
fs.writeFileSync(cssPath, finalCSS)
|
|
73
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Composes the final multi-theme `unity.css` by concatenating the formatted
|
|
3
|
-
* outputs from three SD instances into a single file.
|
|
4
|
-
*/
|
|
5
|
-
import fs from 'fs'
|
|
6
|
-
import path from 'path'
|
|
7
|
-
|
|
8
|
-
import prettier from 'prettier'
|
|
9
|
-
|
|
10
|
-
export interface ComposeInput {
|
|
11
|
-
/** css/variables output for legacy (:root selector) */
|
|
12
|
-
legacyCss: string
|
|
13
|
-
/** css/variables output for rebrand ([data-uy-theme="rebrand"] selector) */
|
|
14
|
-
rebrandCss: string
|
|
15
|
-
/** css/unity-theme output (@theme block + grid/typography utilities) */
|
|
16
|
-
themeCss: string
|
|
17
|
-
/** File header + imports */
|
|
18
|
-
header: string
|
|
19
|
-
/** Custom variant declarations */
|
|
20
|
-
customVariants: string
|
|
21
|
-
/** Output path for the composed CSS */
|
|
22
|
-
outputPath: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function composeMultiThemeCss(input: ComposeInput): Promise<void> {
|
|
26
|
-
const {
|
|
27
|
-
legacyCss,
|
|
28
|
-
rebrandCss,
|
|
29
|
-
themeCss,
|
|
30
|
-
header,
|
|
31
|
-
customVariants,
|
|
32
|
-
outputPath,
|
|
33
|
-
} = input
|
|
34
|
-
|
|
35
|
-
const css = `${header}
|
|
36
|
-
|
|
37
|
-
@layer base {
|
|
38
|
-
${legacyCss}
|
|
39
|
-
|
|
40
|
-
${rebrandCss}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
${themeCss}
|
|
44
|
-
|
|
45
|
-
${customVariants}
|
|
46
|
-
`
|
|
47
|
-
|
|
48
|
-
console.log(' \x1b[2m→\x1b[0m Formatting with Prettier...')
|
|
49
|
-
const formatted = await prettier.format(css, {
|
|
50
|
-
parser: 'css',
|
|
51
|
-
printWidth: 120,
|
|
52
|
-
tabWidth: 2,
|
|
53
|
-
useTabs: false,
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
fs.mkdirSync(path.dirname(outputPath), { recursive: true })
|
|
57
|
-
fs.writeFileSync(outputPath, formatted)
|
|
58
|
-
console.log(` \x1b[2m→\x1b[0m Written to \x1b[33m${outputPath}\x1b[0m`)
|
|
59
|
-
}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
generatePrimitiveColorReferenceFiles,
|
|
5
|
-
generateSemanticColorReferenceFiles,
|
|
6
|
-
generateThemesAgentReferencesReadme,
|
|
7
|
-
} from './color-reference.js'
|
|
8
|
-
|
|
9
|
-
const createToken = (path: string[], value: string) => ({
|
|
10
|
-
name: `uy-${path.join('-').toLowerCase()}`,
|
|
11
|
-
path,
|
|
12
|
-
$value: value,
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
describe('color agent references', () => {
|
|
16
|
-
it('generates one compact primitive reference file for all palettes', () => {
|
|
17
|
-
const files = generatePrimitiveColorReferenceFiles([
|
|
18
|
-
createToken(['color', 'grayscale', 'L0'], '#000'),
|
|
19
|
-
createToken(['color', 'grayscale', 'L1'], '#005'),
|
|
20
|
-
createToken(['color', 'blue', 'L10'], '#001'),
|
|
21
|
-
createToken(['color', 'blue', 'L2'], '#002'),
|
|
22
|
-
createToken(['color', 'blue', 'L1'], '#003'),
|
|
23
|
-
createToken(['color', 'red', 'L1'], '#004'),
|
|
24
|
-
])
|
|
25
|
-
|
|
26
|
-
expect(files.map(file => file.filename)).toEqual(['primitive-colors.mdx'])
|
|
27
|
-
|
|
28
|
-
const primitiveReference = files[0]?.content
|
|
29
|
-
|
|
30
|
-
expect(
|
|
31
|
-
primitiveReference!.startsWith(
|
|
32
|
-
`import { Meta } from '@storybook/addon-docs/blocks'\n\n<Meta title="Agent References/Primitive Colors" />`,
|
|
33
|
-
),
|
|
34
|
-
).toBe(true)
|
|
35
|
-
expect(primitiveReference).toContain(
|
|
36
|
-
`{/* AUTO-GENERATED FILE. DO NOT EDIT.
|
|
37
|
-
Generated by unity-themes:pre-build.
|
|
38
|
-
Source: Unity Style Dictionary color tokens.
|
|
39
|
-
*/}`,
|
|
40
|
-
)
|
|
41
|
-
expect(primitiveReference).toContain('Color names: grayscale, blue, red')
|
|
42
|
-
expect(primitiveReference).toContain('Scale: l1, l2, l10')
|
|
43
|
-
expect(primitiveReference).toContain('Grayscale also has l0')
|
|
44
|
-
expect(primitiveReference!.indexOf('l1')).toBeLessThan(
|
|
45
|
-
primitiveReference!.indexOf('l2'),
|
|
46
|
-
)
|
|
47
|
-
expect(primitiveReference!.indexOf('l2')).toBeLessThan(
|
|
48
|
-
primitiveReference!.indexOf('l10'),
|
|
49
|
-
)
|
|
50
|
-
expect(primitiveReference).not.toContain('`--uy-color-blue-l1`')
|
|
51
|
-
expect(primitiveReference).not.toContain('Names: l1')
|
|
52
|
-
expect(primitiveReference).not.toContain('## Names')
|
|
53
|
-
expect(primitiveReference).not.toContain('- l1')
|
|
54
|
-
expect(primitiveReference).toContain('`uy:bg-<color>-<scale>`')
|
|
55
|
-
expect(primitiveReference).toContain('`uy:bg-blue-l')
|
|
56
|
-
expect(primitiveReference).not.toContain('Legacy value')
|
|
57
|
-
expect(primitiveReference).not.toContain('Rebrand value')
|
|
58
|
-
expect(primitiveReference).not.toContain('#001')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('generates one compact semantic reference file per semantic group', () => {
|
|
62
|
-
const files = generateSemanticColorReferenceFiles([
|
|
63
|
-
createToken(['color', 'surface', 'primary'], '#fff'),
|
|
64
|
-
createToken(['color', 'content', 'neutral'], '#111'),
|
|
65
|
-
createToken(['color', 'border', 'primary'], '#222'),
|
|
66
|
-
createToken(['color', 'canvas', 'default'], '#333'),
|
|
67
|
-
])
|
|
68
|
-
|
|
69
|
-
expect(files.map(file => file.filename)).toEqual([
|
|
70
|
-
'semantic-surface-colors.mdx',
|
|
71
|
-
'semantic-content-colors.mdx',
|
|
72
|
-
'semantic-border-colors.mdx',
|
|
73
|
-
'semantic-utility-colors.mdx',
|
|
74
|
-
])
|
|
75
|
-
|
|
76
|
-
const surfaceReference = files[0]!.content
|
|
77
|
-
|
|
78
|
-
expect(surfaceReference).toContain(
|
|
79
|
-
'<Meta title="Agent References/Semantic Colors/Surface" />',
|
|
80
|
-
)
|
|
81
|
-
expect(surfaceReference).toContain('Names: primary')
|
|
82
|
-
expect(surfaceReference).not.toContain('## Names')
|
|
83
|
-
expect(surfaceReference).not.toContain('- primary')
|
|
84
|
-
expect(surfaceReference).not.toContain('`--uy-color-surface-primary`')
|
|
85
|
-
expect(surfaceReference).toContain('`uy:bg-surface-{name}`')
|
|
86
|
-
expect(surfaceReference).toContain('using surface tokens for text')
|
|
87
|
-
expect(surfaceReference).not.toContain('`uy:text-surface-{name}`')
|
|
88
|
-
expect(surfaceReference).not.toContain('CSS properties')
|
|
89
|
-
expect(surfaceReference).not.toContain(
|
|
90
|
-
'background-color: var(--uy-color-surface-primary);',
|
|
91
|
-
)
|
|
92
|
-
expect(surfaceReference).not.toContain('#fff')
|
|
93
|
-
|
|
94
|
-
const contentReference = files[1]!.content
|
|
95
|
-
expect(contentReference).toContain('`uy:text-content-{name}`')
|
|
96
|
-
expect(contentReference).toContain('`uy:decoration-content-{name}`')
|
|
97
|
-
expect(contentReference).not.toContain('`uy:bg-content-{name}`')
|
|
98
|
-
|
|
99
|
-
const borderReference = files[2]!.content
|
|
100
|
-
expect(borderReference).toContain('`uy:border-border-{name}`')
|
|
101
|
-
expect(borderReference).toContain('`uy:outline-border-{name}`')
|
|
102
|
-
expect(borderReference).not.toContain('`uy:bg-border-{name}`')
|
|
103
|
-
|
|
104
|
-
const utilityReference = files[3]!.content
|
|
105
|
-
expect(utilityReference).toContain('Use utility and canvas tokens only')
|
|
106
|
-
expect(utilityReference).toContain('`uy:ring-utility-focus-ring`')
|
|
107
|
-
expect(utilityReference).not.toContain('`uy:bg-utility-{name}`')
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('links generated files from the README', () => {
|
|
111
|
-
const primitiveFiles = generatePrimitiveColorReferenceFiles([
|
|
112
|
-
createToken(['color', 'blue', 'L1'], '#001'),
|
|
113
|
-
])
|
|
114
|
-
const semanticFiles = generateSemanticColorReferenceFiles([
|
|
115
|
-
createToken(['color', 'surface', 'primary'], '#fff'),
|
|
116
|
-
])
|
|
117
|
-
|
|
118
|
-
const readme = generateThemesAgentReferencesReadme({
|
|
119
|
-
primitiveFiles,
|
|
120
|
-
semanticFiles,
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
expect(
|
|
124
|
-
readme.startsWith(
|
|
125
|
-
`import { Meta } from '@storybook/addon-docs/blocks'\n\n<Meta title="Agent References/Unity Themes" />`,
|
|
126
|
-
),
|
|
127
|
-
).toBe(true)
|
|
128
|
-
expect(readme).toContain(
|
|
129
|
-
`{/* AUTO-GENERATED FILE. DO NOT EDIT.
|
|
130
|
-
Generated by unity-themes:pre-build.
|
|
131
|
-
Source: Unity Style Dictionary color tokens.
|
|
132
|
-
*/}`,
|
|
133
|
-
)
|
|
134
|
-
expect(readme).toContain(
|
|
135
|
-
'[semantic-surface-colors](./semantic-surface-colors.mdx)',
|
|
136
|
-
)
|
|
137
|
-
expect(readme).toContain('[primitive-colors](./primitive-colors.mdx)')
|
|
138
|
-
expect(readme).not.toContain('primitive-blue-colors')
|
|
139
|
-
})
|
|
140
|
-
})
|
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
|
|
4
|
-
type ColorToken = {
|
|
5
|
-
name: string
|
|
6
|
-
value?: string
|
|
7
|
-
$value?: string
|
|
8
|
-
path: string[]
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
type ReferenceToken = {
|
|
12
|
-
name: string
|
|
13
|
-
group: string
|
|
14
|
-
variable: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type ReferenceFile = {
|
|
18
|
-
filename: string
|
|
19
|
-
content: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
type UtilitySection = {
|
|
23
|
-
title: string
|
|
24
|
-
patterns: string[]
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const PRIMITIVE_GROUPS = [
|
|
28
|
-
'grayscale',
|
|
29
|
-
'blue',
|
|
30
|
-
'red',
|
|
31
|
-
'green',
|
|
32
|
-
'orange',
|
|
33
|
-
'cyan',
|
|
34
|
-
'purple',
|
|
35
|
-
'plum',
|
|
36
|
-
'peach',
|
|
37
|
-
'sunglow',
|
|
38
|
-
'teal',
|
|
39
|
-
'yellow',
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
const SEMANTIC_GROUPS = [
|
|
43
|
-
{
|
|
44
|
-
title: 'Surface',
|
|
45
|
-
slug: 'surface',
|
|
46
|
-
groups: ['surface'],
|
|
47
|
-
usageGuidance:
|
|
48
|
-
'Use surface tokens for backgrounds and filled UI surfaces. Other Tailwind color utilities may exist for these tokens, but using surface tokens for text, borders, rings, or outlines is discouraged.',
|
|
49
|
-
utilitySections: [
|
|
50
|
-
{
|
|
51
|
-
title: 'Recommended',
|
|
52
|
-
patterns: ['uy:bg-surface-{name}'],
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
title: 'Content',
|
|
58
|
-
slug: 'content',
|
|
59
|
-
groups: ['content'],
|
|
60
|
-
usageGuidance:
|
|
61
|
-
'Use content tokens for text, icon color, and text decoration. Other Tailwind color utilities may exist for these tokens, but using content tokens for backgrounds, borders, rings, or outlines is discouraged.',
|
|
62
|
-
utilitySections: [
|
|
63
|
-
{
|
|
64
|
-
title: 'Recommended',
|
|
65
|
-
patterns: ['uy:text-content-{name}', 'uy:decoration-content-{name}'],
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
title: 'Border',
|
|
71
|
-
slug: 'border',
|
|
72
|
-
groups: ['border'],
|
|
73
|
-
usageGuidance:
|
|
74
|
-
'Use border tokens for borders and outlines. Other Tailwind color utilities may exist for these tokens, but using border tokens for backgrounds, text, gradients, or rings is discouraged.',
|
|
75
|
-
utilitySections: [
|
|
76
|
-
{
|
|
77
|
-
title: 'Recommended',
|
|
78
|
-
patterns: ['uy:border-border-{name}', 'uy:outline-border-{name}'],
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
title: 'Utility & Canvas',
|
|
84
|
-
slug: 'utility',
|
|
85
|
-
groups: ['utility', 'canvas'],
|
|
86
|
-
usageGuidance:
|
|
87
|
-
'Use utility and canvas tokens only for their named UI purpose. These tokens are intentionally narrow and should not be treated as a general color palette.',
|
|
88
|
-
utilitySections: [
|
|
89
|
-
{
|
|
90
|
-
title: 'Token-specific',
|
|
91
|
-
patterns: [
|
|
92
|
-
'uy:bg-canvas-{name}',
|
|
93
|
-
'uy:ring-utility-focus-ring',
|
|
94
|
-
'uy:ring-utility-inverted-focus-ring',
|
|
95
|
-
'uy:bg-utility-backdrop',
|
|
96
|
-
'shadow-\\* tokens are not UI colors',
|
|
97
|
-
],
|
|
98
|
-
},
|
|
99
|
-
],
|
|
100
|
-
},
|
|
101
|
-
]
|
|
102
|
-
|
|
103
|
-
const SEMANTIC_GROUP_NAMES = SEMANTIC_GROUPS.flatMap(group => group.groups)
|
|
104
|
-
|
|
105
|
-
const generatedNotice = (source: string) =>
|
|
106
|
-
`{/* AUTO-GENERATED FILE. DO NOT EDIT.
|
|
107
|
-
Generated by unity-themes:pre-build.
|
|
108
|
-
Source: ${source}.
|
|
109
|
-
*/}`
|
|
110
|
-
|
|
111
|
-
const storybookMeta = (title: string) =>
|
|
112
|
-
`import { Meta } from '@storybook/addon-docs/blocks'\n\n<Meta title="Agent References/${title}" />`
|
|
113
|
-
|
|
114
|
-
const parseReferenceToken = (token: ColorToken): ReferenceToken | null => {
|
|
115
|
-
const normalizedVariableName = token.name
|
|
116
|
-
.replace(/^uy-color-/, '')
|
|
117
|
-
.toLowerCase()
|
|
118
|
-
const [group, ...nameParts] = normalizedVariableName.split('-')
|
|
119
|
-
const name = nameParts.join('-')
|
|
120
|
-
|
|
121
|
-
if (!group || !name) {
|
|
122
|
-
return null
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
name,
|
|
127
|
-
group,
|
|
128
|
-
variable: `--uy-color-${normalizedVariableName}`,
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const getReferenceTokens = (tokens: ColorToken[]) =>
|
|
133
|
-
tokens
|
|
134
|
-
.filter(token => token.path[0] === 'color')
|
|
135
|
-
.map(parseReferenceToken)
|
|
136
|
-
.filter((token): token is ReferenceToken => token !== null)
|
|
137
|
-
|
|
138
|
-
const naturalLevelOrder = (name: string) => {
|
|
139
|
-
const levelMatch = /^l(\d+)$/i.exec(name)
|
|
140
|
-
return levelMatch ? Number(levelMatch[1]) : Number.POSITIVE_INFINITY
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const sortByPrimitiveLevel = (a: ReferenceToken, b: ReferenceToken) => {
|
|
144
|
-
const aLevel = naturalLevelOrder(a.name)
|
|
145
|
-
const bLevel = naturalLevelOrder(b.name)
|
|
146
|
-
|
|
147
|
-
if (aLevel !== bLevel) {
|
|
148
|
-
return aLevel - bLevel
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return a.name.localeCompare(b.name)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const sortByName = (a: ReferenceToken, b: ReferenceToken) =>
|
|
155
|
-
a.name.localeCompare(b.name)
|
|
156
|
-
|
|
157
|
-
const renderNames = (tokens: ReferenceToken[]) =>
|
|
158
|
-
`Names: ${tokens.map(token => token.name).join(', ')}`
|
|
159
|
-
|
|
160
|
-
const renderRecommendedUsageSections = (sections: UtilitySection[]) =>
|
|
161
|
-
sections.flatMap(section => [
|
|
162
|
-
`${section.title}: ${section.patterns
|
|
163
|
-
.map(pattern => (pattern.startsWith('uy:') ? `\`${pattern}\`` : pattern))
|
|
164
|
-
.join(', ')}`,
|
|
165
|
-
])
|
|
166
|
-
|
|
167
|
-
export const generateColorGroupReference = ({
|
|
168
|
-
title,
|
|
169
|
-
storybookTitle,
|
|
170
|
-
description,
|
|
171
|
-
tokens,
|
|
172
|
-
utilitySections,
|
|
173
|
-
}: {
|
|
174
|
-
title: string
|
|
175
|
-
storybookTitle: string
|
|
176
|
-
description: string
|
|
177
|
-
tokens: ReferenceToken[]
|
|
178
|
-
utilitySections?: UtilitySection[]
|
|
179
|
-
}) => {
|
|
180
|
-
const lines = [
|
|
181
|
-
storybookMeta(storybookTitle),
|
|
182
|
-
generatedNotice('Unity Style Dictionary color tokens'),
|
|
183
|
-
`# ${title}`,
|
|
184
|
-
description,
|
|
185
|
-
renderNames(tokens),
|
|
186
|
-
...(utilitySections ? renderRecommendedUsageSections(utilitySections) : []),
|
|
187
|
-
]
|
|
188
|
-
|
|
189
|
-
return `${lines.join('\n\n').trimEnd()}\n`
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const sortPrimitiveGroups = (groups: string[]) =>
|
|
193
|
-
groups.slice().sort((a, b) => {
|
|
194
|
-
const aIndex = PRIMITIVE_GROUPS.indexOf(a)
|
|
195
|
-
const bIndex = PRIMITIVE_GROUPS.indexOf(b)
|
|
196
|
-
|
|
197
|
-
return aIndex - bIndex
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
const uniqueSortedPrimitiveLevels = (tokens: ReferenceToken[]) =>
|
|
201
|
-
[...new Set(tokens.map(token => token.name))].sort((a, b) =>
|
|
202
|
-
sortByPrimitiveLevel(
|
|
203
|
-
{ name: a, group: '', variable: '' },
|
|
204
|
-
{ name: b, group: '', variable: '' },
|
|
205
|
-
),
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
const generatePrimitiveColorsReference = (tokens: ReferenceToken[]) => {
|
|
209
|
-
const colors = sortPrimitiveGroups([
|
|
210
|
-
...new Set(tokens.map(token => token.group)),
|
|
211
|
-
])
|
|
212
|
-
const nonGrayscaleLevels = uniqueSortedPrimitiveLevels(
|
|
213
|
-
tokens.filter(token => token.group !== 'grayscale'),
|
|
214
|
-
)
|
|
215
|
-
const grayscaleLevels = uniqueSortedPrimitiveLevels(
|
|
216
|
-
tokens.filter(token => token.group === 'grayscale'),
|
|
217
|
-
)
|
|
218
|
-
const grayscaleExtraLevels = grayscaleLevels.filter(
|
|
219
|
-
level => !nonGrayscaleLevels.includes(level),
|
|
220
|
-
)
|
|
221
|
-
const exampleScale = nonGrayscaleLevels.includes('l6')
|
|
222
|
-
? 'l6'
|
|
223
|
-
: nonGrayscaleLevels[0]
|
|
224
|
-
const grayscaleExampleScale = grayscaleExtraLevels[0] ?? grayscaleLevels[0]
|
|
225
|
-
|
|
226
|
-
const lines = [
|
|
227
|
-
storybookMeta('Primitive Colors'),
|
|
228
|
-
generatedNotice('Unity Style Dictionary color tokens'),
|
|
229
|
-
'# Primitive Colors',
|
|
230
|
-
'Prefer semantic color tokens for product UI. Use primitive colors only when no semantic token fits the use case.',
|
|
231
|
-
`Color names: ${colors.join(', ')}`,
|
|
232
|
-
`Scale: ${nonGrayscaleLevels.join(', ')}${
|
|
233
|
-
grayscaleExtraLevels.length > 0
|
|
234
|
-
? `. Grayscale also has ${grayscaleExtraLevels.join(', ')}`
|
|
235
|
-
: ''
|
|
236
|
-
}.`,
|
|
237
|
-
`Tailwind usage: \`uy:bg-<color>-<scale>\`, for example \`uy:bg-blue-${exampleScale}\`${
|
|
238
|
-
grayscaleExampleScale
|
|
239
|
-
? ` or \`uy:bg-grayscale-${grayscaleExampleScale}\``
|
|
240
|
-
: ''
|
|
241
|
-
}.`,
|
|
242
|
-
]
|
|
243
|
-
|
|
244
|
-
return `${lines.join('\n\n').trimEnd()}\n`
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export const generatePrimitiveColorReferenceFiles = (
|
|
248
|
-
legacyTokens: ColorToken[],
|
|
249
|
-
): ReferenceFile[] => {
|
|
250
|
-
const tokens = getReferenceTokens(legacyTokens).filter(token =>
|
|
251
|
-
PRIMITIVE_GROUPS.includes(token.group),
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
if (tokens.length === 0) {
|
|
255
|
-
return []
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return [
|
|
259
|
-
{
|
|
260
|
-
filename: 'primitive-colors.mdx',
|
|
261
|
-
content: generatePrimitiveColorsReference(tokens),
|
|
262
|
-
},
|
|
263
|
-
]
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
export const generateSemanticColorReferenceFiles = (
|
|
267
|
-
legacyTokens: ColorToken[],
|
|
268
|
-
): ReferenceFile[] => {
|
|
269
|
-
const tokens = getReferenceTokens(legacyTokens).filter(token =>
|
|
270
|
-
SEMANTIC_GROUP_NAMES.includes(token.group),
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
return SEMANTIC_GROUPS.flatMap(group => {
|
|
274
|
-
const groupTokens = tokens
|
|
275
|
-
.filter(token => group.groups.includes(token.group))
|
|
276
|
-
.sort(sortByName)
|
|
277
|
-
|
|
278
|
-
if (groupTokens.length === 0) {
|
|
279
|
-
return []
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return [
|
|
283
|
-
{
|
|
284
|
-
filename: `semantic-${group.slug}-colors.mdx`,
|
|
285
|
-
content: generateColorGroupReference({
|
|
286
|
-
title: `Semantic ${group.title} Colors`,
|
|
287
|
-
storybookTitle: `Semantic Colors/${group.title}`,
|
|
288
|
-
description: `Use semantic color tokens for product UI. These names encode intent and stay stable across Unity themes. ${group.usageGuidance}`,
|
|
289
|
-
tokens: groupTokens,
|
|
290
|
-
utilitySections: group.utilitySections,
|
|
291
|
-
}),
|
|
292
|
-
},
|
|
293
|
-
]
|
|
294
|
-
})
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const linkList = (files: ReferenceFile[]) =>
|
|
298
|
-
files.map(
|
|
299
|
-
file => `- [${file.filename.replace(/\.mdx$/, '')}](./${file.filename})`,
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
export const generateThemesAgentReferencesReadme = ({
|
|
303
|
-
primitiveFiles,
|
|
304
|
-
semanticFiles,
|
|
305
|
-
}: {
|
|
306
|
-
primitiveFiles: ReferenceFile[]
|
|
307
|
-
semanticFiles: ReferenceFile[]
|
|
308
|
-
}) => `${storybookMeta('Unity Themes')}
|
|
309
|
-
|
|
310
|
-
${generatedNotice('Unity Style Dictionary color tokens')}
|
|
311
|
-
|
|
312
|
-
# Unity Themes Agent References
|
|
313
|
-
|
|
314
|
-
Static Unity theme references for Storybook MCP, search, and agents.
|
|
315
|
-
|
|
316
|
-
Storybook includes these MDX files through the shared \`../src/**/*.mdx\` stories glob, and \`@storybook/addon-mcp\` exposes docs content to MCP clients.
|
|
317
|
-
|
|
318
|
-
## Semantic colors
|
|
319
|
-
|
|
320
|
-
${linkList(semanticFiles).join('\n')}
|
|
321
|
-
|
|
322
|
-
## Primitive colors
|
|
323
|
-
|
|
324
|
-
${linkList(primitiveFiles).join('\n')}
|
|
325
|
-
`
|
|
326
|
-
|
|
327
|
-
export const writeColorAgentReferences = ({
|
|
328
|
-
legacyTokens,
|
|
329
|
-
outputDir,
|
|
330
|
-
}: {
|
|
331
|
-
legacyTokens: ColorToken[]
|
|
332
|
-
outputDir: string
|
|
333
|
-
}) => {
|
|
334
|
-
const primitiveFiles = generatePrimitiveColorReferenceFiles(legacyTokens)
|
|
335
|
-
const semanticFiles = generateSemanticColorReferenceFiles(legacyTokens)
|
|
336
|
-
|
|
337
|
-
fs.rmSync(outputDir, { recursive: true, force: true })
|
|
338
|
-
fs.mkdirSync(outputDir, { recursive: true })
|
|
339
|
-
fs.writeFileSync(
|
|
340
|
-
path.join(outputDir, 'README.mdx'),
|
|
341
|
-
generateThemesAgentReferencesReadme({ primitiveFiles, semanticFiles }),
|
|
342
|
-
'utf8',
|
|
343
|
-
)
|
|
344
|
-
|
|
345
|
-
for (const file of [...semanticFiles, ...primitiveFiles]) {
|
|
346
|
-
fs.writeFileSync(path.join(outputDir, file.filename), file.content, 'utf8')
|
|
347
|
-
}
|
|
348
|
-
}
|