Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .changeset/color-palette-handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
'@pandacss/types': minor
'@pandacss/token-dictionary': minor
'@pandacss/core': minor
'@pandacss/config': minor
'@pandacss/generator': minor
---

Add support for controlling the color palette generation via `theme.colorPalette` property.

```ts
// Disable color palette generation completely
export default defineConfig({
theme: {
colorPalette: {
enabled: false,
},
},
})

// Include only specific colors
export default defineConfig({
theme: {
colorPalette: {
include: ['gray', 'blue', 'red'],
},
},
})

// Exclude specific colors
export default defineConfig({
theme: {
colorPalette: {
exclude: ['yellow', 'orange'],
},
},
})
```
1 change: 1 addition & 0 deletions packages/core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ export class Context {
themes: themeVariants,
prefix: this.prefix.tokens,
hash: this.hash.tokens,
colorPalette: theme.colorPalette,
})
}

Expand Down
167 changes: 167 additions & 0 deletions packages/token-dictionary/__tests__/color-palette.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import { addVirtualPalette } from '../src/middleware'
import { transforms } from '../src/transform'
import { TokenDictionary } from '../src/dictionary'

const createDictionary = (options: any = {}) => {
const dictionary = new TokenDictionary(options)
return dictionary
.registerTokens()
.registerTransform(...transforms)
.registerMiddleware(addVirtualPalette)
.build()
}

const dasherize = (token: string) =>
token
.toString()
Expand Down Expand Up @@ -1634,3 +1643,161 @@ test('should generate virtual palette with DEFAULT value', () => {
}
`)
})

test('should disable color palette when enabled is false', () => {
const dictionary = createDictionary({
tokens: {
colors: {
red: {
500: { value: '#red500' },
700: { value: '#red700' },
},
blue: {
500: { value: '#blue500' },
700: { value: '#blue700' },
},
},
},
colorPalette: {
enabled: false,
},
})

const getVar = dictionary.view.get
expect(getVar('colors.colorPalette.500')).toBeUndefined()
expect(getVar('colors.colorPalette.700')).toBeUndefined()

expect(Array.from(dictionary.view.colorPalettes.keys())).toHaveLength(0)
})

test('should include only specified colors in color palette', () => {
const dictionary = createDictionary({
tokens: {
colors: {
red: {
500: { value: '#red500' },
700: { value: '#red700' },
},
blue: {
500: { value: '#blue500' },
700: { value: '#blue700' },
},
green: {
500: { value: '#green500' },
700: { value: '#green700' },
},
},
},
colorPalette: {
include: ['red', 'blue'],
},
})

const getVar = dictionary.view.get
expect(getVar('colors.colorPalette.500')).toBe('var(--colors-color-palette-500)')
expect(getVar('colors.colorPalette.700')).toBe('var(--colors-color-palette-700)')

expect(Array.from(dictionary.view.colorPalettes.keys())).toMatchInlineSnapshot(`
[
"red",
"blue",
]
`)
})

test('should exclude specified colors from color palette', () => {
const dictionary = createDictionary({
tokens: {
colors: {
red: {
500: { value: '#red500' },
700: { value: '#red700' },
},
blue: {
500: { value: '#blue500' },
700: { value: '#blue700' },
},
green: {
500: { value: '#green500' },
700: { value: '#green700' },
},
},
},
colorPalette: {
exclude: ['red'],
},
})

const getVar = dictionary.view.get
expect(getVar('colors.colorPalette.500')).toBe('var(--colors-color-palette-500)')
expect(getVar('colors.colorPalette.700')).toBe('var(--colors-color-palette-700)')

expect(Array.from(dictionary.view.colorPalettes.keys())).toMatchInlineSnapshot(`
[
"blue",
"green",
]
`)
})

test('should handle semantic tokens with colorPalette configuration', () => {
const dictionary = createDictionary({
semanticTokens: {
colors: {
primary: {
value: '{colors.blue.500}',
},
secondary: {
value: '{colors.red.500}',
},
accent: {
value: '{colors.green.500}',
},
},
},
tokens: {
colors: {
blue: {
500: { value: '#blue500' },
},
red: {
500: { value: '#red500' },
},
green: {
500: { value: '#green500' },
},
},
},
colorPalette: {
include: ['primary'],
},
})

expect(Array.from(dictionary.view.colorPalettes.keys())).toMatchInlineSnapshot(`
[
"primary",
]
`)
})

test('should enable color palette by default when no configuration is provided', () => {
const dictionary = createDictionary({
tokens: {
colors: {
red: {
500: { value: '#red500' },
},
},
},
// No colorPalette config provided - should default to enabled: true
})

const getVar = dictionary.view.get
expect(getVar('colors.colorPalette.500')).toBe('var(--colors-color-palette-500)')

expect(Array.from(dictionary.view.colorPalettes.keys())).toMatchInlineSnapshot(`
[
"red",
]
`)
})
14 changes: 13 additions & 1 deletion packages/token-dictionary/src/dictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ import {
type CssVar,
type CssVarOptions,
} from '@pandacss/shared'
import type { Recursive, SemanticTokens, ThemeVariantsMap, TokenCategory, Tokens } from '@pandacss/types'
import type {
Recursive,
SemanticTokens,
ThemeVariantsMap,
TokenCategory,
Tokens,
ColorPaletteOptions,
} from '@pandacss/types'
import { isMatching, match } from 'ts-pattern'
import { isCompositeTokenValue } from './is-composite'
import { middlewares } from './middleware'
Expand All @@ -36,6 +43,7 @@ export interface TokenDictionaryOptions {
themes?: ThemeVariantsMap | undefined
prefix?: string
hash?: boolean
colorPalette?: ColorPaletteOptions
}

export interface TokenMiddleware {
Expand Down Expand Up @@ -80,6 +88,10 @@ export class TokenDictionary {
return this.options.hash
}

get colorPalette() {
return this.options.colorPalette
}

getByName = (path: string) => {
return this.byName.get(path)
}
Expand Down
6 changes: 6 additions & 0 deletions packages/token-dictionary/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ export const addPixelUnit: TokenMiddleware = {
export const addVirtualPalette: TokenMiddleware = {
enforce: 'post',
transform(dictionary: TokenDictionary) {
const colorPaletteConfig = dictionary.colorPalette
const enabled = colorPaletteConfig?.enabled ?? true

// If disabled, skip generating color palettes
if (!enabled) return

const tokens = dictionary.filter({ extensions: { category: 'colors' } })

const keys = new Map<string, string[]>()
Expand Down
14 changes: 14 additions & 0 deletions packages/token-dictionary/src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,15 @@ export const addColorPalette: TokenTransformer = {
return token.extensions.category === 'colors' && !token.extensions.isVirtual
},
transform(token, dict) {
// Check colorPalette configuration
const colorPaletteConfig = dict.colorPalette
const enabled = colorPaletteConfig?.enabled ?? true
const include = colorPaletteConfig?.include
const exclude = colorPaletteConfig?.exclude

// If disabled, don't add colorPalette extensions
if (!enabled) return {}

let tokenPathClone = [...token.path]
tokenPathClone.pop()
tokenPathClone.shift()
Expand All @@ -243,6 +252,11 @@ export const addColorPalette: TokenTransformer = {
return {}
}

// Check include/exclude filters
const colorName = token.path[1] // e.g., 'blue' from ['colors', 'blue', '500']
if (include && !include.includes(colorName)) return {}
if (exclude && exclude.includes(colorName)) return {}

/**
* If this is the nested color palette:
* ```json
Expand Down
26 changes: 26 additions & 0 deletions packages/types/src/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@ import type { RecipeConfig, SlotRecipeConfig } from './recipe'
import type { CssKeyframes } from './system-types'
import type { SemanticTokens, Tokens } from './tokens'

export interface ColorPaletteOptions {
/**
* Whether to enable color palette generation.
* @default true
*/
enabled?: boolean
/**
* List of color names to include in color palette generation.
* When specified, only these colors will be used for color palettes.
*/
include?: string[]
/**
* List of color names to exclude from color palette generation.
* When specified, these colors will not be used for color palettes.
*/
exclude?: string[]
}

export interface Theme {
/**
* The breakpoints for your project.
Expand Down Expand Up @@ -49,6 +67,10 @@ export interface Theme {
* The predefined container sizes for your project.
*/
containerSizes?: Record<string, string>
/**
* The color palette configuration for your project.
*/
colorPalette?: ColorPaletteOptions
}

interface PartialTheme extends Omit<Theme, 'recipes' | 'slotRecipes'> {
Expand All @@ -61,6 +83,10 @@ interface PartialTheme extends Omit<Theme, 'recipes' | 'slotRecipes'> {
* Multi-variant style definitions for component slots.
*/
slotRecipes?: Record<string, Partial<SlotRecipeConfig>>
/**
* The color palette configuration for your project.
*/
colorPalette?: Partial<ColorPaletteOptions>
}

export interface ExtendableTheme extends Theme {
Expand Down
Loading