diff --git a/.changeset/color-palette-handling.md b/.changeset/color-palette-handling.md new file mode 100644 index 000000000..20095d4a3 --- /dev/null +++ b/.changeset/color-palette-handling.md @@ -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'], + }, + }, +}) +``` diff --git a/packages/core/src/context.ts b/packages/core/src/context.ts index 429c954d4..36dc6fb60 100644 --- a/packages/core/src/context.ts +++ b/packages/core/src/context.ts @@ -258,6 +258,7 @@ export class Context { themes: themeVariants, prefix: this.prefix.tokens, hash: this.hash.tokens, + colorPalette: theme.colorPalette, }) } diff --git a/packages/token-dictionary/__tests__/color-palette.test.ts b/packages/token-dictionary/__tests__/color-palette.test.ts index e8c08d585..83213f70b 100644 --- a/packages/token-dictionary/__tests__/color-palette.test.ts +++ b/packages/token-dictionary/__tests__/color-palette.test.ts @@ -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() @@ -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", + ] + `) +}) diff --git a/packages/token-dictionary/src/dictionary.ts b/packages/token-dictionary/src/dictionary.ts index 2235cabdc..c60ed9337 100644 --- a/packages/token-dictionary/src/dictionary.ts +++ b/packages/token-dictionary/src/dictionary.ts @@ -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' @@ -36,6 +43,7 @@ export interface TokenDictionaryOptions { themes?: ThemeVariantsMap | undefined prefix?: string hash?: boolean + colorPalette?: ColorPaletteOptions } export interface TokenMiddleware { @@ -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) } diff --git a/packages/token-dictionary/src/middleware.ts b/packages/token-dictionary/src/middleware.ts index 5ed9ca05d..f767feec4 100644 --- a/packages/token-dictionary/src/middleware.ts +++ b/packages/token-dictionary/src/middleware.ts @@ -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() diff --git a/packages/token-dictionary/src/transform.ts b/packages/token-dictionary/src/transform.ts index 6e1930dad..574f7307c 100644 --- a/packages/token-dictionary/src/transform.ts +++ b/packages/token-dictionary/src/transform.ts @@ -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() @@ -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 diff --git a/packages/types/src/theme.ts b/packages/types/src/theme.ts index 3b8375bf3..97e1ceef7 100644 --- a/packages/types/src/theme.ts +++ b/packages/types/src/theme.ts @@ -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. @@ -49,6 +67,10 @@ export interface Theme { * The predefined container sizes for your project. */ containerSizes?: Record + /** + * The color palette configuration for your project. + */ + colorPalette?: ColorPaletteOptions } interface PartialTheme extends Omit { @@ -61,6 +83,10 @@ interface PartialTheme extends Omit { * Multi-variant style definitions for component slots. */ slotRecipes?: Record> + /** + * The color palette configuration for your project. + */ + colorPalette?: Partial } export interface ExtendableTheme extends Theme { diff --git a/website/pages/docs/concepts/virtual-color.md b/website/pages/docs/concepts/virtual-color.md index 12ef3d9d0..243ea5970 100644 --- a/website/pages/docs/concepts/virtual-color.md +++ b/website/pages/docs/concepts/virtual-color.md @@ -224,3 +224,81 @@ function ButtonShowcase() { ) } ``` + +## Configuration + +By default, color palette generation is enabled and includes all colors defined in your theme. + +You can control which colors are used to generate color palettes by configuring the `colorPalette` property in your theme. + +### Disable Color Palette + +To completely disable color palette generation, set `enabled` to `false`: + +```ts filename="panda.config.ts" +import { defineConfig } from '@pandacss/dev' + +export default defineConfig({ + theme: { + colorPalette: { + enabled: false + } + } +}) +``` + +### Include Specific Colors + +To generate color palettes for only specific colors, use the `include` option: + +```ts filename="panda.config.ts" +import { defineConfig } from '@pandacss/dev' + +export default defineConfig({ + theme: { + colorPalette: { + include: ['gray', 'blue', 'red'] + } + } +}) +``` + +This will only generate color palettes for `gray`, `blue`, and `red` colors, even if you have other colors defined in your theme. + +### Exclude Specific Colors + +To exclude certain colors from color palette generation, use the `exclude` option: + +```ts filename="panda.config.ts" +import { defineConfig } from '@pandacss/dev' + +export default defineConfig({ + theme: { + colorPalette: { + exclude: ['yellow', 'orange'] + } + } +}) +``` + +This will generate color palettes for all colors except `yellow` and `orange`. + +### Combination of Options + +You can combine the `enabled`, `include`, and `exclude` options as needed: + +```ts filename="panda.config.ts" +import { defineConfig } from '@pandacss/dev' + +export default defineConfig({ + theme: { + colorPalette: { + enabled: true, + include: ['gray', 'blue', 'red', 'green'], + exclude: ['red'] // This will override the include for 'red' + } + } +}) +``` + +In this example, color palettes will be generated for `gray`, `blue`, and `green`, but not for `red` (since it's excluded).