@campxdev/react-native-blueprint 0.1.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.
- package/LICENSE +20 -0
- package/README.md +358 -0
- package/lib/module/app/_layout.js +23 -0
- package/lib/module/app/_layout.js.map +1 -0
- package/lib/module/assets/icons/weather_icons/drizzle.png +0 -0
- package/lib/module/assets/icons/weather_icons/foggy.png +0 -0
- package/lib/module/assets/icons/weather_icons/freezing_rain.png +0 -0
- package/lib/module/assets/icons/weather_icons/partly_cloudy.png +0 -0
- package/lib/module/assets/icons/weather_icons/rainy.png +0 -0
- package/lib/module/assets/icons/weather_icons/showers.png +0 -0
- package/lib/module/assets/icons/weather_icons/sunny_weather.png +0 -0
- package/lib/module/assets/icons/weather_icons/thunderstorm.png +0 -0
- package/lib/module/assets/icons/weather_icons/thunderstorm_hail.png +0 -0
- package/lib/module/components/theme-config.js +265 -0
- package/lib/module/components/theme-config.js.map +1 -0
- package/lib/module/components/ui/Accordion.js +228 -0
- package/lib/module/components/ui/Accordion.js.map +1 -0
- package/lib/module/components/ui/Alert-Dialog.js +266 -0
- package/lib/module/components/ui/Alert-Dialog.js.map +1 -0
- package/lib/module/components/ui/Alert.js +107 -0
- package/lib/module/components/ui/Alert.js.map +1 -0
- package/lib/module/components/ui/AppBar.js +403 -0
- package/lib/module/components/ui/AppBar.js.map +1 -0
- package/lib/module/components/ui/Aspect-Ratio.js +27 -0
- package/lib/module/components/ui/Aspect-Ratio.js.map +1 -0
- package/lib/module/components/ui/Avatar.js +97 -0
- package/lib/module/components/ui/Avatar.js.map +1 -0
- package/lib/module/components/ui/Badge.js +127 -0
- package/lib/module/components/ui/Badge.js.map +1 -0
- package/lib/module/components/ui/Bottom-Sheet.js +144 -0
- package/lib/module/components/ui/Bottom-Sheet.js.map +1 -0
- package/lib/module/components/ui/Button.js +88 -0
- package/lib/module/components/ui/Button.js.map +1 -0
- package/lib/module/components/ui/Card.js +176 -0
- package/lib/module/components/ui/Card.js.map +1 -0
- package/lib/module/components/ui/Checkbox.js +65 -0
- package/lib/module/components/ui/Checkbox.js.map +1 -0
- package/lib/module/components/ui/Collapsible.js +42 -0
- package/lib/module/components/ui/Collapsible.js.map +1 -0
- package/lib/module/components/ui/Context-Menu.js +287 -0
- package/lib/module/components/ui/Context-Menu.js.map +1 -0
- package/lib/module/components/ui/Custom-Card.js +202 -0
- package/lib/module/components/ui/Custom-Card.js.map +1 -0
- package/lib/module/components/ui/Dialog.js +202 -0
- package/lib/module/components/ui/Dialog.js.map +1 -0
- package/lib/module/components/ui/Dropdown-Menu.js +421 -0
- package/lib/module/components/ui/Dropdown-Menu.js.map +1 -0
- package/lib/module/components/ui/Floating-Action.js +50 -0
- package/lib/module/components/ui/Floating-Action.js.map +1 -0
- package/lib/module/components/ui/Greeting-Card.js +392 -0
- package/lib/module/components/ui/Greeting-Card.js.map +1 -0
- package/lib/module/components/ui/Hover-Card.js +96 -0
- package/lib/module/components/ui/Hover-Card.js.map +1 -0
- package/lib/module/components/ui/Icon.js +73 -0
- package/lib/module/components/ui/Icon.js.map +1 -0
- package/lib/module/components/ui/Input.js +74 -0
- package/lib/module/components/ui/Input.js.map +1 -0
- package/lib/module/components/ui/Label.js +44 -0
- package/lib/module/components/ui/Label.js.map +1 -0
- package/lib/module/components/ui/Menubar.js +375 -0
- package/lib/module/components/ui/Menubar.js.map +1 -0
- package/lib/module/components/ui/Native-Only-Animated-View.js +41 -0
- package/lib/module/components/ui/Native-Only-Animated-View.js.map +1 -0
- package/lib/module/components/ui/NavBar.js +352 -0
- package/lib/module/components/ui/NavBar.js.map +1 -0
- package/lib/module/components/ui/Popover.js +101 -0
- package/lib/module/components/ui/Popover.js.map +1 -0
- package/lib/module/components/ui/Progress.js +124 -0
- package/lib/module/components/ui/Progress.js.map +1 -0
- package/lib/module/components/ui/Radio-Group.js +75 -0
- package/lib/module/components/ui/Radio-Group.js.map +1 -0
- package/lib/module/components/ui/Select.js +269 -0
- package/lib/module/components/ui/Select.js.map +1 -0
- package/lib/module/components/ui/Separator.js +58 -0
- package/lib/module/components/ui/Separator.js.map +1 -0
- package/lib/module/components/ui/SizedBox.js +101 -0
- package/lib/module/components/ui/SizedBox.js.map +1 -0
- package/lib/module/components/ui/Skeleton.js +57 -0
- package/lib/module/components/ui/Skeleton.js.map +1 -0
- package/lib/module/components/ui/Slider.js +169 -0
- package/lib/module/components/ui/Slider.js.map +1 -0
- package/lib/module/components/ui/Switch.js +55 -0
- package/lib/module/components/ui/Switch.js.map +1 -0
- package/lib/module/components/ui/Table.js +150 -0
- package/lib/module/components/ui/Table.js.map +1 -0
- package/lib/module/components/ui/Tabs.js +106 -0
- package/lib/module/components/ui/Tabs.js.map +1 -0
- package/lib/module/components/ui/Text.js +69 -0
- package/lib/module/components/ui/Text.js.map +1 -0
- package/lib/module/components/ui/Textarea.js +88 -0
- package/lib/module/components/ui/Textarea.js.map +1 -0
- package/lib/module/components/ui/Theme-Toggle.js +156 -0
- package/lib/module/components/ui/Theme-Toggle.js.map +1 -0
- package/lib/module/components/ui/Toast.js +101 -0
- package/lib/module/components/ui/Toast.js.map +1 -0
- package/lib/module/components/ui/Toggle-Group.js +129 -0
- package/lib/module/components/ui/Toggle-Group.js.map +1 -0
- package/lib/module/components/ui/Toggle.js +106 -0
- package/lib/module/components/ui/Toggle.js.map +1 -0
- package/lib/module/components/ui/Tooltip.js +106 -0
- package/lib/module/components/ui/Tooltip.js.map +1 -0
- package/lib/module/components/ui/index.js +45 -0
- package/lib/module/components/ui/index.js.map +1 -0
- package/lib/module/index.js +19 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/lib/ThemeProvider.js +173 -0
- package/lib/module/lib/ThemeProvider.js.map +1 -0
- package/lib/module/lib/cornerRadius.js +164 -0
- package/lib/module/lib/cornerRadius.js.map +1 -0
- package/lib/module/lib/fonts.js +25 -0
- package/lib/module/lib/fonts.js.map +1 -0
- package/lib/module/lib/theme.js +212 -0
- package/lib/module/lib/theme.js.map +1 -0
- package/lib/module/lib/utils.js +137 -0
- package/lib/module/lib/utils.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/package.json +208 -0
- package/src/app/_layout.tsx +25 -0
- package/src/assets/icons/weather_icons/drizzle.png +0 -0
- package/src/assets/icons/weather_icons/foggy.png +0 -0
- package/src/assets/icons/weather_icons/freezing_rain.png +0 -0
- package/src/assets/icons/weather_icons/partly_cloudy.png +0 -0
- package/src/assets/icons/weather_icons/rainy.png +0 -0
- package/src/assets/icons/weather_icons/showers.png +0 -0
- package/src/assets/icons/weather_icons/sunny_weather.png +0 -0
- package/src/assets/icons/weather_icons/thunderstorm.png +0 -0
- package/src/assets/icons/weather_icons/thunderstorm_hail.png +0 -0
- package/src/components/theme-config.ts +331 -0
- package/src/components/ui/Accordion.tsx +253 -0
- package/src/components/ui/Alert-Dialog.tsx +295 -0
- package/src/components/ui/Alert.tsx +137 -0
- package/src/components/ui/AppBar.tsx +551 -0
- package/src/components/ui/Aspect-Ratio.tsx +25 -0
- package/src/components/ui/Avatar.tsx +103 -0
- package/src/components/ui/Badge.tsx +121 -0
- package/src/components/ui/Bottom-Sheet.tsx +224 -0
- package/src/components/ui/Button.tsx +100 -0
- package/src/components/ui/Card.tsx +185 -0
- package/src/components/ui/Checkbox.tsx +81 -0
- package/src/components/ui/Collapsible.tsx +40 -0
- package/src/components/ui/Context-Menu.tsx +407 -0
- package/src/components/ui/Custom-Card.tsx +226 -0
- package/src/components/ui/Dialog.tsx +240 -0
- package/src/components/ui/Dropdown-Menu.tsx +544 -0
- package/src/components/ui/Floating-Action.tsx +54 -0
- package/src/components/ui/Greeting-Card.tsx +471 -0
- package/src/components/ui/Hover-Card.tsx +101 -0
- package/src/components/ui/Icon.tsx +75 -0
- package/src/components/ui/Input.tsx +90 -0
- package/src/components/ui/Label.tsx +48 -0
- package/src/components/ui/Menubar.tsx +509 -0
- package/src/components/ui/Native-Only-Animated-View.tsx +37 -0
- package/src/components/ui/NavBar.tsx +397 -0
- package/src/components/ui/Popover.tsx +110 -0
- package/src/components/ui/Progress.tsx +138 -0
- package/src/components/ui/Radio-Group.tsx +79 -0
- package/src/components/ui/Select.tsx +344 -0
- package/src/components/ui/Separator.tsx +68 -0
- package/src/components/ui/SizedBox.tsx +116 -0
- package/src/components/ui/Skeleton.tsx +55 -0
- package/src/components/ui/Slider.tsx +222 -0
- package/src/components/ui/Switch.tsx +67 -0
- package/src/components/ui/Table.tsx +170 -0
- package/src/components/ui/Tabs.tsx +119 -0
- package/src/components/ui/Text.tsx +73 -0
- package/src/components/ui/Textarea.tsx +93 -0
- package/src/components/ui/Theme-Toggle.tsx +204 -0
- package/src/components/ui/Toast.tsx +127 -0
- package/src/components/ui/Toggle-Group.tsx +160 -0
- package/src/components/ui/Toggle.tsx +122 -0
- package/src/components/ui/Tooltip.tsx +117 -0
- package/src/components/ui/index.ts +42 -0
- package/src/index.tsx +24 -0
- package/src/lib/ThemeProvider.tsx +204 -0
- package/src/lib/cornerRadius.ts +160 -0
- package/src/lib/fonts.ts +28 -0
- package/src/lib/theme.ts +151 -0
- package/src/lib/utils.ts +146 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive Dark Theme Configuration
|
|
3
|
+
*
|
|
4
|
+
* This file provides complete theme tokens and utilities for consistent
|
|
5
|
+
* dark mode support across all components in the Blueprint library.
|
|
6
|
+
*
|
|
7
|
+
* Best Practices:
|
|
8
|
+
* - Uses HSL color format for better color manipulation
|
|
9
|
+
* - Provides semantic color tokens (not raw colors)
|
|
10
|
+
* - Maintains proper contrast ratios for accessibility (WCAG AA/AAA)
|
|
11
|
+
* - Consistent with Tailwind CSS conventions
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ColorValue } from 'react-native';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Color tokens for light and dark themes
|
|
18
|
+
* Each token follows the pattern: purpose-variant
|
|
19
|
+
*/
|
|
20
|
+
export const THEME_COLORS = {
|
|
21
|
+
light: {
|
|
22
|
+
// Base colors
|
|
23
|
+
background: 'hsl(0, 0%, 100%)', // Pure white
|
|
24
|
+
foreground: 'hsl(0, 0%, 3.9%)', // Near black
|
|
25
|
+
|
|
26
|
+
// Card colors
|
|
27
|
+
card: 'hsl(0, 0%, 100%)',
|
|
28
|
+
cardForeground: 'hsl(0, 0%, 3.9%)',
|
|
29
|
+
|
|
30
|
+
// Popover colors
|
|
31
|
+
popover: 'hsl(0, 0%, 100%)',
|
|
32
|
+
popoverForeground: 'hsl(0, 0%, 3.9%)',
|
|
33
|
+
|
|
34
|
+
// Primary action colors
|
|
35
|
+
primary: 'hsl(0, 0%, 9%)',
|
|
36
|
+
primaryForeground: 'hsl(0, 0%, 98%)',
|
|
37
|
+
|
|
38
|
+
// Secondary action colors
|
|
39
|
+
secondary: 'hsl(0, 0%, 96.1%)',
|
|
40
|
+
secondaryForeground: 'hsl(0, 0%, 9%)',
|
|
41
|
+
|
|
42
|
+
// Muted/subtle colors
|
|
43
|
+
muted: 'hsl(0, 0%, 96.1%)',
|
|
44
|
+
mutedForeground: 'hsl(0, 0%, 45.1%)',
|
|
45
|
+
|
|
46
|
+
// Accent colors
|
|
47
|
+
accent: 'hsl(0, 0%, 96.1%)',
|
|
48
|
+
accentForeground: 'hsl(0, 0%, 9%)',
|
|
49
|
+
|
|
50
|
+
// Destructive/error colors
|
|
51
|
+
destructive: 'hsl(0, 84.2%, 60.2%)',
|
|
52
|
+
destructiveForeground: 'hsl(0, 0%, 98%)',
|
|
53
|
+
|
|
54
|
+
// Border and input colors
|
|
55
|
+
border: 'hsl(0, 0%, 89.8%)',
|
|
56
|
+
input: 'hsl(0, 0%, 89.8%)',
|
|
57
|
+
ring: 'hsl(0, 0%, 63%)',
|
|
58
|
+
|
|
59
|
+
// Success colors
|
|
60
|
+
success: 'hsl(142, 76%, 36%)',
|
|
61
|
+
successForeground: 'hsl(0, 0%, 98%)',
|
|
62
|
+
|
|
63
|
+
// Warning colors
|
|
64
|
+
warning: 'hsl(38, 92%, 50%)',
|
|
65
|
+
warningForeground: 'hsl(0, 0%, 9%)',
|
|
66
|
+
|
|
67
|
+
// Info colors
|
|
68
|
+
info: 'hsl(199, 89%, 48%)',
|
|
69
|
+
infoForeground: 'hsl(0, 0%, 98%)',
|
|
70
|
+
},
|
|
71
|
+
dark: {
|
|
72
|
+
// Base colors
|
|
73
|
+
background: 'hsl(0, 0%, 3.9%)', // Near black
|
|
74
|
+
foreground: 'hsl(0, 0%, 98%)', // Near white
|
|
75
|
+
|
|
76
|
+
// Card colors
|
|
77
|
+
card: 'hsl(0, 0%, 3.9%)',
|
|
78
|
+
cardForeground: 'hsl(0, 0%, 98%)',
|
|
79
|
+
|
|
80
|
+
// Popover colors
|
|
81
|
+
popover: 'hsl(0, 0%, 3.9%)',
|
|
82
|
+
popoverForeground: 'hsl(0, 0%, 98%)',
|
|
83
|
+
|
|
84
|
+
// Primary action colors (inverted from light)
|
|
85
|
+
primary: 'hsl(0, 0%, 98%)',
|
|
86
|
+
primaryForeground: 'hsl(0, 0%, 9%)',
|
|
87
|
+
|
|
88
|
+
// Secondary action colors
|
|
89
|
+
secondary: 'hsl(0, 0%, 14.9%)',
|
|
90
|
+
secondaryForeground: 'hsl(0, 0%, 98%)',
|
|
91
|
+
|
|
92
|
+
// Muted/subtle colors
|
|
93
|
+
muted: 'hsl(0, 0%, 14.9%)',
|
|
94
|
+
mutedForeground: 'hsl(0, 0%, 63.9%)',
|
|
95
|
+
|
|
96
|
+
// Accent colors
|
|
97
|
+
accent: 'hsl(0, 0%, 14.9%)',
|
|
98
|
+
accentForeground: 'hsl(0, 0%, 98%)',
|
|
99
|
+
|
|
100
|
+
// Destructive/error colors
|
|
101
|
+
destructive: 'hsl(0, 62.8%, 30.6%)',
|
|
102
|
+
destructiveForeground: 'hsl(0, 0%, 98%)',
|
|
103
|
+
|
|
104
|
+
// Border and input colors
|
|
105
|
+
border: 'hsl(0, 0%, 14.9%)',
|
|
106
|
+
input: 'hsl(0, 0%, 14.9%)',
|
|
107
|
+
ring: 'hsl(0, 0%, 45%)',
|
|
108
|
+
|
|
109
|
+
// Success colors
|
|
110
|
+
success: 'hsl(142, 76%, 36%)',
|
|
111
|
+
successForeground: 'hsl(0, 0%, 98%)',
|
|
112
|
+
|
|
113
|
+
// Warning colors
|
|
114
|
+
warning: 'hsl(38, 92%, 50%)',
|
|
115
|
+
warningForeground: 'hsl(0, 0%, 9%)',
|
|
116
|
+
|
|
117
|
+
// Info colors
|
|
118
|
+
info: 'hsl(199, 89%, 48%)',
|
|
119
|
+
infoForeground: 'hsl(0, 0%, 98%)',
|
|
120
|
+
},
|
|
121
|
+
} as const;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Convert HSL string to React Native ColorValue
|
|
125
|
+
* Useful for native platform styling where HSL needs to be converted
|
|
126
|
+
*/
|
|
127
|
+
export function hslToRgb(hsl: string): ColorValue {
|
|
128
|
+
// Extract h, s, l from "hsl(h, s%, l%)" format
|
|
129
|
+
const match = hsl.match(
|
|
130
|
+
/hsl\((\d+),\s*(\d+(?:\.\d+)?)%,\s*(\d+(?:\.\d+)?)%\)/
|
|
131
|
+
);
|
|
132
|
+
if (!match || !match[1] || !match[2] || !match[3]) return hsl as ColorValue;
|
|
133
|
+
|
|
134
|
+
const h = parseInt(match[1], 10) / 360;
|
|
135
|
+
const s = parseFloat(match[2]) / 100;
|
|
136
|
+
const l = parseFloat(match[3]) / 100;
|
|
137
|
+
|
|
138
|
+
let r, g, b;
|
|
139
|
+
|
|
140
|
+
if (s === 0) {
|
|
141
|
+
r = g = b = l;
|
|
142
|
+
} else {
|
|
143
|
+
const hue2rgb = (p: number, q: number, t: number) => {
|
|
144
|
+
if (t < 0) t += 1;
|
|
145
|
+
if (t > 1) t -= 1;
|
|
146
|
+
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
147
|
+
if (t < 1 / 2) return q;
|
|
148
|
+
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
149
|
+
return p;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
153
|
+
const p = 2 * l - q;
|
|
154
|
+
|
|
155
|
+
r = hue2rgb(p, q, h + 1 / 3);
|
|
156
|
+
g = hue2rgb(p, q, h);
|
|
157
|
+
b = hue2rgb(p, q, h - 1 / 3);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const toHex = (x: number) => {
|
|
161
|
+
const hex = Math.round(x * 255).toString(16);
|
|
162
|
+
return hex.length === 1 ? '0' + hex : hex;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}` as ColorValue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get theme color value based on current theme mode
|
|
170
|
+
* Automatically selects the appropriate color from light/dark theme
|
|
171
|
+
*/
|
|
172
|
+
export function getThemeColorValue(
|
|
173
|
+
colorKey: keyof typeof THEME_COLORS.light,
|
|
174
|
+
isDark: boolean,
|
|
175
|
+
asRgb = false
|
|
176
|
+
): ColorValue {
|
|
177
|
+
const color = isDark
|
|
178
|
+
? THEME_COLORS.dark[colorKey]
|
|
179
|
+
: THEME_COLORS.light[colorKey];
|
|
180
|
+
return asRgb ? hslToRgb(color) : (color as ColorValue);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Component-specific dark mode class mappings
|
|
185
|
+
* Use these to ensure consistent styling across all components
|
|
186
|
+
*/
|
|
187
|
+
export const DARK_MODE_CLASSES = {
|
|
188
|
+
// Text variants
|
|
189
|
+
text: {
|
|
190
|
+
default: 'dark:text-foreground',
|
|
191
|
+
muted: 'dark:text-muted-foreground',
|
|
192
|
+
primary: 'dark:text-primary-foreground',
|
|
193
|
+
secondary: 'dark:text-secondary-foreground',
|
|
194
|
+
accent: 'dark:text-accent-foreground',
|
|
195
|
+
destructive: 'dark:text-destructive-foreground',
|
|
196
|
+
card: 'dark:text-card-foreground',
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
// Background variants
|
|
200
|
+
background: {
|
|
201
|
+
default: 'dark:bg-background',
|
|
202
|
+
card: 'dark:bg-card',
|
|
203
|
+
primary: 'dark:bg-primary',
|
|
204
|
+
secondary: 'dark:bg-secondary',
|
|
205
|
+
muted: 'dark:bg-muted',
|
|
206
|
+
accent: 'dark:bg-accent',
|
|
207
|
+
destructive: 'dark:bg-destructive',
|
|
208
|
+
popover: 'dark:bg-popover',
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
// Border variants
|
|
212
|
+
border: {
|
|
213
|
+
default: 'dark:border-border',
|
|
214
|
+
input: 'dark:border-input',
|
|
215
|
+
primary: 'dark:border-primary',
|
|
216
|
+
muted: 'dark:border-muted',
|
|
217
|
+
accent: 'dark:border-accent',
|
|
218
|
+
destructive: 'dark:border-destructive',
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
// Interactive states
|
|
222
|
+
state: {
|
|
223
|
+
hover: 'dark:hover:bg-accent',
|
|
224
|
+
active: 'dark:active:bg-accent',
|
|
225
|
+
focus: 'dark:focus:ring-ring',
|
|
226
|
+
disabled: 'dark:disabled:opacity-50',
|
|
227
|
+
},
|
|
228
|
+
} as const;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Helper to combine base classes with dark mode classes
|
|
232
|
+
* Ensures consistent application of dark mode styles
|
|
233
|
+
*/
|
|
234
|
+
export function withDarkMode(
|
|
235
|
+
baseClass: string,
|
|
236
|
+
darkClass: string,
|
|
237
|
+
additionalClasses?: string
|
|
238
|
+
): string {
|
|
239
|
+
return [baseClass, darkClass, additionalClasses].filter(Boolean).join(' ');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Pre-configured class strings for common component patterns
|
|
244
|
+
* Use these for consistent styling across the library
|
|
245
|
+
*/
|
|
246
|
+
export const THEME_PRESETS = {
|
|
247
|
+
// Card-like containers
|
|
248
|
+
card: 'bg-card text-card-foreground dark:bg-card dark:text-card-foreground border border-border dark:border-border',
|
|
249
|
+
|
|
250
|
+
// Input fields
|
|
251
|
+
input:
|
|
252
|
+
'bg-background text-foreground dark:bg-background dark:text-foreground border border-input dark:border-input',
|
|
253
|
+
|
|
254
|
+
// Buttons - primary
|
|
255
|
+
buttonPrimary:
|
|
256
|
+
'bg-primary text-primary-foreground dark:bg-primary dark:text-primary-foreground',
|
|
257
|
+
|
|
258
|
+
// Buttons - secondary
|
|
259
|
+
buttonSecondary:
|
|
260
|
+
'bg-secondary text-secondary-foreground dark:bg-secondary dark:text-secondary-foreground',
|
|
261
|
+
|
|
262
|
+
// Buttons - outline
|
|
263
|
+
buttonOutline:
|
|
264
|
+
'bg-background text-foreground dark:bg-background dark:text-foreground border border-input dark:border-input',
|
|
265
|
+
|
|
266
|
+
// Buttons - ghost
|
|
267
|
+
buttonGhost:
|
|
268
|
+
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent dark:hover:text-accent-foreground',
|
|
269
|
+
|
|
270
|
+
// Destructive elements
|
|
271
|
+
destructive:
|
|
272
|
+
'bg-destructive text-destructive-foreground dark:bg-destructive dark:text-destructive-foreground',
|
|
273
|
+
|
|
274
|
+
// Muted/subtle elements
|
|
275
|
+
muted:
|
|
276
|
+
'bg-muted text-muted-foreground dark:bg-muted dark:text-muted-foreground',
|
|
277
|
+
|
|
278
|
+
// Popover/dropdown
|
|
279
|
+
popover:
|
|
280
|
+
'bg-popover text-popover-foreground dark:bg-popover dark:text-popover-foreground border border-border dark:border-border',
|
|
281
|
+
} as const;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Semantic color map for native components
|
|
285
|
+
* Use these for StyleSheet-based styling
|
|
286
|
+
*/
|
|
287
|
+
export function getNativeThemeColors(isDark: boolean) {
|
|
288
|
+
return {
|
|
289
|
+
background: hslToRgb(getThemeColorValue('background', isDark) as string),
|
|
290
|
+
foreground: hslToRgb(getThemeColorValue('foreground', isDark) as string),
|
|
291
|
+
card: hslToRgb(getThemeColorValue('card', isDark) as string),
|
|
292
|
+
cardForeground: hslToRgb(
|
|
293
|
+
getThemeColorValue('cardForeground', isDark) as string
|
|
294
|
+
),
|
|
295
|
+
primary: hslToRgb(getThemeColorValue('primary', isDark) as string),
|
|
296
|
+
primaryForeground: hslToRgb(
|
|
297
|
+
getThemeColorValue('primaryForeground', isDark) as string
|
|
298
|
+
),
|
|
299
|
+
secondary: hslToRgb(getThemeColorValue('secondary', isDark) as string),
|
|
300
|
+
secondaryForeground: hslToRgb(
|
|
301
|
+
getThemeColorValue('secondaryForeground', isDark) as string
|
|
302
|
+
),
|
|
303
|
+
muted: hslToRgb(getThemeColorValue('muted', isDark) as string),
|
|
304
|
+
mutedForeground: hslToRgb(
|
|
305
|
+
getThemeColorValue('mutedForeground', isDark) as string
|
|
306
|
+
),
|
|
307
|
+
accent: hslToRgb(getThemeColorValue('accent', isDark) as string),
|
|
308
|
+
accentForeground: hslToRgb(
|
|
309
|
+
getThemeColorValue('accentForeground', isDark) as string
|
|
310
|
+
),
|
|
311
|
+
destructive: hslToRgb(getThemeColorValue('destructive', isDark) as string),
|
|
312
|
+
destructiveForeground: hslToRgb(
|
|
313
|
+
getThemeColorValue('destructiveForeground', isDark) as string
|
|
314
|
+
),
|
|
315
|
+
border: hslToRgb(getThemeColorValue('border', isDark) as string),
|
|
316
|
+
input: hslToRgb(getThemeColorValue('input', isDark) as string),
|
|
317
|
+
ring: hslToRgb(getThemeColorValue('ring', isDark) as string),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Type-safe theme color keys
|
|
323
|
+
*/
|
|
324
|
+
export type ThemeColorKey = keyof typeof THEME_COLORS.light;
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Check if a color key exists in the theme
|
|
328
|
+
*/
|
|
329
|
+
export function isValidThemeColor(key: string): key is ThemeColorKey {
|
|
330
|
+
return key in THEME_COLORS.light;
|
|
331
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import * as AccordionPrimitive from '@rn-primitives/accordion';
|
|
2
|
+
import { ChevronDown } from 'lucide-react-native';
|
|
3
|
+
import { cssInterop } from 'nativewind';
|
|
4
|
+
import { Platform, Pressable, View } from 'react-native';
|
|
5
|
+
import Animated, {
|
|
6
|
+
FadeOutUp,
|
|
7
|
+
LayoutAnimationConfig,
|
|
8
|
+
LinearTransition,
|
|
9
|
+
useAnimatedStyle,
|
|
10
|
+
useDerivedValue,
|
|
11
|
+
withTiming,
|
|
12
|
+
} from 'react-native-reanimated';
|
|
13
|
+
import { cn } from '../../lib/utils';
|
|
14
|
+
import { Icon } from './Icon';
|
|
15
|
+
import { TextClassContext } from './Text';
|
|
16
|
+
|
|
17
|
+
cssInterop(View, { className: 'style' });
|
|
18
|
+
cssInterop(Pressable, { className: 'style' });
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Root accordion container with smooth animations
|
|
22
|
+
*
|
|
23
|
+
* A vertically stacked set of interactive panels where only one or multiple panels can be expanded at a time.
|
|
24
|
+
* Features smooth layout animations using Reanimated for a polished user experience.
|
|
25
|
+
*
|
|
26
|
+
* @component
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* // Single selection
|
|
30
|
+
* <Accordion type="single" collapsible defaultValue="item-1">
|
|
31
|
+
* <AccordionItem value="item-1">
|
|
32
|
+
* <AccordionTrigger>
|
|
33
|
+
* <Text>Question 1</Text>
|
|
34
|
+
* </AccordionTrigger>
|
|
35
|
+
* <AccordionContent>
|
|
36
|
+
* <Text>Answer 1</Text>
|
|
37
|
+
* </AccordionContent>
|
|
38
|
+
* </AccordionItem>
|
|
39
|
+
* </Accordion>
|
|
40
|
+
*
|
|
41
|
+
* // Multiple selection
|
|
42
|
+
* <Accordion type="multiple">
|
|
43
|
+
* <AccordionItem value="item-1">
|
|
44
|
+
* <AccordionTrigger><Text>Section 1</Text></AccordionTrigger>
|
|
45
|
+
* <AccordionContent><Text>Content 1</Text></AccordionContent>
|
|
46
|
+
* </AccordionItem>
|
|
47
|
+
* <AccordionItem value="item-2">
|
|
48
|
+
* <AccordionTrigger><Text>Section 2</Text></AccordionTrigger>
|
|
49
|
+
* <AccordionContent><Text>Content 2</Text></AccordionContent>
|
|
50
|
+
* </AccordionItem>
|
|
51
|
+
* </Accordion>
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @accessibility
|
|
55
|
+
* - Implements WAI-ARIA accordion pattern
|
|
56
|
+
* - Keyboard navigation with arrow keys and Enter/Space
|
|
57
|
+
* - Proper ARIA attributes for expanded/collapsed states
|
|
58
|
+
*/
|
|
59
|
+
function Accordion({
|
|
60
|
+
children,
|
|
61
|
+
...props
|
|
62
|
+
}: Omit<AccordionPrimitive.RootProps, 'asChild'> &
|
|
63
|
+
React.RefAttributes<AccordionPrimitive.RootRef>) {
|
|
64
|
+
return (
|
|
65
|
+
<LayoutAnimationConfig skipEntering>
|
|
66
|
+
<AccordionPrimitive.Root
|
|
67
|
+
{...(props as AccordionPrimitive.RootProps)}
|
|
68
|
+
asChild={Platform.OS !== 'web'}
|
|
69
|
+
>
|
|
70
|
+
<Animated.View layout={LinearTransition.duration(200)}>
|
|
71
|
+
{children}
|
|
72
|
+
</Animated.View>
|
|
73
|
+
</AccordionPrimitive.Root>
|
|
74
|
+
</LayoutAnimationConfig>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Individual accordion item with border styling
|
|
80
|
+
*
|
|
81
|
+
* Contains a trigger and content section. Each item must have a unique value prop
|
|
82
|
+
* for identification within the parent Accordion.
|
|
83
|
+
*
|
|
84
|
+
* @component
|
|
85
|
+
* @example
|
|
86
|
+
* ```tsx
|
|
87
|
+
* <AccordionItem value="item-1">
|
|
88
|
+
* <AccordionTrigger>
|
|
89
|
+
* <Text>Click to expand</Text>
|
|
90
|
+
* </AccordionTrigger>
|
|
91
|
+
* <AccordionContent>
|
|
92
|
+
* <Text>Hidden content revealed on expansion</Text>
|
|
93
|
+
* </AccordionContent>
|
|
94
|
+
* </AccordionItem>
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
function AccordionItem({
|
|
98
|
+
children,
|
|
99
|
+
className,
|
|
100
|
+
value,
|
|
101
|
+
...props
|
|
102
|
+
}: AccordionPrimitive.ItemProps &
|
|
103
|
+
React.RefAttributes<AccordionPrimitive.ItemRef>) {
|
|
104
|
+
return (
|
|
105
|
+
<AccordionPrimitive.Item
|
|
106
|
+
className={cn(
|
|
107
|
+
'border-border border-b',
|
|
108
|
+
Platform.select({ web: 'last:border-b-0' }),
|
|
109
|
+
className
|
|
110
|
+
)}
|
|
111
|
+
value={value}
|
|
112
|
+
asChild
|
|
113
|
+
{...props}
|
|
114
|
+
>
|
|
115
|
+
<Animated.View
|
|
116
|
+
className="native:overflow-hidden"
|
|
117
|
+
layout={Platform.select({ native: LinearTransition.duration(200) })}
|
|
118
|
+
>
|
|
119
|
+
{children}
|
|
120
|
+
</Animated.View>
|
|
121
|
+
</AccordionPrimitive.Item>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const Trigger = Platform.OS === 'web' ? View : Pressable;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Clickable trigger button that toggles accordion item expansion
|
|
129
|
+
*
|
|
130
|
+
* Features an animated chevron icon that rotates based on expansion state.
|
|
131
|
+
* Automatically manages focus states and hover effects on web.
|
|
132
|
+
*
|
|
133
|
+
* @component
|
|
134
|
+
* @example
|
|
135
|
+
* ```tsx
|
|
136
|
+
* <AccordionTrigger>
|
|
137
|
+
* <Text>Click to toggle</Text>
|
|
138
|
+
* </AccordionTrigger>
|
|
139
|
+
* ```
|
|
140
|
+
*
|
|
141
|
+
* @accessibility
|
|
142
|
+
* - Automatically includes chevron icon for visual expansion indicator
|
|
143
|
+
* - Focus visible states for keyboard navigation on web
|
|
144
|
+
* - Disabled state prevents interaction
|
|
145
|
+
*/
|
|
146
|
+
function AccordionTrigger({
|
|
147
|
+
className,
|
|
148
|
+
children,
|
|
149
|
+
...props
|
|
150
|
+
}: AccordionPrimitive.TriggerProps & {
|
|
151
|
+
children?: React.ReactNode;
|
|
152
|
+
} & React.RefAttributes<AccordionPrimitive.TriggerRef>) {
|
|
153
|
+
const { isExpanded } = AccordionPrimitive.useItemContext();
|
|
154
|
+
|
|
155
|
+
const progress = useDerivedValue(
|
|
156
|
+
() =>
|
|
157
|
+
isExpanded
|
|
158
|
+
? withTiming(1, { duration: 250 })
|
|
159
|
+
: withTiming(0, { duration: 200 }),
|
|
160
|
+
[isExpanded]
|
|
161
|
+
);
|
|
162
|
+
const chevronStyle = useAnimatedStyle(
|
|
163
|
+
() => ({
|
|
164
|
+
transform: [{ rotate: `${progress.value * 180}deg` }],
|
|
165
|
+
}),
|
|
166
|
+
[progress]
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<TextClassContext.Provider
|
|
171
|
+
value={cn(
|
|
172
|
+
'text-foreground text-left text-sm font-medium',
|
|
173
|
+
Platform.select({ web: 'group-hover:underline' })
|
|
174
|
+
)}
|
|
175
|
+
>
|
|
176
|
+
<AccordionPrimitive.Header>
|
|
177
|
+
<AccordionPrimitive.Trigger {...props} asChild>
|
|
178
|
+
<Trigger
|
|
179
|
+
className={cn(
|
|
180
|
+
'flex-row items-start justify-between gap-4 rounded-md py-4 disabled:opacity-50',
|
|
181
|
+
Platform.select({
|
|
182
|
+
web: 'focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 outline-none transition-all hover:underline focus-visible:ring-[3px] disabled:pointer-events-none [&[data-state=open]>svg]:rotate-180',
|
|
183
|
+
}),
|
|
184
|
+
className
|
|
185
|
+
)}
|
|
186
|
+
>
|
|
187
|
+
<>{children}</>
|
|
188
|
+
<Animated.View style={chevronStyle}>
|
|
189
|
+
<Icon
|
|
190
|
+
as={ChevronDown}
|
|
191
|
+
size={16}
|
|
192
|
+
className={cn(
|
|
193
|
+
'text-muted-foreground shrink-0',
|
|
194
|
+
Platform.select({
|
|
195
|
+
web: 'pointer-events-none translate-y-0.5 transition-transform duration-200',
|
|
196
|
+
})
|
|
197
|
+
)}
|
|
198
|
+
/>
|
|
199
|
+
</Animated.View>
|
|
200
|
+
</Trigger>
|
|
201
|
+
</AccordionPrimitive.Trigger>
|
|
202
|
+
</AccordionPrimitive.Header>
|
|
203
|
+
</TextClassContext.Provider>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Collapsible content container with smooth animations
|
|
209
|
+
*
|
|
210
|
+
* Automatically animates height changes when expanding/collapsing. Uses platform-specific
|
|
211
|
+
* animations (CSS transitions on web, Reanimated on native).
|
|
212
|
+
*
|
|
213
|
+
* @component
|
|
214
|
+
* @example
|
|
215
|
+
* ```tsx
|
|
216
|
+
* <AccordionContent>
|
|
217
|
+
* <Text>This content slides in and out smoothly</Text>
|
|
218
|
+
* <View className="mt-2">
|
|
219
|
+
* <Text>Additional content here</Text>
|
|
220
|
+
* </View>
|
|
221
|
+
* </AccordionContent>
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
function AccordionContent({
|
|
225
|
+
className,
|
|
226
|
+
children,
|
|
227
|
+
...props
|
|
228
|
+
}: AccordionPrimitive.ContentProps &
|
|
229
|
+
React.RefAttributes<AccordionPrimitive.ContentRef>) {
|
|
230
|
+
const { isExpanded } = AccordionPrimitive.useItemContext();
|
|
231
|
+
return (
|
|
232
|
+
<TextClassContext.Provider value="text-foreground text-sm">
|
|
233
|
+
<AccordionPrimitive.Content
|
|
234
|
+
className={cn(
|
|
235
|
+
'overflow-hidden',
|
|
236
|
+
Platform.select({
|
|
237
|
+
web: isExpanded ? 'animate-accordion-down' : 'animate-accordion-up',
|
|
238
|
+
})
|
|
239
|
+
)}
|
|
240
|
+
{...props}
|
|
241
|
+
>
|
|
242
|
+
<Animated.View
|
|
243
|
+
exiting={Platform.select({ native: FadeOutUp.duration(200) })}
|
|
244
|
+
className={cn('pb-4', className)}
|
|
245
|
+
>
|
|
246
|
+
{children}
|
|
247
|
+
</Animated.View>
|
|
248
|
+
</AccordionPrimitive.Content>
|
|
249
|
+
</TextClassContext.Provider>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|