@idealyst/theme 1.2.61 → 1.2.63
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/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/shadow.native.ts +141 -0
- package/src/shadow.ts +119 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -28,6 +28,9 @@ export * from './useResponsiveStyle';
|
|
|
28
28
|
// Style props hook (platform-specific via .native.ts)
|
|
29
29
|
export { useStyleProps, type StyleProps } from './useStyleProps';
|
|
30
30
|
|
|
31
|
+
// Shadow utility (platform-specific via .native.ts)
|
|
32
|
+
export { shadow, type ShadowOptions, type ShadowStyle } from './shadow';
|
|
33
|
+
|
|
31
34
|
// Animation tokens and utilities
|
|
32
35
|
// Note: Use '@idealyst/theme/animation' for full animation API
|
|
33
36
|
export { durations, easings, presets } from './animation/tokens';
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* shadow - Native implementation
|
|
3
|
+
*
|
|
4
|
+
* Creates cross-platform shadow styles from a simple, unified API.
|
|
5
|
+
* All parameters work consistently across platforms.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { View } from '@idealyst/components';
|
|
10
|
+
* import { shadow } from '@idealyst/theme';
|
|
11
|
+
*
|
|
12
|
+
* <View style={shadow({ radius: 10, y: 4 })} />
|
|
13
|
+
* <View style={shadow({ radius: 20, y: 8, color: '#3b82f6', opacity: 0.3 })} />
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Platform } from 'react-native';
|
|
18
|
+
|
|
19
|
+
export interface ShadowOptions {
|
|
20
|
+
/** Shadow radius/size - controls blur and elevation (default: 10) */
|
|
21
|
+
radius?: number;
|
|
22
|
+
/** Horizontal offset in pixels (default: 0) */
|
|
23
|
+
x?: number;
|
|
24
|
+
/** Vertical offset in pixels (default: 4) */
|
|
25
|
+
y?: number;
|
|
26
|
+
/** Shadow color (default: '#000000') */
|
|
27
|
+
color?: string;
|
|
28
|
+
/** Shadow opacity 0-1 (default: 0.15) */
|
|
29
|
+
opacity?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ShadowStyle {
|
|
33
|
+
// Web
|
|
34
|
+
boxShadow?: string;
|
|
35
|
+
// iOS
|
|
36
|
+
shadowColor?: string;
|
|
37
|
+
shadowOffset?: { width: number; height: number };
|
|
38
|
+
shadowOpacity?: number;
|
|
39
|
+
shadowRadius?: number;
|
|
40
|
+
// Android
|
|
41
|
+
elevation?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Approximate Android elevation from shadow radius.
|
|
46
|
+
* Range is clamped to 0-24 (Android's max elevation).
|
|
47
|
+
*/
|
|
48
|
+
function radiusToElevation(radius: number): number {
|
|
49
|
+
// Map radius to elevation: radius 10 ≈ elevation 3-4
|
|
50
|
+
// This provides reasonable visual parity with iOS/web
|
|
51
|
+
const elevation = Math.round(radius / 3);
|
|
52
|
+
return Math.max(0, Math.min(24, elevation));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parse color string to RGBA components.
|
|
57
|
+
* Supports: #RGB, #RRGGBB, #RRGGBBAA, rgb(), rgba()
|
|
58
|
+
*/
|
|
59
|
+
function parseColor(color: string): { r: number; g: number; b: number; a: number } {
|
|
60
|
+
// Default fallback
|
|
61
|
+
const fallback = { r: 0, g: 0, b: 0, a: 1 };
|
|
62
|
+
|
|
63
|
+
// Handle rgba(r, g, b, a) or rgb(r, g, b)
|
|
64
|
+
const rgbaMatch = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)/i);
|
|
65
|
+
if (rgbaMatch) {
|
|
66
|
+
return {
|
|
67
|
+
r: parseInt(rgbaMatch[1], 10),
|
|
68
|
+
g: parseInt(rgbaMatch[2], 10),
|
|
69
|
+
b: parseInt(rgbaMatch[3], 10),
|
|
70
|
+
a: rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Handle hex: #RGB, #RRGGBB, #RRGGBBAA
|
|
75
|
+
const hex = color.replace('#', '');
|
|
76
|
+
if (hex.length === 3) {
|
|
77
|
+
// #RGB -> #RRGGBB
|
|
78
|
+
return {
|
|
79
|
+
r: parseInt(hex[0] + hex[0], 16),
|
|
80
|
+
g: parseInt(hex[1] + hex[1], 16),
|
|
81
|
+
b: parseInt(hex[2] + hex[2], 16),
|
|
82
|
+
a: 1,
|
|
83
|
+
};
|
|
84
|
+
} else if (hex.length === 6) {
|
|
85
|
+
// #RRGGBB
|
|
86
|
+
return {
|
|
87
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
88
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
89
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
90
|
+
a: 1,
|
|
91
|
+
};
|
|
92
|
+
} else if (hex.length === 8) {
|
|
93
|
+
// #RRGGBBAA
|
|
94
|
+
return {
|
|
95
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
96
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
97
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
98
|
+
a: parseInt(hex.slice(6, 8), 16) / 255,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return fallback;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Creates a cross-platform shadow style object.
|
|
107
|
+
*
|
|
108
|
+
* @param options - Shadow configuration
|
|
109
|
+
* @returns Style object with platform-appropriate shadow properties
|
|
110
|
+
*/
|
|
111
|
+
export function shadow(options: ShadowOptions = {}): ShadowStyle {
|
|
112
|
+
const {
|
|
113
|
+
radius = 10,
|
|
114
|
+
x = 0,
|
|
115
|
+
y = 4,
|
|
116
|
+
color = '#000000',
|
|
117
|
+
opacity = 0.15,
|
|
118
|
+
} = options;
|
|
119
|
+
|
|
120
|
+
const parsed = parseColor(color);
|
|
121
|
+
// Multiply color's alpha with opacity parameter
|
|
122
|
+
const finalAlpha = parsed.a * opacity;
|
|
123
|
+
|
|
124
|
+
if (Platform.OS === 'android') {
|
|
125
|
+
// Android: elevation + shadowColor (limited control)
|
|
126
|
+
// Offset (x, y) is not supported - elevation controls everything
|
|
127
|
+
// Bake opacity into shadowColor as alpha channel
|
|
128
|
+
return {
|
|
129
|
+
elevation: radiusToElevation(radius),
|
|
130
|
+
shadowColor: `rgba(${parsed.r}, ${parsed.g}, ${parsed.b}, ${finalAlpha})`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// iOS: Full shadow control
|
|
135
|
+
return {
|
|
136
|
+
shadowColor: `rgb(${parsed.r}, ${parsed.g}, ${parsed.b})`,
|
|
137
|
+
shadowOffset: { width: x, height: y },
|
|
138
|
+
shadowOpacity: finalAlpha,
|
|
139
|
+
shadowRadius: radius / 2, // iOS shadowRadius is roughly half the CSS blur
|
|
140
|
+
};
|
|
141
|
+
}
|
package/src/shadow.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* shadow - Web implementation
|
|
3
|
+
*
|
|
4
|
+
* Creates cross-platform shadow styles from a simple, unified API.
|
|
5
|
+
* All parameters work consistently across platforms.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { View } from '@idealyst/components';
|
|
10
|
+
* import { shadow } from '@idealyst/theme';
|
|
11
|
+
*
|
|
12
|
+
* <View style={shadow({ radius: 10, y: 4 })} />
|
|
13
|
+
* <View style={shadow({ radius: 20, y: 8, color: '#3b82f6', opacity: 0.3 })} />
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface ShadowOptions {
|
|
18
|
+
/** Shadow radius/size - controls blur and spread (default: 10) */
|
|
19
|
+
radius?: number;
|
|
20
|
+
/** Horizontal offset in pixels (default: 0) */
|
|
21
|
+
x?: number;
|
|
22
|
+
/** Vertical offset in pixels (default: 4) */
|
|
23
|
+
y?: number;
|
|
24
|
+
/** Shadow color (default: '#000000') */
|
|
25
|
+
color?: string;
|
|
26
|
+
/** Shadow opacity 0-1 (default: 0.15) */
|
|
27
|
+
opacity?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ShadowStyle {
|
|
31
|
+
// Web
|
|
32
|
+
boxShadow?: string;
|
|
33
|
+
// iOS
|
|
34
|
+
shadowColor?: string;
|
|
35
|
+
shadowOffset?: { width: number; height: number };
|
|
36
|
+
shadowOpacity?: number;
|
|
37
|
+
shadowRadius?: number;
|
|
38
|
+
// Android
|
|
39
|
+
elevation?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Parse color string to RGBA components.
|
|
44
|
+
* Supports: #RGB, #RRGGBB, #RRGGBBAA, rgb(), rgba()
|
|
45
|
+
*/
|
|
46
|
+
function parseColor(color: string): { r: number; g: number; b: number; a: number } {
|
|
47
|
+
// Default fallback
|
|
48
|
+
const fallback = { r: 0, g: 0, b: 0, a: 1 };
|
|
49
|
+
|
|
50
|
+
// Handle rgba(r, g, b, a) or rgb(r, g, b)
|
|
51
|
+
const rgbaMatch = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)/i);
|
|
52
|
+
if (rgbaMatch) {
|
|
53
|
+
return {
|
|
54
|
+
r: parseInt(rgbaMatch[1], 10),
|
|
55
|
+
g: parseInt(rgbaMatch[2], 10),
|
|
56
|
+
b: parseInt(rgbaMatch[3], 10),
|
|
57
|
+
a: rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Handle hex: #RGB, #RRGGBB, #RRGGBBAA
|
|
62
|
+
const hex = color.replace('#', '');
|
|
63
|
+
if (hex.length === 3) {
|
|
64
|
+
// #RGB -> #RRGGBB
|
|
65
|
+
return {
|
|
66
|
+
r: parseInt(hex[0] + hex[0], 16),
|
|
67
|
+
g: parseInt(hex[1] + hex[1], 16),
|
|
68
|
+
b: parseInt(hex[2] + hex[2], 16),
|
|
69
|
+
a: 1,
|
|
70
|
+
};
|
|
71
|
+
} else if (hex.length === 6) {
|
|
72
|
+
// #RRGGBB
|
|
73
|
+
return {
|
|
74
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
75
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
76
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
77
|
+
a: 1,
|
|
78
|
+
};
|
|
79
|
+
} else if (hex.length === 8) {
|
|
80
|
+
// #RRGGBBAA
|
|
81
|
+
return {
|
|
82
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
83
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
84
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
85
|
+
a: parseInt(hex.slice(6, 8), 16) / 255,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return fallback;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Creates a cross-platform shadow style object.
|
|
94
|
+
*
|
|
95
|
+
* @param options - Shadow configuration
|
|
96
|
+
* @returns Style object with platform-appropriate shadow properties
|
|
97
|
+
*/
|
|
98
|
+
export function shadow(options: ShadowOptions = {}): ShadowStyle {
|
|
99
|
+
const {
|
|
100
|
+
radius = 10,
|
|
101
|
+
x = 0,
|
|
102
|
+
y = 4,
|
|
103
|
+
color = '#000000',
|
|
104
|
+
opacity = 0.15,
|
|
105
|
+
} = options;
|
|
106
|
+
|
|
107
|
+
const parsed = parseColor(color);
|
|
108
|
+
// Multiply color's alpha with opacity parameter
|
|
109
|
+
const finalAlpha = parsed.a * opacity;
|
|
110
|
+
|
|
111
|
+
// Derive blur and spread from radius for natural-looking shadows
|
|
112
|
+
// Blur is the main visual size, spread adds subtle expansion
|
|
113
|
+
const blur = radius;
|
|
114
|
+
const spread = Math.round(radius * 0.1); // 10% of radius for subtle spread
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
boxShadow: `${x}px ${y}px ${blur}px ${spread}px rgba(${parsed.r}, ${parsed.g}, ${parsed.b}, ${finalAlpha})`,
|
|
118
|
+
};
|
|
119
|
+
}
|