• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zendeskgarden / react-components / 6e5f2891-3ea4-4289-bc30-25451585b0ff

25 Mar 2024 12:53PM UTC coverage: 95.187%. Remained the same
6e5f2891-3ea4-4289-bc30-25451585b0ff

push

circleci

jzempel
Move theme `variables` under `colors`

3244 of 3647 branches covered (88.95%)

Branch coverage included in aggregate %.

0 of 4 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

10007 of 10274 relevant lines covered (97.4%)

214.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

10.0
/packages/theming/src/utils/getColor.ts
1
/**
2
 * Copyright Zendesk, Inc.
3
 *
4
 * Use of this source code is governed under the Apache License, Version 2.0
5
 * found at http://www.apache.org/licenses/LICENSE-2.0.
6
 */
7

8
import { scale, valid } from 'chroma-js';
333✔
9
import { darken, lighten, rgba } from 'polished';
333✔
10
import get from 'lodash.get';
333✔
11
import memoize from 'lodash.memoize';
333✔
12
import DEFAULT_THEME from '../elements/theme';
333✔
13
import PALETTE from '../elements/palette';
333✔
14
import { Hue, IGardenTheme } from '../types';
15

16
const PALETTE_SIZE = Object.keys(PALETTE.blue).length;
333✔
17

18
const adjust = (color: string, expected: number, actual: number) => {
333✔
19
  if (expected !== actual) {
×
20
    // Adjust darkness/lightness if color is not the expected shade
21
    const amount = (Math.abs(expected - actual) / 100) * 0.05;
×
22

23
    return expected > actual ? darken(amount, color) : lighten(amount, color);
×
24
  }
25

26
  return color;
×
27
};
28

29
/* convert the optional shade + offset to a shade for the given scheme */
30
const toShade = (shade?: number | string, offset?: number, scheme?: 'dark' | 'light') => {
333✔
31
  let _shade;
32

33
  if (shade === undefined) {
×
34
    _shade = scheme === 'dark' ? 500 : 700;
×
35
  } else {
36
    _shade = parseInt(shade.toString(), 10);
×
37

38
    if (isNaN(_shade)) {
×
39
      throw new TypeError(`Error: unexpected '${typeof shade}' type for color shade "${shade}"`);
×
40
    }
41
  }
42

43
  return _shade + (offset || 0);
×
44
};
45

46
/* convert the given hue object to a hex color */
47
const toHex = (
333✔
48
  hue: Record<string | number, string>,
49
  shade?: number | string,
50
  offset?: number,
51
  scheme?: 'dark' | 'light'
52
) => {
53
  const _shade = toShade(shade, offset, scheme);
×
54
  let retVal = hue[_shade];
×
55

56
  if (!retVal) {
×
57
    const closestShade = Object.keys(hue)
×
58
      .map(hueShade => parseInt(hueShade, 10))
×
59
      .reduce((previous, current) => {
60
        // Find the closest available shade within the given hue
61
        return Math.abs(current - _shade) < Math.abs(previous - _shade) ? current : previous;
×
62
      });
63

64
    retVal = adjust(hue[closestShade], _shade, closestShade);
×
65
  }
66

67
  return retVal;
×
68
};
69

70
/* convert the given hue + shade to a color */
71
const toColor = (
333✔
72
  colors: Omit<IGardenTheme['colors'], 'base' | 'variables'>,
73
  palette: IGardenTheme['palette'],
74
  scheme: 'dark' | 'light',
75
  hue: string,
76
  shade?: number | string,
77
  offset?: number,
78
  transparency?: number
79
) => {
80
  let retVal;
81
  let _hue: Hue =
82
    colors[hue as keyof typeof colors] /* ex. `hue` = 'primaryHue' */ ||
×
83
    hue; /* ex. `hue` = '#fd5a1e' */
84

85
  if (Object.hasOwn(palette, _hue)) {
×
86
    _hue = palette[_hue]; /* ex. `hue` = 'grey' */
×
87
  }
88

89
  if (typeof _hue === 'object') {
×
90
    retVal = toHex(_hue, shade, offset, scheme);
×
91
  } else if (valid(_hue)) {
×
92
    if (shade === undefined) {
×
93
      retVal = _hue;
×
94
    } else {
95
      const _colors = scale([PALETTE.white, _hue, PALETTE.black])
×
96
        .correctLightness()
97
        .colors(PALETTE_SIZE + 2);
98

99
      _hue = _colors.reduce<Record<number, string>>((_retVal, color, index) => {
×
100
        if (index > 0 && index <= PALETTE_SIZE) {
×
101
          _retVal[index * 100] = color;
×
102
        }
103

104
        return _retVal;
×
105
      }, {});
106

107
      retVal = toHex(_hue, shade, offset, scheme);
×
108
    }
109
  }
110

111
  if (retVal && transparency) {
×
112
    retVal = rgba(retVal, transparency);
×
113
  }
114

115
  return retVal;
×
116
};
117

118
/* convert the given object + path to a string value */
119
const toProperty = (object: object, path: string) => {
333✔
120
  const retVal = get(object, path);
×
121

122
  if (typeof retVal === 'string') {
×
123
    return retVal;
×
124
  } else if (retVal === undefined) {
×
125
    throw new ReferenceError(`Error: color variable "${path}" is not defined`);
×
126
  } else {
127
    throw new TypeError(`Error: unexpected '${typeof retVal}' type for color variable "${path}"`);
×
128
  }
129
};
130

131
type ColorParameters = {
132
  dark?: {
133
    hue?: string;
134
    offset?: number;
135
    shade?: number;
136
    transparency?: number;
137
  };
138
  hue?: string;
139
  light?: {
140
    hue?: string;
141
    offset?: number;
142
    shade?: number;
143
    transparency?: number;
144
  };
145
  offset?: number;
146
  shade?: number;
147
  theme: IGardenTheme;
148
  transparency?: number;
149
  variable?: string;
150
};
151

152
export const getColor = memoize(
333✔
153
  ({ dark, hue, light, offset, shade, theme, transparency, variable }: ColorParameters) => {
154
    let retVal;
155

156
    // bulletproof object references for potential non-typed usage
157
    const palette = theme.palette ? theme.palette : DEFAULT_THEME.palette;
×
NEW
158
    const { base, variables, ...colors } = theme.colors ? theme.colors : DEFAULT_THEME.colors;
×
NEW
159
    const scheme = base === 'dark' ? 'dark' : 'light';
×
160
    const mode = (scheme === 'dark' ? dark : light)!;
×
161
    let _hue = mode?.hue || hue;
×
162
    let _shade = mode?.shade || shade;
×
163
    const _offset = mode?.offset || offset;
×
164
    const _transparency = mode?.transparency || transparency;
×
165

166
    if (variable) {
×
167
      // variable lookup takes precedence
NEW
168
      const _variables = variables?.[scheme]
×
169
        ? variables[scheme]
170
        : DEFAULT_THEME.colors.variables[scheme];
NEW
171
      const property = toProperty(_variables, variable);
×
UNCOV
172
      const [key, value] = property.split(/\.(?<value>.*)/u);
×
173

174
      if (key === 'palette') {
×
175
        _hue = toProperty(palette, value); /* ex. `variable` = 'palette.white' */
×
176
      } else {
177
        _hue = key;
×
178
        _shade = parseInt(value, 10);
×
179
      }
180
    }
181

182
    if (_hue) {
×
183
      retVal = toColor(colors, palette, scheme, _hue, _shade, _offset, _transparency);
×
184
    }
185

186
    if (retVal === undefined) {
×
187
      throw new Error('Error: invalid `getColor` parameters');
×
188
    }
189

190
    return retVal;
×
191
  },
192
  ({ dark, hue, light, offset, shade, theme, transparency, variable }) =>
193
    JSON.stringify({
×
194
      dark,
195
      hue,
196
      light,
197
      offset,
198
      shade,
199
      colors: theme.colors,
200
      palette: theme.palette,
201
      transparency,
202
      variable
203
    })
204
);
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc