@ledgerhq/lumen-ui-rnative 0.0.52 → 0.0.53
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.
- package/dist/package.json +2 -2
- package/dist/src/lib/Components/BaseInput/BaseInput.d.ts +1 -0
- package/dist/src/lib/Components/BaseInput/BaseInput.d.ts.map +1 -1
- package/dist/src/lib/Components/BaseInput/BaseInput.js +6 -2
- package/dist/src/lib/Components/BaseInput/types.d.ts +5 -1
- package/dist/src/lib/Components/BaseInput/types.d.ts.map +1 -1
- package/dist/src/lib/Components/SearchInput/SearchInput.d.ts +3 -1
- package/dist/src/lib/Components/SearchInput/SearchInput.d.ts.map +1 -1
- package/dist/src/lib/Components/SearchInput/SearchInput.js +12 -2
- package/dist/src/lib/Components/SearchInput/SearchInput.stories.d.ts.map +1 -1
- package/dist/src/lib/Components/SearchInput/SearchInput.stories.js +3 -0
- package/dist/src/lib/Components/SearchInput/types.d.ts +7 -1
- package/dist/src/lib/Components/SearchInput/types.d.ts.map +1 -1
- package/dist/src/lib/Components/Tile/Tile.d.ts.map +1 -1
- package/dist/src/lib/Components/Tile/Tile.js +1 -0
- package/dist/src/lib/Components/Utility/Gradient/LinearGradient/LinearGradient.d.ts +32 -0
- package/dist/src/lib/Components/Utility/Gradient/LinearGradient/LinearGradient.d.ts.map +1 -0
- package/dist/src/lib/Components/Utility/Gradient/LinearGradient/LinearGradient.js +75 -0
- package/dist/src/lib/Components/Utility/Gradient/LinearGradient/LinearGradient.stories.d.ts +11 -0
- package/dist/src/lib/Components/Utility/Gradient/LinearGradient/LinearGradient.stories.d.ts.map +1 -0
- package/dist/src/lib/Components/Utility/Gradient/LinearGradient/LinearGradient.stories.js +107 -0
- package/dist/src/lib/Components/Utility/Gradient/LinearGradient/index.d.ts +3 -0
- package/dist/src/lib/Components/Utility/Gradient/LinearGradient/index.d.ts.map +1 -0
- package/dist/src/lib/Components/Utility/Gradient/LinearGradient/index.js +2 -0
- package/dist/src/lib/Components/Utility/Gradient/LinearGradient/types.d.ts +32 -0
- package/dist/src/lib/Components/Utility/Gradient/LinearGradient/types.d.ts.map +1 -0
- package/dist/src/lib/Components/Utility/Gradient/LinearGradient/types.js +1 -0
- package/dist/src/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.d.ts +31 -0
- package/dist/src/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.d.ts.map +1 -0
- package/dist/src/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.js +52 -0
- package/dist/src/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.stories.d.ts +10 -0
- package/dist/src/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.stories.d.ts.map +1 -0
- package/dist/src/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.stories.js +101 -0
- package/dist/src/lib/Components/Utility/Gradient/RadialGradient/index.d.ts +3 -0
- package/dist/src/lib/Components/Utility/Gradient/RadialGradient/index.d.ts.map +1 -0
- package/dist/src/lib/Components/Utility/Gradient/RadialGradient/index.js +2 -0
- package/dist/src/lib/Components/Utility/Gradient/RadialGradient/types.d.ts +32 -0
- package/dist/src/lib/Components/Utility/Gradient/RadialGradient/types.d.ts.map +1 -0
- package/dist/src/lib/Components/Utility/Gradient/RadialGradient/types.js +1 -0
- package/dist/src/lib/Components/Utility/Gradient/gradient.types.d.ts +21 -0
- package/dist/src/lib/Components/Utility/Gradient/gradient.types.d.ts.map +1 -0
- package/dist/src/lib/Components/Utility/Gradient/gradient.types.js +1 -0
- package/dist/src/lib/Components/Utility/Gradient/utils/resolveGradientColor.d.ts +21 -0
- package/dist/src/lib/Components/Utility/Gradient/utils/resolveGradientColor.d.ts.map +1 -0
- package/dist/src/lib/Components/Utility/Gradient/utils/resolveGradientColor.js +38 -0
- package/dist/src/lib/Components/Utility/index.d.ts +2 -0
- package/dist/src/lib/Components/Utility/index.d.ts.map +1 -1
- package/dist/src/lib/Components/Utility/index.js +2 -0
- package/package.json +2 -2
- package/src/lib/Components/BaseInput/BaseInput.tsx +7 -2
- package/src/lib/Components/BaseInput/types.ts +5 -1
- package/src/lib/Components/SearchInput/SearchInput.stories.tsx +3 -0
- package/src/lib/Components/SearchInput/SearchInput.tsx +32 -8
- package/src/lib/Components/SearchInput/types.ts +7 -1
- package/src/lib/Components/Tile/Tile.tsx +1 -0
- package/src/lib/Components/Utility/Gradient/LinearGradient/LinearGradient.mdx +142 -0
- package/src/lib/Components/Utility/Gradient/LinearGradient/LinearGradient.stories.tsx +173 -0
- package/src/lib/Components/Utility/Gradient/LinearGradient/LinearGradient.test.tsx +69 -0
- package/src/lib/Components/Utility/Gradient/LinearGradient/LinearGradient.tsx +127 -0
- package/src/lib/Components/Utility/Gradient/LinearGradient/index.ts +2 -0
- package/src/lib/Components/Utility/Gradient/LinearGradient/types.ts +42 -0
- package/src/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.mdx +109 -0
- package/src/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.stories.tsx +148 -0
- package/src/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.test.tsx +69 -0
- package/src/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.tsx +102 -0
- package/src/lib/Components/Utility/Gradient/RadialGradient/index.ts +2 -0
- package/src/lib/Components/Utility/Gradient/RadialGradient/types.ts +32 -0
- package/src/lib/Components/Utility/Gradient/gradient.types.ts +22 -0
- package/src/lib/Components/Utility/Gradient/utils/resolveGradientColor.test.ts +144 -0
- package/src/lib/Components/Utility/Gradient/utils/resolveGradientColor.ts +59 -0
- package/src/lib/Components/Utility/index.ts +2 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
|
|
3
|
+
import { render, screen } from '@testing-library/react-native';
|
|
4
|
+
import React, { createRef } from 'react';
|
|
5
|
+
import { Text, View } from 'react-native';
|
|
6
|
+
|
|
7
|
+
import { ThemeProvider } from '../../../ThemeProvider/ThemeProvider';
|
|
8
|
+
import { RadialGradient } from './RadialGradient';
|
|
9
|
+
|
|
10
|
+
const renderWithProvider = (component: React.ReactElement) => {
|
|
11
|
+
return render(
|
|
12
|
+
<ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
|
|
13
|
+
{component}
|
|
14
|
+
</ThemeProvider>,
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('RadialGradient', () => {
|
|
19
|
+
it('should have correct display name', () => {
|
|
20
|
+
expect(RadialGradient.displayName).toBe('RadialGradient');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should render children', () => {
|
|
24
|
+
renderWithProvider(
|
|
25
|
+
<RadialGradient stops={[{ color: '#FF6B6B' }, { color: '#4ECDC4' }]}>
|
|
26
|
+
<Text>Child Content</Text>
|
|
27
|
+
</RadialGradient>,
|
|
28
|
+
);
|
|
29
|
+
expect(screen.getByText('Child Content')).toBeTruthy();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should forward ref', () => {
|
|
33
|
+
const ref = createRef<View>();
|
|
34
|
+
renderWithProvider(
|
|
35
|
+
<RadialGradient
|
|
36
|
+
ref={ref}
|
|
37
|
+
stops={[{ color: '#FF6B6B' }, { color: '#4ECDC4' }]}
|
|
38
|
+
/>,
|
|
39
|
+
);
|
|
40
|
+
expect(ref.current).toBeTruthy();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should resolve lx style tokens', () => {
|
|
44
|
+
renderWithProvider(
|
|
45
|
+
<RadialGradient
|
|
46
|
+
testID='radial-gradient'
|
|
47
|
+
stops={[{ color: '#FF6B6B' }, { color: '#4ECDC4' }]}
|
|
48
|
+
lx={{ padding: 's16', marginTop: 's8' }}
|
|
49
|
+
/>,
|
|
50
|
+
);
|
|
51
|
+
const gradient = screen.getByTestId('radial-gradient');
|
|
52
|
+
expect(gradient.props.style.padding).toBe(16);
|
|
53
|
+
expect(gradient.props.style.marginTop).toBe(8);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should pass accessibility props', () => {
|
|
57
|
+
renderWithProvider(
|
|
58
|
+
<RadialGradient
|
|
59
|
+
testID='radial-gradient'
|
|
60
|
+
stops={[{ color: '#FF6B6B' }, { color: '#4ECDC4' }]}
|
|
61
|
+
accessibilityLabel='Decorative gradient'
|
|
62
|
+
accessible={false}
|
|
63
|
+
/>,
|
|
64
|
+
);
|
|
65
|
+
const gradient = screen.getByTestId('radial-gradient');
|
|
66
|
+
expect(gradient.props.accessibilityLabel).toBe('Decorative gradient');
|
|
67
|
+
expect(gradient.props.accessible).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { forwardRef, memo, useId, useMemo } from 'react';
|
|
2
|
+
import { StyleSheet } from 'react-native';
|
|
3
|
+
import type { View } from 'react-native';
|
|
4
|
+
import Svg, {
|
|
5
|
+
Defs,
|
|
6
|
+
RadialGradient as SvgRadialGradient,
|
|
7
|
+
Rect,
|
|
8
|
+
Stop,
|
|
9
|
+
} from 'react-native-svg';
|
|
10
|
+
import { useTheme } from '../../../../../styles';
|
|
11
|
+
import { Box } from '../../Box';
|
|
12
|
+
import { processGradientStops } from '../utils/resolveGradientColor';
|
|
13
|
+
import type { RadialGradientProps } from './types';
|
|
14
|
+
|
|
15
|
+
const DEFAULT_CENTER = { x: 0.5, y: 0.5 };
|
|
16
|
+
|
|
17
|
+
const styles = StyleSheet.create({
|
|
18
|
+
gradient: {
|
|
19
|
+
...StyleSheet.absoluteFillObject,
|
|
20
|
+
width: '100%',
|
|
21
|
+
height: '100%',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* RadialGradient - A container component that renders a radial gradient background.
|
|
27
|
+
*
|
|
28
|
+
* Uses react-native-svg to render gradients that work consistently across platforms.
|
|
29
|
+
* Extends Box, so it supports all lx style props for layout and sizing.
|
|
30
|
+
*
|
|
31
|
+
* @see {@link https://ldls.vercel.app/?path=/docs/utility-radialgradient--docs Storybook}
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```tsx
|
|
35
|
+
* import { RadialGradient } from '@ledgerhq/lumen-ui-rnative';
|
|
36
|
+
*
|
|
37
|
+
* // Basic usage (center to edge)
|
|
38
|
+
* <RadialGradient
|
|
39
|
+
* lx={{ height: 's200', borderRadius: 'lg' }}
|
|
40
|
+
* stops={[
|
|
41
|
+
* { color: '#845EC2', offset: 0 },
|
|
42
|
+
* { color: '#FF6F91', offset: 1 },
|
|
43
|
+
* ]}
|
|
44
|
+
* />
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export const RadialGradient = memo(
|
|
48
|
+
forwardRef<View, RadialGradientProps>(
|
|
49
|
+
({ center = DEFAULT_CENTER, stops, children, lx = {}, ...props }, ref) => {
|
|
50
|
+
const gradientId = useId();
|
|
51
|
+
const { theme } = useTheme();
|
|
52
|
+
|
|
53
|
+
const processedStops = useMemo(() => {
|
|
54
|
+
return processGradientStops(stops, theme.colors.bg);
|
|
55
|
+
}, [stops, theme.colors.bg]);
|
|
56
|
+
|
|
57
|
+
const centerCoordinates = useMemo(() => {
|
|
58
|
+
return {
|
|
59
|
+
cx: `${center.x * 100}%`,
|
|
60
|
+
cy: `${center.y * 100}%`,
|
|
61
|
+
};
|
|
62
|
+
}, [center]);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Box ref={ref} lx={{ overflow: 'hidden', ...lx }} {...props}>
|
|
66
|
+
<Svg style={styles.gradient} preserveAspectRatio='none'>
|
|
67
|
+
<Defs>
|
|
68
|
+
<SvgRadialGradient
|
|
69
|
+
id={gradientId}
|
|
70
|
+
cx={centerCoordinates.cx}
|
|
71
|
+
cy={centerCoordinates.cy}
|
|
72
|
+
rx='50%'
|
|
73
|
+
ry='50%'
|
|
74
|
+
fx={centerCoordinates.cx}
|
|
75
|
+
fy={centerCoordinates.cy}
|
|
76
|
+
>
|
|
77
|
+
{processedStops.map((stop, index) => (
|
|
78
|
+
<Stop
|
|
79
|
+
key={index}
|
|
80
|
+
offset={`${stop.offset * 100}%`}
|
|
81
|
+
stopColor={stop.color}
|
|
82
|
+
stopOpacity={stop.opacity}
|
|
83
|
+
/>
|
|
84
|
+
))}
|
|
85
|
+
</SvgRadialGradient>
|
|
86
|
+
</Defs>
|
|
87
|
+
<Rect
|
|
88
|
+
x='0'
|
|
89
|
+
y='0'
|
|
90
|
+
width='100%'
|
|
91
|
+
height='100%'
|
|
92
|
+
fill={`url(#${gradientId})`}
|
|
93
|
+
/>
|
|
94
|
+
</Svg>
|
|
95
|
+
{children}
|
|
96
|
+
</Box>
|
|
97
|
+
);
|
|
98
|
+
},
|
|
99
|
+
),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
RadialGradient.displayName = 'RadialGradient';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StyledViewProps } from '../../../../../styles';
|
|
3
|
+
import { GradientStop } from '../gradient.types';
|
|
4
|
+
export type RadialGradientCenter = {
|
|
5
|
+
/**
|
|
6
|
+
* Horizontal position (0 = left, 1 = right)
|
|
7
|
+
*/
|
|
8
|
+
x: number;
|
|
9
|
+
/**
|
|
10
|
+
* Vertical position (0 = top, 1 = bottom)
|
|
11
|
+
*/
|
|
12
|
+
y: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type RadialGradientProps = {
|
|
16
|
+
/**
|
|
17
|
+
* Gradient color stops
|
|
18
|
+
* Takes an object of color, offset and opacity.
|
|
19
|
+
* Offset is a number between 0 and 1.
|
|
20
|
+
* Opacity is a number between 0 and 1.
|
|
21
|
+
*/
|
|
22
|
+
stops: GradientStop[];
|
|
23
|
+
/**
|
|
24
|
+
* Optional children to render on top of the gradient
|
|
25
|
+
*/
|
|
26
|
+
children?: ReactNode;
|
|
27
|
+
/**
|
|
28
|
+
* Center position of the gradient.
|
|
29
|
+
* @default { x: 0.5, y: 0.5 }
|
|
30
|
+
*/
|
|
31
|
+
center?: RadialGradientCenter;
|
|
32
|
+
} & StyledViewProps;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LumenStyleSheetTheme } from '../../../../styles';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Background color token from the theme
|
|
5
|
+
*/
|
|
6
|
+
export type BackgroundColorToken = keyof LumenStyleSheetTheme['colors']['bg'];
|
|
7
|
+
|
|
8
|
+
export type GradientStop = {
|
|
9
|
+
/**
|
|
10
|
+
* Color value - can be a design token key (e.g., 'accent', 'error')
|
|
11
|
+
* or a raw color string (e.g., '#FF6B6B', 'rgba(...)')
|
|
12
|
+
*/
|
|
13
|
+
color: BackgroundColorToken | (string & {});
|
|
14
|
+
/**
|
|
15
|
+
* Position of the stop (0-1). If omitted, stops are auto-spread evenly.
|
|
16
|
+
*/
|
|
17
|
+
offset?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Opacity of the color (0-1).
|
|
20
|
+
*/
|
|
21
|
+
opacity?: number;
|
|
22
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
|
|
3
|
+
import {
|
|
4
|
+
resolveGradientColor,
|
|
5
|
+
processGradientStops,
|
|
6
|
+
} from './resolveGradientColor';
|
|
7
|
+
|
|
8
|
+
const bgColors = ledgerLiveThemes.dark.colors.bg;
|
|
9
|
+
|
|
10
|
+
describe('resolveGradientColor', () => {
|
|
11
|
+
describe('resolveGradientColor', () => {
|
|
12
|
+
it('should resolve background tokens to actual color values', () => {
|
|
13
|
+
const accentColor = resolveGradientColor('accent', bgColors);
|
|
14
|
+
expect(accentColor).toBe(bgColors.accent);
|
|
15
|
+
expect(accentColor).toMatch(/^#|^rgb/);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should pass through raw hex colors unchanged', () => {
|
|
19
|
+
expect(resolveGradientColor('#FF6B6B', bgColors)).toBe('#FF6B6B');
|
|
20
|
+
expect(resolveGradientColor('#000', bgColors)).toBe('#000');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should pass through rgb/rgba colors unchanged', () => {
|
|
24
|
+
expect(resolveGradientColor('rgb(255, 0, 0)', bgColors)).toBe(
|
|
25
|
+
'rgb(255, 0, 0)',
|
|
26
|
+
);
|
|
27
|
+
expect(resolveGradientColor('rgba(255, 0, 0, 0.5)', bgColors)).toBe(
|
|
28
|
+
'rgba(255, 0, 0, 0.5)',
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should pass through named CSS colors unchanged', () => {
|
|
33
|
+
expect(resolveGradientColor('red', bgColors)).toBe('red');
|
|
34
|
+
expect(resolveGradientColor('blue', bgColors)).toBe('blue');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('processGradientStops', () => {
|
|
39
|
+
it('should resolve color tokens in stops', () => {
|
|
40
|
+
const stops = [{ color: 'accent' }, { color: 'error' }];
|
|
41
|
+
const processed = processGradientStops(stops, bgColors);
|
|
42
|
+
|
|
43
|
+
expect(processed[0].color).toBe(bgColors.accent);
|
|
44
|
+
expect(processed[1].color).toBe(bgColors.error);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should preserve raw colors in stops', () => {
|
|
48
|
+
const stops = [{ color: '#FF6B6B' }, { color: '#4ECDC4' }];
|
|
49
|
+
const processed = processGradientStops(stops, bgColors);
|
|
50
|
+
|
|
51
|
+
expect(processed[0].color).toBe('#FF6B6B');
|
|
52
|
+
expect(processed[1].color).toBe('#4ECDC4');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should auto-spread offsets for 2 stops', () => {
|
|
56
|
+
const stops = [{ color: '#FF6B6B' }, { color: '#4ECDC4' }];
|
|
57
|
+
const processed = processGradientStops(stops, bgColors);
|
|
58
|
+
|
|
59
|
+
expect(processed[0].offset).toBe(0);
|
|
60
|
+
expect(processed[1].offset).toBe(1);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should auto-spread offsets for 3 stops', () => {
|
|
64
|
+
const stops = [
|
|
65
|
+
{ color: '#FF6B6B' },
|
|
66
|
+
{ color: '#FFD93D' },
|
|
67
|
+
{ color: '#4ECDC4' },
|
|
68
|
+
];
|
|
69
|
+
const processed = processGradientStops(stops, bgColors);
|
|
70
|
+
|
|
71
|
+
expect(processed[0].offset).toBe(0);
|
|
72
|
+
expect(processed[1].offset).toBe(0.5);
|
|
73
|
+
expect(processed[2].offset).toBe(1);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should auto-spread offsets for 4 stops', () => {
|
|
77
|
+
const stops = [
|
|
78
|
+
{ color: '#FF6B6B' },
|
|
79
|
+
{ color: '#FFD93D' },
|
|
80
|
+
{ color: '#6BCB77' },
|
|
81
|
+
{ color: '#4ECDC4' },
|
|
82
|
+
];
|
|
83
|
+
const processed = processGradientStops(stops, bgColors);
|
|
84
|
+
|
|
85
|
+
expect(processed[0].offset).toBeCloseTo(0);
|
|
86
|
+
expect(processed[1].offset).toBeCloseTo(0.333, 2);
|
|
87
|
+
expect(processed[2].offset).toBeCloseTo(0.666, 2);
|
|
88
|
+
expect(processed[3].offset).toBeCloseTo(1);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should preserve explicit offsets', () => {
|
|
92
|
+
const stops = [
|
|
93
|
+
{ color: '#FF6B6B', offset: 0.1 },
|
|
94
|
+
{ color: '#FFD93D', offset: 0.3 },
|
|
95
|
+
{ color: '#4ECDC4', offset: 0.9 },
|
|
96
|
+
];
|
|
97
|
+
const processed = processGradientStops(stops, bgColors);
|
|
98
|
+
|
|
99
|
+
expect(processed[0].offset).toBe(0.1);
|
|
100
|
+
expect(processed[1].offset).toBe(0.3);
|
|
101
|
+
expect(processed[2].offset).toBe(0.9);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should default opacity to 1', () => {
|
|
105
|
+
const stops = [{ color: '#FF6B6B' }, { color: '#4ECDC4' }];
|
|
106
|
+
const processed = processGradientStops(stops, bgColors);
|
|
107
|
+
|
|
108
|
+
expect(processed[0].opacity).toBe(1);
|
|
109
|
+
expect(processed[1].opacity).toBe(1);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should preserve explicit opacity', () => {
|
|
113
|
+
const stops = [
|
|
114
|
+
{ color: '#FF6B6B', opacity: 0.5 },
|
|
115
|
+
{ color: '#4ECDC4', opacity: 0.8 },
|
|
116
|
+
];
|
|
117
|
+
const processed = processGradientStops(stops, bgColors);
|
|
118
|
+
|
|
119
|
+
expect(processed[0].opacity).toBe(0.5);
|
|
120
|
+
expect(processed[1].opacity).toBe(0.8);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should handle single stop', () => {
|
|
124
|
+
const stops = [{ color: '#FF6B6B' }];
|
|
125
|
+
const processed = processGradientStops(stops, bgColors);
|
|
126
|
+
|
|
127
|
+
expect(processed[0].offset).toBe(0);
|
|
128
|
+
expect(processed[0].opacity).toBe(1);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should handle mixed token and raw colors', () => {
|
|
132
|
+
const stops = [
|
|
133
|
+
{ color: 'accent', offset: 0 },
|
|
134
|
+
{ color: '#FF6B6B', offset: 0.5 },
|
|
135
|
+
{ color: 'error', offset: 1 },
|
|
136
|
+
];
|
|
137
|
+
const processed = processGradientStops(stops, bgColors);
|
|
138
|
+
|
|
139
|
+
expect(processed[0].color).toBe(bgColors.accent);
|
|
140
|
+
expect(processed[1].color).toBe('#FF6B6B');
|
|
141
|
+
expect(processed[2].color).toBe(bgColors.error);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { LumenStyleSheetTheme } from '../../../../../styles';
|
|
2
|
+
import type { BackgroundColorToken, GradientStop } from '../gradient.types';
|
|
3
|
+
|
|
4
|
+
type BackgroundColors = LumenStyleSheetTheme['colors']['bg'];
|
|
5
|
+
const DEFAULT_OPACITY = 1;
|
|
6
|
+
const DEFAULT_OFFSET = 0;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolves a gradient stop color to a concrete color value.
|
|
10
|
+
* If the color is a design token, it's resolved from the theme.
|
|
11
|
+
* If it's a raw color string, it's returned as-is.
|
|
12
|
+
*/
|
|
13
|
+
export const resolveGradientColor = (
|
|
14
|
+
color: string,
|
|
15
|
+
bgColors: BackgroundColors,
|
|
16
|
+
): string => {
|
|
17
|
+
const isBackgroundToken = color in bgColors;
|
|
18
|
+
if (isBackgroundToken) {
|
|
19
|
+
return bgColors[color as BackgroundColorToken];
|
|
20
|
+
}
|
|
21
|
+
return color;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Processes gradient stops by:
|
|
26
|
+
* 1. Resolving color tokens to actual values
|
|
27
|
+
* 2. Auto-spreading offsets if not provided
|
|
28
|
+
*/
|
|
29
|
+
export const processGradientStops = (
|
|
30
|
+
stops: GradientStop[],
|
|
31
|
+
bgColors: BackgroundColors,
|
|
32
|
+
): Array<{
|
|
33
|
+
color: string;
|
|
34
|
+
offset: number;
|
|
35
|
+
opacity: number;
|
|
36
|
+
}> => {
|
|
37
|
+
const stopCount = stops.length;
|
|
38
|
+
|
|
39
|
+
return stops.map((stop, index) => {
|
|
40
|
+
const resolvedColor = resolveGradientColor(stop.color, bgColors);
|
|
41
|
+
|
|
42
|
+
// Auto-spread offsets if not provided
|
|
43
|
+
// For n stops: 0, 1/(n-1), 2/(n-1), ..., 1
|
|
44
|
+
const offset =
|
|
45
|
+
stop.offset !== undefined
|
|
46
|
+
? stop.offset
|
|
47
|
+
: stopCount === 1
|
|
48
|
+
? DEFAULT_OFFSET
|
|
49
|
+
: index / (stopCount - 1);
|
|
50
|
+
|
|
51
|
+
const opacity = stop.opacity !== undefined ? stop.opacity : DEFAULT_OPACITY;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
color: resolvedColor,
|
|
55
|
+
offset,
|
|
56
|
+
opacity,
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
};
|