@payfit/unity-themes 2.24.2 → 2.25.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.
Files changed (57) hide show
  1. package/dist/css/unity.css +1653 -543
  2. package/dist/esm/components/unity-theme-provider.d.ts +25 -0
  3. package/dist/esm/components/unity-theme-provider.js +34 -0
  4. package/dist/esm/components/unity-theme-provider.test.d.ts +1 -0
  5. package/dist/esm/index.d.ts +1 -0
  6. package/dist/esm/index.js +8 -5
  7. package/dist/esm/scripts/actions/compose-multi-theme.d.ts +15 -0
  8. package/dist/esm/scripts/transforms/tailwind-color-token.d.ts +4 -3
  9. package/dist/esm/scripts/transforms/tailwind-grid-token.d.ts +2 -1
  10. package/dist/esm/scripts/transforms/tailwind-spacing-token.d.ts +1 -1
  11. package/dist/esm/scripts/transforms/tailwind-text-token.d.ts +1 -1
  12. package/dist/esm/scripts/transforms/tailwind-typography-token.d.ts +1 -4
  13. package/dist/esm/scripts/utils/prefix-transform.d.ts +4 -0
  14. package/dist/esm/utils/cn.d.ts +4 -3
  15. package/package.json +3 -2
  16. package/src/components/unity-theme-provider.stories.tsx +532 -0
  17. package/src/components/unity-theme-provider.test.tsx +150 -0
  18. package/src/components/unity-theme-provider.tsx +72 -0
  19. package/src/index.ts +8 -0
  20. package/src/scripts/actions/compose-multi-theme.ts +59 -0
  21. package/src/scripts/build.ts +261 -55
  22. package/src/scripts/formats/unity-theme.test.ts +180 -253
  23. package/src/scripts/formats/unity-theme.ts +27 -64
  24. package/src/scripts/transforms/tailwind-color-token.test.ts +18 -0
  25. package/src/scripts/transforms/tailwind-color-token.ts +7 -3
  26. package/src/scripts/transforms/tailwind-grid-token.test.ts +22 -0
  27. package/src/scripts/transforms/tailwind-grid-token.ts +7 -3
  28. package/src/scripts/transforms/tailwind-spacing-token.test.ts +9 -0
  29. package/src/scripts/transforms/tailwind-spacing-token.ts +15 -2
  30. package/src/scripts/transforms/tailwind-text-token.test.ts +18 -0
  31. package/src/scripts/transforms/tailwind-text-token.ts +15 -2
  32. package/src/scripts/transforms/tailwind-typography-token.test.ts +8 -2
  33. package/src/scripts/transforms/tailwind-typography-token.ts +5 -1
  34. package/src/scripts/utils/prefix-transform.test.ts +137 -0
  35. package/src/scripts/utils/prefix-transform.ts +16 -0
  36. package/src/utils/cn.ts +2 -2
  37. package/tokens/common/aspect-ratios.json +11 -0
  38. package/tokens/common/breakpoints.json +18 -0
  39. package/tokens/{text.json → common/font-sizes.json} +0 -28
  40. package/tokens/common/font-weights.json +18 -0
  41. package/tokens/{radii.json → common/radii.json} +0 -15
  42. package/tokens/{spacings.json → common/spacings.json} +0 -25
  43. package/tokens/legacy/radii.json +21 -0
  44. package/tokens/legacy/text.json +14 -0
  45. package/tokens/rebrand/colors.json +1400 -0
  46. package/tokens/rebrand/radii.json +21 -0
  47. package/tokens/rebrand/shadows.json +81 -0
  48. package/tokens/rebrand/text.json +14 -0
  49. package/tokens/rebrand/typography.json +329 -0
  50. package/dist/esm/scripts/formats/generators/header-generator.d.ts +0 -2
  51. package/src/scripts/formats/generators/header-generator.ts +0 -32
  52. /package/tokens/{animations.json → common/animations.json} +0 -0
  53. /package/tokens/{layout.json → common/layout.json} +0 -0
  54. /package/tokens/{sizes.json → common/sizes.json} +0 -0
  55. /package/tokens/{colors.json → legacy/colors.json} +0 -0
  56. /package/tokens/{shadows.json → legacy/shadows.json} +0 -0
  57. /package/tokens/{typography.json → legacy/typography.json} +0 -0
@@ -0,0 +1,72 @@
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 CHANGED
@@ -5,3 +5,11 @@ export * from './utils/tailwind-variants'
5
5
 
6
6
  // Configuration (for advanced users)
7
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'
@@ -0,0 +1,59 @@
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,12 +1,15 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
1
4
  import StyleDictionary from 'style-dictionary'
2
5
  import {
3
6
  logBrokenReferenceLevels,
4
7
  logVerbosityLevels,
5
8
  logWarningLevels,
6
- transformGroups,
7
9
  } from 'style-dictionary/enums'
8
10
 
9
- import * as animationAssetProcessor from './actions/append-animations.js'
11
+ import { appendAnimations } from './actions/append-animations.js'
12
+ import { composeMultiThemeCss } from './actions/compose-multi-theme.js'
10
13
  import {
11
14
  unityFileHeader,
12
15
  name as unityFileHeaderName,
@@ -23,6 +26,10 @@ import * as tailwindSpacingTokenTransformer from './transforms/tailwind-spacing-
23
26
  import * as tailwindTextTokenTransformer from './transforms/tailwind-text-token.js'
24
27
  import * as tailwindTypographyTokenTransformer from './transforms/tailwind-typography-token.js'
25
28
 
29
+ // ---------------------------------------------------------------------------
30
+ // Register extensions (shared by all SD instances)
31
+ // ---------------------------------------------------------------------------
32
+
26
33
  StyleDictionary.registerFormat({
27
34
  name: unityThemeFormatName,
28
35
  format: unityThemeFormat,
@@ -84,67 +91,266 @@ StyleDictionary.registerTransform({
84
91
  transform: tailwindAnimationTokenTransformer.transform,
85
92
  })
86
93
 
87
- StyleDictionary.registerAction({
88
- name: 'tailwind/v4/append_animations',
89
- do: animationAssetProcessor.appendAnimations,
90
- undo: animationAssetProcessor.removeAnimations,
91
- })
92
-
93
94
  StyleDictionary.registerFileHeader({
94
95
  name: unityFileHeaderName,
95
96
  fileHeader: unityFileHeader,
96
97
  })
97
98
 
98
- const sd = new StyleDictionary({
99
- source: ['./tokens/**/*.json'],
100
- platforms: {
101
- css: {
102
- name: 'tailwind',
103
- transformGroup: transformGroups.css,
104
- transforms: [
105
- 'attribute/cti',
106
- 'name/kebab',
107
- 'name/tailwind/v4/text',
108
- 'name/tailwind/v4/typography',
109
- 'name/tailwind/v4/color',
110
- 'name/tailwind/v4/spacing',
111
- 'name/tailwind/v4/grid',
112
- 'color/oklch',
113
- 'fontFamily/css',
114
- 'shadow/css/shorthand',
115
- 'cubicBezier/css',
116
- 'tailwind/v4/animations/shorthand',
117
- ],
118
- buildPath: './dist/css/',
119
- animationsPath: './assets/animations/',
120
- files: [
121
- {
122
- destination: 'unity.css',
123
- format: unityThemeFormatName,
124
- options: {
125
- prefix: 'uy',
126
- selector: `@theme static`,
127
- outputReferences: true,
128
- fileHeader: 'unity/theme',
99
+ // ---------------------------------------------------------------------------
100
+ // Shared config
101
+ // ---------------------------------------------------------------------------
102
+
103
+ const sharedTransforms = [
104
+ 'attribute/cti',
105
+ 'name/kebab',
106
+ 'name/tailwind/v4/text',
107
+ 'name/tailwind/v4/typography',
108
+ 'name/tailwind/v4/color',
109
+ 'name/tailwind/v4/spacing',
110
+ 'name/tailwind/v4/grid',
111
+ 'color/oklch',
112
+ 'fontFamily/css',
113
+ 'typography/css/shorthand',
114
+ 'shadow/css/shorthand',
115
+ 'cubicBezier/css',
116
+ 'tailwind/v4/animations/shorthand',
117
+ ]
118
+
119
+ const sharedLogConfig = {
120
+ warnings: logWarningLevels.warn,
121
+ verbosity: logVerbosityLevels.verbose,
122
+ errors: {
123
+ brokenReferences: logBrokenReferenceLevels.throw,
124
+ },
125
+ } as const
126
+
127
+ const BUILD_PATH = './dist/css/'
128
+ const OUTPUT_FILE = 'unity.css'
129
+ const ANIMATIONS_PATH = './assets/animations/'
130
+ const PREFIX = 'uy'
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Logging helpers
134
+ // ---------------------------------------------------------------------------
135
+
136
+ const color = {
137
+ cyan: (s: string) => `\x1b[36m${s}\x1b[0m`,
138
+ green: (s: string) => `\x1b[32m${s}\x1b[0m`,
139
+ yellow: (s: string) => `\x1b[33m${s}\x1b[0m`,
140
+ red: (s: string) => `\x1b[31m${s}\x1b[0m`,
141
+ dim: (s: string) => `\x1b[2m${s}\x1b[0m`,
142
+ bold: (s: string) => `\x1b[1m${s}\x1b[0m`,
143
+ } as const
144
+
145
+ function logStep(step: number, total: number, message: string) {
146
+ console.log(`${color.cyan(`[${step}/${total}]`)} ${color.bold(message)}`)
147
+ }
148
+
149
+ function logDetail(message: string) {
150
+ console.log(` ${color.dim('→')} ${message}`)
151
+ }
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // Build
155
+ // ---------------------------------------------------------------------------
156
+
157
+ const TOTAL_STEPS = 7
158
+
159
+ async function build() {
160
+ const startTime = performance.now()
161
+ console.log(color.bold('\n🏗 Unity Themes — Build\n'))
162
+
163
+ // 1. Legacy SD — css/variables with :root selector
164
+ logStep(1, TOTAL_STEPS, 'Initializing Style Dictionary instances')
165
+ logDetail(
166
+ `Legacy — tokens/common + tokens/legacy → ${color.yellow(':root')}`,
167
+ )
168
+
169
+ const legacySD = new StyleDictionary({
170
+ source: ['./tokens/common/**/*.json', './tokens/legacy/**/*.json'],
171
+ platforms: {
172
+ css: {
173
+ transforms: sharedTransforms,
174
+ prefix: PREFIX,
175
+ buildPath: BUILD_PATH,
176
+ files: [
177
+ {
178
+ destination: '_legacy.css',
179
+ format: 'css/variables',
180
+ options: {
181
+ selector: ':root, [data-uy-theme="legacy"]',
182
+ outputReferences: true,
183
+ showFileHeader: false,
184
+ },
129
185
  },
130
- },
131
- ],
132
- actions: ['tailwind/v4/append_animations'],
186
+ ],
187
+ },
133
188
  },
134
- },
135
- log: {
136
- warnings: logWarningLevels.warn,
137
- verbosity: logVerbosityLevels.verbose,
138
- errors: {
139
- brokenReferences: logBrokenReferenceLevels.throw,
189
+ log: sharedLogConfig,
190
+ })
191
+
192
+ // 2. Rebrand SD — css/variables with [data-uy-theme="rebrand"] selector
193
+ logDetail(
194
+ `Rebrand — tokens/common + tokens/rebrand → ${color.yellow(
195
+ '[data-uy-theme="rebrand"]',
196
+ )}`,
197
+ )
198
+ const rebrandSD = new StyleDictionary({
199
+ source: ['./tokens/common/**/*.json', './tokens/rebrand/**/*.json'],
200
+ platforms: {
201
+ css: {
202
+ transforms: sharedTransforms,
203
+ prefix: PREFIX,
204
+ buildPath: BUILD_PATH,
205
+ files: [
206
+ {
207
+ destination: '_rebrand.css',
208
+ format: 'css/variables',
209
+ options: {
210
+ selector: '[data-uy-theme="rebrand"]',
211
+ outputReferences: true,
212
+ showFileHeader: false,
213
+ },
214
+ },
215
+ ],
216
+ },
140
217
  },
141
- },
142
- })
218
+ log: sharedLogConfig,
219
+ })
143
220
 
144
- sd.buildAllPlatforms()
145
- .then(() => {
146
- console.log('✅ Build completed successfully')
221
+ // 3. Theme SD — css/unity-theme format for @theme block + utilities
222
+ logDetail(`TW theme — → ${color.yellow('@theme')}`)
223
+ const themeSD = new StyleDictionary({
224
+ source: [
225
+ './tokens/common/**/*.json',
226
+ './tokens/legacy/**/*.json',
227
+ './tokens/rebrand/**/*.json',
228
+ ],
229
+ platforms: {
230
+ css: {
231
+ transforms: sharedTransforms,
232
+ prefix: PREFIX,
233
+ buildPath: BUILD_PATH,
234
+ files: [
235
+ {
236
+ destination: '_theme.css',
237
+ format: unityThemeFormatName,
238
+ options: {
239
+ prefix: PREFIX,
240
+ selector: '@theme',
241
+ outputReferences: true,
242
+ showFileHeader: false,
243
+ },
244
+ },
245
+ ],
246
+ },
247
+ },
248
+ log: sharedLogConfig,
147
249
  })
148
- .catch((error: unknown) => {
149
- console.error('❌ Build failed:', error)
250
+
251
+ // 4. Format all three in memory
252
+ logStep(2, TOTAL_STEPS, 'Formatting tokens (in-memory)')
253
+ const formatStart = performance.now()
254
+
255
+ const [legacyOutputs, rebrandOutputs, themeOutputs] = await Promise.all([
256
+ legacySD.formatPlatform('css'),
257
+ rebrandSD.formatPlatform('css'),
258
+ themeSD.formatPlatform('css'),
259
+ ])
260
+
261
+ const legacyCss = String(legacyOutputs[0]?.output ?? '')
262
+ const rebrandCss = String(rebrandOutputs[0]?.output ?? '')
263
+ const themeCss = String(themeOutputs[0]?.output ?? '')
264
+
265
+ const formatDuration = (performance.now() - formatStart).toFixed(0)
266
+ logDetail(
267
+ `Legacy CSS: ${color.green(`${legacyCss.split('\n').length} lines`)}`,
268
+ )
269
+ logDetail(
270
+ `Rebrand CSS: ${color.green(`${rebrandCss.split('\n').length} lines`)}`,
271
+ )
272
+ logDetail(
273
+ `Theme CSS: ${color.green(`${themeCss.split('\n').length} lines`)}`,
274
+ )
275
+ logDetail(color.dim(`Formatted in ${formatDuration}ms`))
276
+
277
+ // 5. Generate header + custom variants
278
+ logStep(3, TOTAL_STEPS, 'Generating file header and font imports')
279
+ const headerLines = unityFileHeader()
280
+ const header = [
281
+ '/**',
282
+ ...headerLines.map(l => ` * ${l}`),
283
+ ' */\n',
284
+ '@import "@fontsource/inter";',
285
+ '@import "@fontsource/inter/500.css";',
286
+ '@import "@fontsource/inter/600.css";',
287
+ '@import "@fontsource/inter/700.css";',
288
+ '@import "@fontsource/source-serif-4";',
289
+ '@import "@fontsource/source-serif-4/500.css";',
290
+ '@import "@fontsource/source-serif-4/600.css";',
291
+ '@import "@fontsource/source-serif-4/700.css";',
292
+ '@import "@fontsource/roboto-mono";',
293
+ `@import "tailwindcss" prefix(${PREFIX});`,
294
+ ].join('\n')
295
+
296
+ logStep(4, TOTAL_STEPS, 'Registering custom variants')
297
+ const customVariants = [
298
+ '@custom-variant theme-legacy (&:where([data-uy-theme="legacy"], [data-uy-theme="legacy"] *));',
299
+ '@custom-variant theme-rebrand (&:where([data-uy-theme="rebrand"], [data-uy-theme="rebrand"] *));',
300
+ ].join('\n')
301
+ logDetail('theme-legacy → [data-uy-theme="legacy"]')
302
+ logDetail('theme-rebrand → [data-uy-theme="rebrand"]')
303
+
304
+ // 6. Compose and write
305
+ logStep(5, TOTAL_STEPS, 'Composing multi-theme CSS')
306
+ const outputPath = path.join(BUILD_PATH, OUTPUT_FILE)
307
+ logDetail(`Output: ${color.yellow(outputPath)}`)
308
+
309
+ await composeMultiThemeCss({
310
+ legacyCss,
311
+ rebrandCss,
312
+ themeCss,
313
+ header,
314
+ customVariants,
315
+ outputPath,
150
316
  })
317
+
318
+ // 7. Append animation keyframes + reset CSS
319
+ logStep(6, TOTAL_STEPS, 'Appending animation keyframes')
320
+ logDetail(`Source: ${color.yellow(ANIMATIONS_PATH)}`)
321
+
322
+ const legacyDict = await legacySD.getPlatformTokens('css')
323
+ appendAnimations(
324
+ legacyDict,
325
+ {
326
+ animationsPath: ANIMATIONS_PATH,
327
+ buildPath: BUILD_PATH,
328
+ files: [{ destination: OUTPUT_FILE }],
329
+ },
330
+ {}, // kept for SD compatibility
331
+ )
332
+
333
+ // Done
334
+ const totalDuration = (performance.now() - startTime).toFixed(0)
335
+ logStep(7, TOTAL_STEPS, 'Build verification')
336
+ const outputStat = fs.statSync(outputPath)
337
+ logDetail(
338
+ `File size: ${color.green(`${(outputStat.size / 1024).toFixed(1)} KB`)}`,
339
+ )
340
+ logDetail(`Total time: ${color.green(`${totalDuration}ms`)}`)
341
+
342
+ console.log(`\n${color.green('✅ Build completed successfully')}\n`)
343
+ }
344
+
345
+ build().catch((error: unknown) => {
346
+ console.error(`\n${color.red('❌ Build failed')}\n`)
347
+ if (error instanceof Error) {
348
+ console.error(color.red(`Error: ${error.message}`))
349
+ if (error.stack) {
350
+ console.error(color.dim(error.stack))
351
+ }
352
+ } else {
353
+ console.error(error)
354
+ }
355
+ process.exit(1)
356
+ })