@hero-design/rn 8.108.0 → 8.108.2

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.
@@ -1,13 +1,32 @@
1
+ import { UNSELECTED_TRACK_OPACITY } from '../../components/Slider/constants';
2
+ import { dimHex, hexToRgba } from '../../utils/helpers';
1
3
  import type { GlobalTheme } from '../global';
2
4
 
3
5
  const getSliderTheme = (theme: GlobalTheme) => {
4
6
  const colors = {
5
- minimumTrackTint: theme.colors.primary,
6
- thumbTint: theme.colors.primary,
7
- maximumTrackTint: theme.colors.highlightedSurface,
8
- trackBackground: theme.colors.highlightedSurface,
9
- disabledThumbTint: theme.colors.disabledOnDefaultGlobalSurface,
10
- disabledTrackBackground: theme.colors.neutralGlobalSurface,
7
+ // Unselected track colors
8
+ unselectedTrack: hexToRgba(
9
+ theme.colors.overlayGlobalSurface,
10
+ UNSELECTED_TRACK_OPACITY
11
+ ),
12
+ disabledUnselectedTrack: dimHex(
13
+ theme.colors.overlayGlobalSurface,
14
+ theme.colors.defaultGlobalSurface
15
+ ),
16
+
17
+ // Selected track colors
18
+ selectedTrack: theme.colors.primary,
19
+ disabledSelectedTrack: dimHex(
20
+ theme.colors.primary,
21
+ theme.colors.defaultGlobalSurface
22
+ ),
23
+
24
+ // Thumb/marker colors
25
+ thumb: theme.colors.primary,
26
+ disabledThumb: dimHex(
27
+ theme.colors.primary,
28
+ theme.colors.defaultGlobalSurface
29
+ ),
11
30
  };
12
31
 
13
32
  const shadows = {
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  deepCompareValue,
3
+ dimHex,
4
+ hexToRgba,
3
5
  omit,
4
6
  pick,
5
7
  transformKebabCaseToCamelCase,
@@ -114,3 +116,214 @@ describe('transformKebabCaseToCamelCase', () => {
114
116
  }
115
117
  );
116
118
  });
119
+
120
+ describe('hexToRgba', () => {
121
+ it.each`
122
+ hex | alpha | expected
123
+ ${'#FF0000'} | ${1} | ${'rgba(255,0,0,1)'}
124
+ ${'#00FF00'} | ${0.5} | ${'rgba(0,255,0,0.5)'}
125
+ ${'#0000FF'} | ${0} | ${'rgba(0,0,255,0)'}
126
+ ${'#FFFFFF'} | ${0.8} | ${'rgba(255,255,255,0.8)'}
127
+ ${'#000000'} | ${0.3} | ${'rgba(0,0,0,0.3)'}
128
+ ${'#123456'} | ${0.7} | ${'rgba(18,52,86,0.7)'}
129
+ ${'#ABCDEF'} | ${0.2} | ${'rgba(171,205,239,0.2)'}
130
+ ${'#FF0000'} | ${0.25} | ${'rgba(255,0,0,0.25)'}
131
+ `(
132
+ 'should convert hex "$hex" with alpha $alpha to "$expected"',
133
+ ({ hex, alpha, expected }) => {
134
+ expect(hexToRgba(hex, alpha)).toBe(expected);
135
+ }
136
+ );
137
+
138
+ it.each`
139
+ hex | alpha | expected
140
+ ${'FF0000'} | ${1} | ${'rgba(255,0,0,1)'}
141
+ ${'00FF00'} | ${0.5} | ${'rgba(0,255,0,0.5)'}
142
+ ${'0000FF'} | ${0} | ${'rgba(0,0,255,0)'}
143
+ ${'FFFFFF'} | ${0.8} | ${'rgba(255,255,255,0.8)'}
144
+ ${'000000'} | ${0.3} | ${'rgba(0,0,0,0.3)'}
145
+ ${'123456'} | ${0.7} | ${'rgba(18,52,86,0.7)'}
146
+ ${'ABCDEF'} | ${0.2} | ${'rgba(171,205,239,0.2)'}
147
+ `(
148
+ 'should convert hex without # "$hex" with alpha $alpha to "$expected"',
149
+ ({ hex, alpha, expected }) => {
150
+ expect(hexToRgba(hex, alpha)).toBe(expected);
151
+ }
152
+ );
153
+
154
+ it('should handle lowercase hex values', () => {
155
+ expect(hexToRgba('#ff0000', 1)).toBe('rgba(255,0,0,1)');
156
+ expect(hexToRgba('#abcdef', 0.5)).toBe('rgba(171,205,239,0.5)');
157
+ });
158
+
159
+ it('should handle mixed case hex values', () => {
160
+ expect(hexToRgba('#Ff0000', 1)).toBe('rgba(255,0,0,1)');
161
+ expect(hexToRgba('#AbCdEf', 0.5)).toBe('rgba(171,205,239,0.5)');
162
+ });
163
+
164
+ it('should handle edge case alpha values', () => {
165
+ expect(hexToRgba('#000000', 0)).toBe('rgba(0,0,0,0)');
166
+ expect(hexToRgba('#FFFFFF', 1)).toBe('rgba(255,255,255,1)');
167
+ });
168
+
169
+ it('should handle decimal alpha values', () => {
170
+ expect(hexToRgba('#FF0000', 0.123)).toBe('rgba(255,0,0,0.123)');
171
+ expect(hexToRgba('#00FF00', 0.999)).toBe('rgba(0,255,0,0.999)');
172
+ });
173
+
174
+ describe('3-character hex codes', () => {
175
+ it.each`
176
+ hex | alpha | expected
177
+ ${'#fff'} | ${1} | ${'rgba(255,255,255,1)'}
178
+ ${'#000'} | ${0.5} | ${'rgba(0,0,0,0.5)'}
179
+ ${'#f00'} | ${0.8} | ${'rgba(255,0,0,0.8)'}
180
+ ${'#0f0'} | ${0.3} | ${'rgba(0,255,0,0.3)'}
181
+ ${'#00f'} | ${0.7} | ${'rgba(0,0,255,0.7)'}
182
+ ${'#abc'} | ${0.2} | ${'rgba(170,187,204,0.2)'}
183
+ ${'#123'} | ${0.25} | ${'rgba(17,34,51,0.25)'}
184
+ `(
185
+ 'should convert 3-char hex "$hex" with alpha $alpha to "$expected"',
186
+ ({ hex, alpha, expected }) => {
187
+ expect(hexToRgba(hex, alpha)).toBe(expected);
188
+ }
189
+ );
190
+
191
+ it.each`
192
+ hex | alpha | expected
193
+ ${'fff'} | ${1} | ${'rgba(255,255,255,1)'}
194
+ ${'000'} | ${0.5} | ${'rgba(0,0,0,0.5)'}
195
+ ${'f00'} | ${0.8} | ${'rgba(255,0,0,0.8)'}
196
+ ${'0f0'} | ${0.3} | ${'rgba(0,255,0,0.3)'}
197
+ ${'00f'} | ${0.7} | ${'rgba(0,0,255,0.7)'}
198
+ ${'abc'} | ${0.2} | ${'rgba(170,187,204,0.2)'}
199
+ `(
200
+ 'should convert 3-char hex without # "$hex" with alpha $alpha to "$expected"',
201
+ ({ hex, alpha, expected }) => {
202
+ expect(hexToRgba(hex, alpha)).toBe(expected);
203
+ }
204
+ );
205
+
206
+ it('should handle lowercase 3-char hex values', () => {
207
+ expect(hexToRgba('#fff', 1)).toBe('rgba(255,255,255,1)');
208
+ expect(hexToRgba('#abc', 0.5)).toBe('rgba(170,187,204,0.5)');
209
+ });
210
+
211
+ it('should handle mixed case 3-char hex values', () => {
212
+ expect(hexToRgba('#Ff0', 1)).toBe('rgba(255,255,0,1)');
213
+ expect(hexToRgba('#AbC', 0.5)).toBe('rgba(170,187,204,0.5)');
214
+ });
215
+ });
216
+
217
+ describe('validation', () => {
218
+ it('should throw error for invalid input types', () => {
219
+ expect(() => hexToRgba(123 as any, 0.5)).toThrow(
220
+ 'hexToRgba: hex must be a string and alpha must be a number'
221
+ );
222
+ expect(() => hexToRgba('#fff', '0.5' as any)).toThrow(
223
+ 'hexToRgba: hex must be a string and alpha must be a number'
224
+ );
225
+ });
226
+
227
+ it('should throw error for alpha values outside 0-1 range', () => {
228
+ expect(() => hexToRgba('#fff', -0.1)).toThrow(
229
+ 'hexToRgba: alpha must be between 0 and 1'
230
+ );
231
+ expect(() => hexToRgba('#fff', 1.1)).toThrow(
232
+ 'hexToRgba: alpha must be between 0 and 1'
233
+ );
234
+ });
235
+
236
+ it('should throw error for invalid hex formats', () => {
237
+ expect(() => hexToRgba('invalid', 0.5)).toThrow(
238
+ 'hexToRgba: hex must be a valid 3 or 6 character hex color'
239
+ );
240
+ expect(() => hexToRgba('#gggggg', 0.5)).toThrow(
241
+ 'hexToRgba: hex must be a valid 3 or 6 character hex color'
242
+ );
243
+ expect(() => hexToRgba('#ff', 0.5)).toThrow(
244
+ 'hexToRgba: hex must be a valid 3 or 6 character hex color'
245
+ );
246
+ expect(() => hexToRgba('#ffffff00', 0.5)).toThrow(
247
+ 'hexToRgba: hex must be a valid 3 or 6 character hex color'
248
+ );
249
+ expect(() => hexToRgba('', 0.5)).toThrow(
250
+ 'hexToRgba: hex must be a valid 3 or 6 character hex color'
251
+ );
252
+ });
253
+
254
+ it('should accept valid alpha boundary values', () => {
255
+ expect(() => hexToRgba('#fff', 0)).not.toThrow();
256
+ expect(() => hexToRgba('#fff', 1)).not.toThrow();
257
+ });
258
+ });
259
+ });
260
+
261
+ describe('dimHex', () => {
262
+ it.each`
263
+ hex | surface | amount | expected
264
+ ${'#FF0000'} | ${'#FFFFFF'} | ${0.5} | ${'#ff8080'}
265
+ ${'#000000'} | ${'#FFFFFF'} | ${0.5} | ${'#808080'}
266
+ ${'#FF0000'} | ${'#FFFFFF'} | ${0} | ${'#ff0000'}
267
+ ${'#FF0000'} | ${'#FFFFFF'} | ${1} | ${'#ffffff'}
268
+ ${'FF0000'} | ${'FFFFFF'} | ${0.5} | ${'#ff8080'}
269
+ ${'#fff'} | ${'#000'} | ${0.5} | ${'#808080'}
270
+ ${'fff'} | ${'000'} | ${0.5} | ${'#808080'}
271
+ `(
272
+ 'should dim hex "$hex" with surface "$surface" by amount $amount to "$expected"',
273
+ ({ hex, surface, amount, expected }) => {
274
+ expect(dimHex(hex, surface, amount)).toBe(expected);
275
+ }
276
+ );
277
+
278
+ it('should use default parameters', () => {
279
+ expect(dimHex('#000000', '#ffffff')).toBe('#999999');
280
+ });
281
+
282
+ it('should throw error for invalid input types', () => {
283
+ // @ts-expect-error: Invalid param type
284
+ expect(() => dimHex(123, '#fff', 0.5)).toThrow(
285
+ 'dimHex: hex and surface must be strings and amount must be a number'
286
+ );
287
+ // @ts-expect-error: Invalid param type
288
+ expect(() => dimHex('#fff', 123, 0.5)).toThrow(
289
+ 'dimHex: hex and surface must be strings and amount must be a number'
290
+ );
291
+ // @ts-expect-error: Invalid param type
292
+ expect(() => dimHex('#fff', '#fff', '0.5')).toThrow(
293
+ 'dimHex: hex and surface must be strings and amount must be a number'
294
+ );
295
+ });
296
+
297
+ it('should throw error for amount values outside 0-1 range', () => {
298
+ expect(() => dimHex('#fff', '#fff', -0.1)).toThrow(
299
+ 'dimHex: amount must be between 0 and 1'
300
+ );
301
+ expect(() => dimHex('#fff', '#fff', 1.1)).toThrow(
302
+ 'dimHex: amount must be between 0 and 1'
303
+ );
304
+ });
305
+
306
+ it('should throw error for invalid hex formats', () => {
307
+ expect(() => dimHex('invalid', '#fff', 0.5)).toThrow(
308
+ 'dimHex: hex must be a valid 3 or 6 character hex color'
309
+ );
310
+ expect(() => dimHex('#gggggg', '#fff', 0.5)).toThrow(
311
+ 'dimHex: hex must be a valid 3 or 6 character hex color'
312
+ );
313
+ expect(() => dimHex('#ff', '#fff', 0.5)).toThrow(
314
+ 'dimHex: hex must be a valid 3 or 6 character hex color'
315
+ );
316
+ });
317
+
318
+ it('should throw error for invalid surface formats', () => {
319
+ expect(() => dimHex('#fff', 'invalid', 0.5)).toThrow(
320
+ 'dimHex: surface must be a valid 3 or 6 character hex color'
321
+ );
322
+ expect(() => dimHex('#fff', '#gggggg', 0.5)).toThrow(
323
+ 'dimHex: surface must be a valid 3 or 6 character hex color'
324
+ );
325
+ expect(() => dimHex('#fff', '#ff', 0.5)).toThrow(
326
+ 'dimHex: surface must be a valid 3 or 6 character hex color'
327
+ );
328
+ });
329
+ });
@@ -31,14 +31,122 @@ export function omit<O, T extends keyof O>(keys: T[], obj: O): Omit<O, T> {
31
31
  }
32
32
 
33
33
  export function hexToRgba(hex: string, a: number) {
34
- const arrBuff = new ArrayBuffer(4);
35
- const vw = new DataView(arrBuff);
36
- vw.setUint32(0, parseInt(hex, 16), false);
37
- const arrByte = new Uint8Array(arrBuff);
38
- const [r, g, b] = arrByte;
34
+ // Validate inputs
35
+ if (typeof hex !== 'string' || typeof a !== 'number') {
36
+ throw new Error(
37
+ 'hexToRgba: hex must be a string and alpha must be a number'
38
+ );
39
+ }
40
+
41
+ if (a < 0 || a > 1) {
42
+ throw new Error('hexToRgba: alpha must be between 0 and 1');
43
+ }
44
+
45
+ // Remove # if present and validate hex format
46
+ const cleanHex = hex.replace(/^#/, '');
47
+
48
+ if (!/^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(cleanHex)) {
49
+ throw new Error(
50
+ 'hexToRgba: hex must be a valid 3 or 6 character hex color'
51
+ );
52
+ }
53
+
54
+ // Handle 3-character hex codes (e.g., #fff -> #ffffff)
55
+ const normalizedHex =
56
+ cleanHex.length === 3
57
+ ? cleanHex
58
+ .split('')
59
+ .map((char) => char + char)
60
+ .join('')
61
+ : cleanHex;
62
+
63
+ // Parse hex values
64
+ const r = parseInt(normalizedHex.substring(0, 2), 16);
65
+ const g = parseInt(normalizedHex.substring(2, 4), 16);
66
+ const b = parseInt(normalizedHex.substring(4, 6), 16);
67
+
39
68
  return `rgba(${r},${g},${b},${a})`;
40
69
  }
41
70
 
71
+ /**
72
+ * Dim a hex color by blending it with a surface color
73
+ * @param hex - The hex color to dim (3 or 6 characters, with or without #)
74
+ * @param surface - The surface color to blend with.
75
+ * @param amount - The amount of dimming (0 = original color, 1 = fully surface color)
76
+ * @returns The dimmed hex color
77
+ */
78
+ const DEFAULT_DIM_AMOUNT = 0.6;
79
+ export function dimHex(
80
+ hex: string,
81
+ surface: string,
82
+ amount = DEFAULT_DIM_AMOUNT
83
+ ): string {
84
+ // Validate inputs
85
+ if (
86
+ typeof hex !== 'string' ||
87
+ typeof surface !== 'string' ||
88
+ typeof amount !== 'number'
89
+ ) {
90
+ throw new Error(
91
+ 'dimHex: hex and surface must be strings and amount must be a number'
92
+ );
93
+ }
94
+
95
+ if (amount < 0 || amount > 1) {
96
+ throw new Error('dimHex: amount must be between 0 and 1');
97
+ }
98
+
99
+ // Remove # if present and validate hex format
100
+ const cleanHex = hex.replace(/^#/, '');
101
+ const cleanSurface = surface.replace(/^#/, '');
102
+
103
+ if (!/^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(cleanHex)) {
104
+ throw new Error('dimHex: hex must be a valid 3 or 6 character hex color');
105
+ }
106
+
107
+ if (!/^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(cleanSurface)) {
108
+ throw new Error(
109
+ 'dimHex: surface must be a valid 3 or 6 character hex color'
110
+ );
111
+ }
112
+
113
+ // Handle 3-character hex codes (e.g., #fff -> #ffffff)
114
+ const normalizedHex =
115
+ cleanHex.length === 3
116
+ ? cleanHex
117
+ .split('')
118
+ .map((char) => char + char)
119
+ .join('')
120
+ : cleanHex;
121
+
122
+ const normalizedSurface =
123
+ cleanSurface.length === 3
124
+ ? cleanSurface
125
+ .split('')
126
+ .map((char) => char + char)
127
+ .join('')
128
+ : cleanSurface;
129
+
130
+ // Parse hex values
131
+ const r = parseInt(normalizedHex.substring(0, 2), 16);
132
+ const g = parseInt(normalizedHex.substring(2, 4), 16);
133
+ const b = parseInt(normalizedHex.substring(4, 6), 16);
134
+
135
+ const sr = parseInt(normalizedSurface.substring(0, 2), 16);
136
+ const sg = parseInt(normalizedSurface.substring(2, 4), 16);
137
+ const sb = parseInt(normalizedSurface.substring(4, 6), 16);
138
+
139
+ // Linear interpolation between colors
140
+ const lerp = (c: number, s: number): number =>
141
+ Math.round(c * (1 - amount) + s * amount);
142
+
143
+ const rr = lerp(r, sr).toString(16).padStart(2, '0');
144
+ const gg = lerp(g, sg).toString(16).padStart(2, '0');
145
+ const bb = lerp(b, sb).toString(16).padStart(2, '0');
146
+
147
+ return `#${rr}${gg}${bb}`;
148
+ }
149
+
42
150
  export const deepCompareValue = <V>(a: V, b: V): boolean => {
43
151
  // Handle strict equality first (handles primitives, null, undefined)
44
152
  if (a === b) return true;