@codecademy/variance 0.24.1-alpha.3e8b72.0 → 0.24.1-alpha.43251f.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.
Files changed (41) hide show
  1. package/dist/core.d.ts +10 -0
  2. package/dist/core.js +223 -0
  3. package/dist/createTheme/createTheme.d.ts +55 -0
  4. package/dist/createTheme/createTheme.js +121 -0
  5. package/dist/createTheme/createTheme.test.js +176 -0
  6. package/dist/createTheme/index.d.ts +3 -0
  7. package/dist/createTheme/index.js +3 -0
  8. package/dist/createTheme/types.d.ts +42 -0
  9. package/dist/createTheme/types.js +1 -0
  10. package/dist/index.d.ts +5 -0
  11. package/dist/index.js +5 -0
  12. package/dist/scales/createScale.d.ts +3 -0
  13. package/dist/scales/createScale.js +1 -0
  14. package/dist/scales/createScaleLookup.d.ts +5 -0
  15. package/dist/scales/createScaleLookup.js +16 -0
  16. package/dist/transforms/index.d.ts +1 -0
  17. package/dist/transforms/index.js +1 -0
  18. package/dist/transforms/transformSize.d.ts +2 -0
  19. package/dist/transforms/transformSize.js +25 -0
  20. package/dist/types/config.d.ts +71 -0
  21. package/dist/types/config.js +1 -0
  22. package/dist/types/properties.d.ts +22 -0
  23. package/dist/types/properties.js +1 -0
  24. package/dist/types/props.d.ts +66 -0
  25. package/dist/types/props.js +1 -0
  26. package/dist/types/theme.d.ts +23 -0
  27. package/dist/types/theme.js +1 -0
  28. package/dist/types/utils.d.ts +3 -0
  29. package/dist/types/utils.js +1 -0
  30. package/dist/utils/__fixtures__/testConfig.d.ts +153 -0
  31. package/dist/utils/flattenScale.d.ts +20 -0
  32. package/dist/utils/flattenScale.js +35 -0
  33. package/dist/utils/getStaticProperties.d.ts +1 -0
  34. package/dist/utils/getStaticProperties.js +3 -0
  35. package/dist/utils/propNames.d.ts +6 -0
  36. package/dist/utils/propNames.js +47 -0
  37. package/dist/utils/responsive.d.ts +16 -0
  38. package/dist/utils/responsive.js +85 -0
  39. package/dist/utils/serializeTokens.d.ts +18 -0
  40. package/dist/utils/serializeTokens.js +50 -0
  41. package/package.json +2 -2
package/dist/core.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { AbstractParser, AbstractPropTransformer, Compose, CSS, Parser, Prop, PropTransformer, States, TransformerMap, Variant } from './types/config';
2
+ export declare const variance: {
3
+ createParser<Config extends Record<string, AbstractPropTransformer>>(config: Config): Parser<Config>;
4
+ createTransform<P extends string, Config_1 extends Prop>(prop: P, config: Config_1): PropTransformer<P, Config_1>;
5
+ compose<Args extends AbstractParser[]>(...parsers: Args): Parser<Compose<Args>>;
6
+ createCss<Config_2 extends Record<string, Prop>, P_1 extends Parser<TransformerMap<Config_2>>>(config: Config_2): CSS<P_1>;
7
+ createVariant<Config_3 extends Record<string, Prop>, P_2 extends Parser<TransformerMap<Config_3>>>(config: Config_3): Variant<P_2>;
8
+ createStates<Config_4 extends Record<string, Prop>, P_3 extends Parser<TransformerMap<Config_4>>>(config: Config_4): States<P_3>;
9
+ create<Config_5 extends Record<string, Prop>>(config: Config_5): Parser<TransformerMap<Config_5>>;
10
+ };
package/dist/core.js ADDED
@@ -0,0 +1,223 @@
1
+ import get from 'lodash/get';
2
+ import identity from 'lodash/identity';
3
+ import isArray from 'lodash/isArray';
4
+ import isObject from 'lodash/isObject';
5
+ import isUndefined from 'lodash/isUndefined';
6
+ import merge from 'lodash/merge';
7
+ import { createScaleLookup } from './scales/createScaleLookup';
8
+ import { getStaticCss } from './utils/getStaticProperties';
9
+ import { orderPropNames } from './utils/propNames';
10
+ import { arrayParser, isMediaArray, isMediaMap, objectParser, orderBreakpoints, parseBreakpoints } from './utils/responsive';
11
+ export const variance = {
12
+ // Parser to handle any set of configured props
13
+ createParser(config) {
14
+ const propNames = orderPropNames(config);
15
+ let breakpoints;
16
+ const parser = props => {
17
+ const styles = {};
18
+ const {
19
+ theme
20
+ } = props;
21
+ // Attempt to cache the breakpoints if we have not yet or if theme has become available.
22
+ if (breakpoints === undefined || breakpoints === null && theme?.breakpoints) {
23
+ // Save the breakpoints if we can
24
+ breakpoints = parseBreakpoints(theme?.breakpoints);
25
+ }
26
+
27
+ // Loops over all prop names on the configured config to check for configured styles
28
+ propNames.forEach(prop => {
29
+ const property = config[prop];
30
+ const value = get(props, prop);
31
+ switch (typeof value) {
32
+ case 'string':
33
+ case 'number':
34
+ case 'function':
35
+ return Object.assign(styles, property.styleFn(value, prop, props));
36
+ // handle any props configured with the responsive notation
37
+ case 'object':
38
+ if (!breakpoints) {
39
+ return;
40
+ }
41
+ // If it is an array the order of values is smallest to largest: [_, xs, ...]
42
+ if (isMediaArray(value)) {
43
+ return merge(styles, arrayParser(value, props, property, breakpoints.array));
44
+ }
45
+ // Check to see if value is an object matching the responsive syntax and generate the styles.
46
+ if (isMediaMap(value)) {
47
+ return merge(styles, objectParser(value, props, property, breakpoints.map));
48
+ }
49
+ }
50
+ });
51
+ return breakpoints ? orderBreakpoints(styles, breakpoints.array) : styles;
52
+ };
53
+ // return the parser function with the resulting meta information for further composition
54
+ return Object.assign(parser, {
55
+ propNames,
56
+ config
57
+ });
58
+ },
59
+ // Given a single property configuration enrich the config with a transform function
60
+ // that traverses the properties the function is responsible for.
61
+ createTransform(prop, config) {
62
+ const {
63
+ transform = identity,
64
+ property,
65
+ properties = [property],
66
+ scale
67
+ } = config;
68
+ const getScaleValue = createScaleLookup(scale);
69
+ const alwaysTransform = scale === undefined || isArray(scale);
70
+ return {
71
+ ...config,
72
+ prop,
73
+ styleFn: (value, prop, props) => {
74
+ const styles = {};
75
+ if (isUndefined(value)) {
76
+ return styles;
77
+ }
78
+ let useTransform = false;
79
+ let intermediateValue;
80
+ let scaleValue;
81
+ switch (typeof value) {
82
+ case 'number':
83
+ case 'string':
84
+ scaleValue = getScaleValue(value, props);
85
+ useTransform = scaleValue !== undefined || alwaysTransform;
86
+ intermediateValue = scaleValue ?? value;
87
+ break;
88
+ case 'function':
89
+ if (props.theme) {
90
+ intermediateValue = value(props.theme);
91
+ }
92
+ break;
93
+ default:
94
+ return styles;
95
+ }
96
+
97
+ // for each property look up the scale value from theme if passed and apply any
98
+ // final transforms to the value
99
+ properties.forEach(property => {
100
+ let styleValue = intermediateValue;
101
+ if (useTransform && !isUndefined(styleValue)) {
102
+ styleValue = transform(styleValue, property, props);
103
+ }
104
+ switch (typeof styleValue) {
105
+ case 'number':
106
+ case 'string':
107
+ return styles[property] = styleValue;
108
+ case 'object':
109
+ return Object.assign(styles, styleValue);
110
+ default:
111
+ }
112
+ });
113
+ // return the resulting styles object
114
+ return styles;
115
+ }
116
+ };
117
+ },
118
+ compose(...parsers) {
119
+ return this.createParser(parsers.reduce((carry, parser) => ({
120
+ ...carry,
121
+ ...parser.config
122
+ }), {}));
123
+ },
124
+ createCss(config) {
125
+ const parser = this.create(config);
126
+ const filteredProps = parser.propNames;
127
+ return cssProps => {
128
+ let cache;
129
+ const allKeys = Object.keys(cssProps);
130
+
131
+ /** Any key of the CSSProps that is not a System Prop or a Static CSS Property is treated as a nested selector */
132
+ const selectors = allKeys.filter(key => !filteredProps.includes(key) && isObject(cssProps[key]));
133
+
134
+ /** Static CSS Properties get extracted if they match neither syntax */
135
+ const staticCss = getStaticCss(cssProps, ['theme',
136
+ // Just in case this gets passed somehow
137
+ ...selectors, ...filteredProps]);
138
+ return ({
139
+ theme
140
+ }) => {
141
+ if (cache) return cache;
142
+ const css = parser({
143
+ ...cssProps,
144
+ theme
145
+ });
146
+ selectors.forEach(selector => {
147
+ const selectorConfig = cssProps[selector] ?? {};
148
+ css[selector] = {
149
+ ...getStaticCss(selectorConfig, filteredProps),
150
+ ...parser({
151
+ ...selectorConfig,
152
+ theme
153
+ })
154
+ };
155
+ });
156
+
157
+ /** Merge the static and generated css and save it to the cache */
158
+ cache = {
159
+ ...staticCss,
160
+ ...css
161
+ };
162
+ return cache;
163
+ };
164
+ };
165
+ },
166
+ createVariant(config) {
167
+ const css = this.createCss(config);
168
+ return ({
169
+ prop = 'variant',
170
+ defaultVariant,
171
+ base = {},
172
+ variants
173
+ }) => {
174
+ const baseFn = css(base);
175
+ const variantFns = {};
176
+ Object.keys(variants).forEach(key => {
177
+ const variantKey = key;
178
+ const cssProps = variants[variantKey];
179
+ variantFns[variantKey] = css(cssProps);
180
+ });
181
+ return props => {
182
+ const {
183
+ [prop]: selected = defaultVariant
184
+ } = props;
185
+ const styles = {};
186
+ if (!selected) return styles;
187
+ return merge(styles, baseFn(props), variantFns?.[selected]?.(props));
188
+ };
189
+ };
190
+ },
191
+ createStates(config) {
192
+ const css = this.createCss(config);
193
+ return states => {
194
+ const orderedStates = Object.keys(states);
195
+ const stateFns = {};
196
+ orderedStates.forEach(key => {
197
+ const stateKey = key;
198
+ const cssProps = states[stateKey];
199
+ stateFns[stateKey] = css(cssProps);
200
+ });
201
+ return props => {
202
+ const styles = {};
203
+ orderedStates.forEach(state => {
204
+ merge(styles, props[state] && stateFns[state](props));
205
+ });
206
+ return styles;
207
+ };
208
+ };
209
+ },
210
+ create(config) {
211
+ const transforms = {};
212
+
213
+ // Create a transform function for each of the props
214
+ for (const prop in config) {
215
+ if (typeof prop === 'string') {
216
+ transforms[prop] = this.createTransform(prop, config[prop]);
217
+ }
218
+ }
219
+
220
+ // Create a parser that handles all the props within the config
221
+ return this.createParser(transforms);
222
+ }
223
+ };
@@ -0,0 +1,55 @@
1
+ import { CSSObject } from '../types/props';
2
+ import { AbstractTheme } from '../types/theme';
3
+ import { LiteralPaths } from '../utils/flattenScale';
4
+ import { KeyAsVariable } from '../utils/serializeTokens';
5
+ import { ColorModeConfig, Merge, MergeTheme, PrivateThemeKeys } from './types';
6
+ declare class ThemeBuilder<T extends AbstractTheme> {
7
+ #private;
8
+ constructor(baseTheme: T);
9
+ /**
10
+ *
11
+ * @param key A key of the current theme to transform into CSS Variables and Variable References
12
+ * @example .createScaleVariables('fontSize')
13
+ */
14
+ createScaleVariables<Key extends keyof Omit<T, 'breakpoints'> & string>(key: Key): ThemeBuilder<MergeTheme<T, PrivateThemeKeys, Record<Key, Record<Key, KeyAsVariable<T[Key], Key>>>>>;
15
+ /**
16
+ *
17
+ * @param colors A map of color tokens to add to the theme. These tokens are immediately converted to CSS Variables `--color-${key}`.
18
+ * @example .addColors({ navy: 'navy', hyper: 'purple' })
19
+ */
20
+ addColors<Colors extends Record<string, string | number | CSSObject>, NextColors extends LiteralPaths<Colors, '-'>>(colors: Colors): ThemeBuilder<MergeTheme<T & PrivateThemeKeys, Record<'colors', KeyAsVariable<NextColors, 'color'>>>>;
21
+ /**
22
+ *
23
+ * @param initialMode A key of the object passed for modes. This sets the default state for the theme and transforms the correct variables.
24
+ * @param modes A map of color modes with keys of each possible mode with a value of alias to color keys. This must be called after `addColors`
25
+ * @example .addColorModes('light', { light: { primary: 'hyper' }, { dark: { primary: 'navy' } } })
26
+ */
27
+ addColorModes<Modes extends string, InitialMode extends keyof Config, Colors extends keyof T['colors'], ModeColors extends ColorModeConfig<Colors>, Config extends Record<Modes, ModeColors>, ColorAliases extends {
28
+ [K in keyof Config]: LiteralPaths<Config[K], '-', '_'>;
29
+ }>(initialMode: InitialMode, modeConfig: Config): ThemeBuilder<MergeTheme<T & PrivateThemeKeys, {
30
+ colors: KeyAsVariable<LiteralPaths<Config[keyof Config], '-', '_'>, 'colors'> & T['colors'];
31
+ modes: Merge<T['modes'], ColorAliases>;
32
+ mode: keyof Config;
33
+ _getColorValue: (color: keyof T['colors']) => string;
34
+ }>>;
35
+ /**
36
+ *
37
+ * @param key A new key of theme
38
+ * @param createScale A function that accepts the current theme and returns a new object of scale values.
39
+ * @example .addScale('fonts', () => ({ basic: 'Gotham', cool: 'Wingdings' }))
40
+ */
41
+ addScale<Key extends string, Fn extends (theme: T) => Record<string | number, string | number | Record<string, string | number>>, NewScale extends LiteralPaths<ReturnType<Fn>, '-'>>(key: Key, createScale: Fn): ThemeBuilder<MergeTheme<T, Record<Key, NewScale>>>;
42
+ /**
43
+ *
44
+ * @param key A current key of theme to be updated with new or computed values
45
+ * @param updateFn A function that accepts an argument of the current values at the specified keys an returns a map of new values to merge.
46
+ * @example .updateScale('fonts', ({ basic }) => ({ basicFallback: `{basic}, Montserrat` }))
47
+ */
48
+ updateScale<Key extends keyof T, Fn extends (tokens: T[Key]) => Record<string | number, unknown>>(key: Key, updateFn: Fn): ThemeBuilder<T & Record<Key, T[Key] & ReturnType<Fn>>>;
49
+ /**
50
+ * This finalizes the theme build and returns the final theme and variables to be provided.
51
+ */
52
+ build(): T & PrivateThemeKeys;
53
+ }
54
+ export declare function createTheme<T extends AbstractTheme>(base: T): ThemeBuilder<T>;
55
+ export {};
@@ -0,0 +1,121 @@
1
+ import mapValues from 'lodash/mapValues';
2
+ import merge from 'lodash/merge';
3
+ import { flattenScale } from '../utils/flattenScale';
4
+ import { serializeTokens } from '../utils/serializeTokens';
5
+ class ThemeBuilder {
6
+ #theme = {};
7
+ constructor(baseTheme) {
8
+ this.#theme = baseTheme;
9
+ }
10
+ /**
11
+ *
12
+ * @param key A key of the current theme to transform into CSS Variables and Variable References
13
+ * @example .createScaleVariables('fontSize')
14
+ */
15
+ createScaleVariables(key) {
16
+ const {
17
+ variables,
18
+ tokens
19
+ } = serializeTokens(this.#theme[key], key, this.#theme);
20
+ this.#theme = merge({}, this.#theme, {
21
+ [key]: tokens,
22
+ _variables: {
23
+ root: variables
24
+ },
25
+ _tokens: {
26
+ [key]: this.#theme[key]
27
+ }
28
+ });
29
+ return this;
30
+ }
31
+
32
+ /**
33
+ *
34
+ * @param colors A map of color tokens to add to the theme. These tokens are immediately converted to CSS Variables `--color-${key}`.
35
+ * @example .addColors({ navy: 'navy', hyper: 'purple' })
36
+ */
37
+ addColors(colors) {
38
+ const flatColors = flattenScale(colors);
39
+ const {
40
+ variables,
41
+ tokens
42
+ } = serializeTokens(flatColors, 'color', this.#theme);
43
+ this.#theme = merge({}, this.#theme, {
44
+ colors: tokens,
45
+ _variables: {
46
+ root: variables
47
+ },
48
+ _tokens: {
49
+ colors: flatColors
50
+ }
51
+ });
52
+ return this;
53
+ }
54
+
55
+ /**
56
+ *
57
+ * @param initialMode A key of the object passed for modes. This sets the default state for the theme and transforms the correct variables.
58
+ * @param modes A map of color modes with keys of each possible mode with a value of alias to color keys. This must be called after `addColors`
59
+ * @example .addColorModes('light', { light: { primary: 'hyper' }, { dark: { primary: 'navy' } } })
60
+ */
61
+ addColorModes(initialMode, modeConfig) {
62
+ const modes = mapValues(modeConfig, mode => flattenScale(mode));
63
+ const {
64
+ tokens: colors,
65
+ variables
66
+ } = serializeTokens(mapValues(merge({}, this.#theme.modes?.[initialMode], modes[initialMode]), color => this.#theme.colors[color]), 'color', this.#theme);
67
+ const getColorValue = color => this.#theme._tokens?.colors?.[color];
68
+ this.#theme = merge({}, this.#theme, {
69
+ colors,
70
+ modes,
71
+ mode: initialMode,
72
+ _getColorValue: getColorValue,
73
+ _variables: {
74
+ mode: variables
75
+ },
76
+ _tokens: {
77
+ modes: mapValues(modes, mode => mapValues(mode, getColorValue))
78
+ }
79
+ });
80
+ return this;
81
+ }
82
+
83
+ /**
84
+ *
85
+ * @param key A new key of theme
86
+ * @param createScale A function that accepts the current theme and returns a new object of scale values.
87
+ * @example .addScale('fonts', () => ({ basic: 'Gotham', cool: 'Wingdings' }))
88
+ */
89
+ addScale(key, createScale) {
90
+ this.#theme = merge({}, this.#theme, {
91
+ [key]: flattenScale(createScale(this.#theme))
92
+ });
93
+ return this;
94
+ }
95
+
96
+ /**
97
+ *
98
+ * @param key A current key of theme to be updated with new or computed values
99
+ * @param updateFn A function that accepts an argument of the current values at the specified keys an returns a map of new values to merge.
100
+ * @example .updateScale('fonts', ({ basic }) => ({ basicFallback: `{basic}, Montserrat` }))
101
+ */
102
+ updateScale(key, updateFn) {
103
+ this.#theme = merge({}, this.#theme, {
104
+ [key]: updateFn(this.#theme[key])
105
+ });
106
+ return this;
107
+ }
108
+
109
+ /**
110
+ * This finalizes the theme build and returns the final theme and variables to be provided.
111
+ */
112
+ build() {
113
+ return merge({}, this.#theme, {
114
+ _variables: {},
115
+ _tokens: {}
116
+ });
117
+ }
118
+ }
119
+ export function createTheme(base) {
120
+ return new ThemeBuilder(base);
121
+ }
@@ -0,0 +1,176 @@
1
+ import mapValues from 'lodash/mapValues';
2
+ import { createTheme } from './createTheme';
3
+ describe('createTheme', () => {
4
+ const base = {
5
+ breakpoints: {
6
+ xs: '1',
7
+ sm: '2',
8
+ md: '3',
9
+ lg: '4',
10
+ xl: '5'
11
+ }
12
+ };
13
+ it('works', () => {
14
+ expect(createTheme(base).build()).toEqual({
15
+ ...base,
16
+ _variables: {},
17
+ _tokens: {}
18
+ });
19
+ });
20
+ it('adds a scale', () => {
21
+ const theme = createTheme(base).addScale('test', () => ({
22
+ test: 1,
23
+ test2: 2
24
+ })).build();
25
+ expect(theme.test).toEqual({
26
+ test: 1,
27
+ test2: 2
28
+ });
29
+ });
30
+ it('updates a scale', () => {
31
+ const builder = createTheme(base).addScale('test', () => ({
32
+ test: 1,
33
+ test2: 2
34
+ }));
35
+ expect(builder.build().test).toEqual({
36
+ test: 1,
37
+ test2: 2
38
+ });
39
+ builder.updateScale('test', () => ({
40
+ test3: 3
41
+ }));
42
+ expect(builder.build().test).toEqual({
43
+ test: 1,
44
+ test2: 2,
45
+ test3: 3
46
+ });
47
+ });
48
+ it('serializes variables', () => {
49
+ const theme = createTheme(base).addScale('test', () => ({
50
+ test: 1,
51
+ test2: 2
52
+ })).createScaleVariables('test').build();
53
+ expect(theme.test).toEqual({
54
+ test: 'var(--test-test)',
55
+ test2: 'var(--test-test2)'
56
+ });
57
+ expect(theme._variables.root).toEqual({
58
+ '--test-test': 1,
59
+ '--test-test2': 2
60
+ });
61
+ });
62
+ describe('colors', () => {
63
+ const staticColors = {
64
+ white: 'white',
65
+ black: 'black',
66
+ blue: 'blue',
67
+ green: 'green',
68
+ red: 'red'
69
+ };
70
+ const cssVariableReferences = mapValues(staticColors, val => `var(--color-${val})`);
71
+ const builder = createTheme(base);
72
+ it('creates color variables', () => {
73
+ const theme = builder.addColors(staticColors).build();
74
+ expect(theme.colors).toEqual(cssVariableReferences);
75
+ });
76
+ it('adds colorModes', () => {
77
+ const theme = builder.addColors(staticColors).addColorModes('light', {
78
+ light: {
79
+ primary: 'red'
80
+ },
81
+ dark: {
82
+ primary: 'blue'
83
+ }
84
+ }).build();
85
+ expect(theme.colors).toEqual({
86
+ ...mapValues(staticColors, val => `var(--color-${val})`),
87
+ primary: 'var(--color-primary)'
88
+ });
89
+ expect(theme._variables.mode).toEqual({
90
+ '--color-primary': 'var(--color-red)'
91
+ });
92
+ });
93
+ it('returns value checker for colors', () => {
94
+ const theme = builder.addColors({
95
+ black: '#000000',
96
+ white: '#FFFFFF'
97
+ }).addColorModes('light', {
98
+ light: {
99
+ primary: 'black'
100
+ },
101
+ dark: {
102
+ primary: 'white'
103
+ }
104
+ }).build();
105
+ expect(theme._getColorValue('white')).toEqual('#FFFFFF');
106
+ expect(theme._getColorValue(theme.modes.light.primary)).toEqual('#000000');
107
+ });
108
+ it('returns value checker for colors with deep values', () => {
109
+ const theme = builder.addColors({
110
+ black: '#000000',
111
+ white: '#FFFFFF',
112
+ gray: {
113
+ 200: '#eeeeee',
114
+ 300: '#666666'
115
+ }
116
+ }).addColorModes('light', {
117
+ light: {
118
+ primary: {
119
+ default: 'gray-200',
120
+ cool: {
121
+ _: 'gray-300',
122
+ town: 'black'
123
+ }
124
+ }
125
+ }
126
+ }).build();
127
+ expect(theme._getColorValue('gray-300')).toEqual('#666666');
128
+ expect(theme._getColorValue(theme.modes.light['primary-default'])).toEqual('#eeeeee');
129
+ });
130
+ it('merges color mode configurations when overriden', () => {
131
+ const theme = builder.addColors({
132
+ black: '#000000',
133
+ white: '#FFFFFF'
134
+ }).addColorModes('light', {
135
+ light: {
136
+ primary: {
137
+ _: 'black',
138
+ hover: 'white'
139
+ }
140
+ }
141
+ }).build();
142
+ const override = createTheme(theme).addColorModes('light', {
143
+ light: {
144
+ primary: {
145
+ _: 'white',
146
+ hover: 'black'
147
+ }
148
+ }
149
+ }).build();
150
+ expect(override.modes.light.primary).toEqual('white');
151
+ });
152
+ it('returns the raw values of color mode colors on the tokens object', () => {
153
+ const theme = createTheme(base).addColors({
154
+ black: '#000000',
155
+ gray: {
156
+ 300: '#666666'
157
+ }
158
+ }).addColorModes('light', {
159
+ light: {
160
+ primary: {
161
+ cool: {
162
+ _: 'gray-300',
163
+ town: 'black'
164
+ }
165
+ }
166
+ }
167
+ }).build();
168
+ expect(theme._tokens.modes).toEqual({
169
+ light: {
170
+ 'primary-cool': '#666666',
171
+ 'primary-cool-town': '#000000'
172
+ }
173
+ });
174
+ });
175
+ });
176
+ });
@@ -0,0 +1,3 @@
1
+ export * from './createTheme';
2
+ export * from '../utils/serializeTokens';
3
+ export * from '../utils/flattenScale';
@@ -0,0 +1,3 @@
1
+ export * from './createTheme';
2
+ export * from '../utils/serializeTokens';
3
+ export * from '../utils/flattenScale';
@@ -0,0 +1,42 @@
1
+ import { CSSObject } from '../types/props';
2
+ import { AbstractTheme } from '../types/theme';
3
+ /**
4
+ * This is a custom generic that ensures the safety of adding additional values to a theme object without accidentally wiping out
5
+ * required keys like `breakpoints`. It works by creating a new mapped type and merging the values of the union of Base & Next:
6
+ * 1. If the key exists on both Base and Next return the intersection of both values
7
+ * 2. If the key exists on next use the value on next.
8
+ * 3. If the key exists on base but nothing else use the value on base.
9
+ *
10
+ * The resulting type is then rejoined with keys that cannot be mutated (breakpoints) as the next version of Theme
11
+ */
12
+ export type MergeTheme<Base extends AbstractTheme, Next, Unmergable = Record<'breakpoints', Base['breakpoints']>> = Unmergable & Merge<Base, Next>;
13
+ /** This merges at 2 levels of depth */
14
+ export type Merge<A, B> = {
15
+ [K in keyof (A & B)]: K extends keyof B ? K extends keyof A ? AssignValueIfUnmergable<A[K], B[K]> : B[K] : K extends keyof A ? A[K] : never;
16
+ };
17
+ /** Extract mergable objects */
18
+ export type Mergable<T> = Exclude<T, ((...args: any) => any) | string | boolean | symbol | number | any[]>;
19
+ /** Return B if either A or B is unmergable */
20
+ export type AssignValueIfUnmergable<A, B> = Mergable<A> extends never ? B : Mergable<B> extends never ? B : Assign<A, B>;
21
+ /** Prefer all values from B */
22
+ export type Assign<A, B> = {
23
+ [K in keyof A | keyof B]: K extends keyof B ? B[K] : K extends keyof A ? A[K] : never;
24
+ };
25
+ /** These are keys that are consistent for all theme builds - they are loosely typed as they are not meant to be accessed directly */
26
+ export type PrivateThemeKeys = {
27
+ _variables: Record<string, CSSObject>;
28
+ _tokens: Record<string | number, any>;
29
+ };
30
+ /** This allows 3 layers of color aliases to be constructed when adding colorModes
31
+ * @example
32
+ * {
33
+ * button: {
34
+ * bg: {
35
+ * hover: 'someAlias'
36
+ * }
37
+ * }
38
+ * }
39
+ *
40
+ * `button-bg-hover`
41
+ * */
42
+ export type ColorModeConfig<Colors> = Record<string, Colors | Record<string, Colors> | Record<string, Colors | Record<string, Colors>>>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ export { variance } from './core';
2
+ export * from './createTheme';
3
+ export * from './types/props';
4
+ export * from './transforms';
5
+ export * from './scales/createScale';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { variance } from './core';
2
+ export * from './createTheme';
3
+ export * from './types/props';
4
+ export * from './transforms';
5
+ export * from './scales/createScale';
@@ -0,0 +1,3 @@
1
+ export declare const createScale: <T extends string | number>() => readonly T[] & {
2
+ length: 0;
3
+ };
@@ -0,0 +1 @@
1
+ export const createScale = () => [];
@@ -0,0 +1,5 @@
1
+ import { Prop } from '../types/config';
2
+ import { ThemeProps } from '../types/props';
3
+ type GetScaleValue = (val: string | number, props: ThemeProps) => string | number | undefined;
4
+ export declare const createScaleLookup: (scale: Prop['scale']) => GetScaleValue;
5
+ export {};